diff --git a/configuration/backup.sh b/configuration/backup.sh new file mode 100644 index 00000000000..4f1834afbbd --- /dev/null +++ b/configuration/backup.sh @@ -0,0 +1,147 @@ +#!/bin/sh + +# This is a template for creating backup of data and persist them +# to a central backup server. Make sure to adapt this script to your needs. +# +# Please do not change the name of the bakup (bup save -n) because that can +# have strange effects. If you change it you absolutely need to make sure that +# only the same root directories are associated to that name. It is NOT possible +# to backup different root directories to the same backup name. +# +# Documentation: http://wiki.sapsailing.com/wiki/amazon-ec2-backup-strategy +# +# Maintainer: simon.marcel.pamies@sap.com + +# If you want to just backup directories follow steps 1-4. If you want to +# backup MongoDB and/or MySQL databases then follow steps 5-8. + +# 1: Select directories to backup. Each directory needs to be separated from +# others by a blank. Directories with blanks need to be surrounded by quotes. +BACKUP_DIRECTORIES="/etc /var/log/mongodb" + +# 2: Prefix for backup - set that to the hostname of your server or any other +# name that describes your server. No blanks or special characters allowed. +PREFIX="database" + +# 3: Directory for temporary files. If you do not want to backup MySQL or MongoBD +# then you can just leave it to /tmp. +TARGET_DIR=/tmp + +# 4: Include any patterns that should match files to be ignored. The parameter +# is a regular expression. You can use multiple --exclude-rx= parameters. +BUP_IGNORES='--exclude-rx=/war/$' + +# ************************************** +# If you do not want any MySQL or MongoDB backup then you're done. +# If not then continue with the following steps. +# ************************************** + +# 5: Enable MongoDB backup by setting this variable to 1 +ENABLE_MONGODB_BACKUP=0 + +# 6: Provide MongoDB instances to backup. Format is : +MONGODB_DATABASES="10202:winddb 10201:winddb" + +# No need to change the following values in most cases +MONGOEXPORT_CMD="/opt/mongodb-linux-x86_64-1.8.1/bin/mongoexport" +MONGODB_COLLECTIONS_NO_WIND="EVENTS LEADERBOARD_GROUPS LEADERBOARDS RACE_LOGS RACES_MESSAGES REGATTA_FOR_RACE_ID REGATTAS TRACTRAC_CONFIGURATIONS COMMAND_MESSAGES COMPETITORS CONFIGURATIONS LAST_MESSAGE_COUNT VIDEOS" +MONGODB_COLLECTIONS_WIND="WIND_TRACKS" + +# 7: Enable MySQL backup by setting this variable to 1 +ENABLE_MYSQL_BACKUP=0 + +# 8: Space separated list of mysql databases to backup +MYSQL_DATABASES="bugs mysql" + +# 9: Provide the username and password for MySQL +MYSQLEXPORT_CMD="mysqldump -u root --password=xxxx" + +# ************************************** +# You're done. +# ************************************** + +# Configuration for external backup server - needs a ssh key +BACKUP_SERVER="backup@172.31.25.136" + +# Set date for this backup - this makes it possible to restore +# files from different branches (named backups) +BACKUP_DATE=`date +%s` + +# Aliases +BUP_CMD="/opt/bup/bup" +BUP_ADDITIONAL="-r $BACKUP_SERVER:/home/backup/$PREFIX" +BUP_CMD_INDEX="$BUP_CMD index $BUP_IGNORES" +BUP_CMD_SAVE="$BUP_CMD save --date=$BACKUP_DATE $BUP_ADDITIONAL" + +PARAM=$@ + +# Make sure to install the right libraries +yum install python-devel fuse-python pyxattr pylibacl perl-Time-HiRes + +# We need to set HOME in case it isn't set right - assuming root is calling +export HOME=/root + +# Make sure to init local and remote repository +# It is perfectly safe to reinitialize a repository w/o overwriting data +$BUP_CMD init +ssh $BACKUP_SERVER "/opt/bup/bup init -r /home/backup/$PREFIX" + +# Backup general directories +for OTHER_DIR in $BACKUP_DIRECTORIES; do + NORMALIZED_DIR_NAME=${OTHER_DIR//\//-} + echo "Backing up $OTHER_DIR" + $BUP_CMD_INDEX $OTHER_DIR + $BUP_CMD_SAVE -n dir$NORMALIZED_DIR_NAME $OTHER_DIR + # no need to remove any backup files here +done + +# ************************************** +# Start of database specific backup +# ************************************** + +if [ $ENABLE_MONGODB_BACKUP -eq 1 ]; then + # Export MongoDB instances with wind upon request (weekly parameter provided) + if [ "$PARAM" = "weekly" ]; then + for MONGO_CONFIG in $MONGODB_DATABASES; do + PARTS=(${MONGO_CONFIG//:/ }) + mkdir $TARGET_DIR/mongodb-${PARTS[0]}-wind + for COLLECTION in $MONGODB_COLLECTIONS_WIND; do + echo "$MONGOEXPORT_CMD --port ${PARTS[0]} -d ${PARTS[1]} -c $COLLECTION > $TARGET_DIR/mongodb-${PARTS[0]}-wind/$COLLECTION.json" + $MONGOEXPORT_CMD --port ${PARTS[0]} -d ${PARTS[1]} -c $COLLECTION > $TARGET_DIR/mongodb-${PARTS[0]}-wind/$COLLECTION.json + done + done + fi + + # Export MongoDB instances without wind + for MONGO_CONFIG in $MONGODB_DATABASES; do + PARTS=(${MONGO_CONFIG//:/ }) + mkdir $TARGET_DIR/mongodb-${PARTS[0]} + for COLLECTION in $MONGODB_COLLECTIONS_NO_WIND; do + echo "$MONGOEXPORT_CMD --port ${PARTS[0]} -d ${PARTS[1]} -c $COLLECTION > $TARGET_DIR/mongodb-${PARTS[0]}/$COLLECTION.json" + $MONGOEXPORT_CMD --port ${PARTS[0]} -d ${PARTS[1]} -c $COLLECTION > $TARGET_DIR/mongodb-${PARTS[0]}/$COLLECTION.json + done + done + + # Backup the whole thing now and push to external server + $BUP_CMD_INDEX $TARGET_DIR + $BUP_CMD_SAVE -n mongodb-databases $TARGET_DIR/ + find $TARGET_DIR -name *.json | xargs rm -f +fi + +if [ $ENABLE_MYSQL_BACKUP -eq 1 ]; then + # Export MySQL dump for selected databases + for MYSQL_DB in $MYSQL_DATABASES; do + mkdir $TARGET_DIR/mysql + echo "$MYSQLEXPORT_CMD $MYSQL_DB > $TARGET_DIR/mysql/$MYSQL_DB.sql" + $MYSQLEXPORT_CMD $MYSQL_DB > $TARGET_DIR/mysql/$MYSQL_DB.sql + done + + # Backup the whole thing now and push to external server + $BUP_CMD_INDEX $TARGET_DIR + $BUP_CMD_SAVE -n mysql-databases $TARGET_DIR/mysql + rm -f $TARGET_DIR/mysql/*.sql +fi + +# ************************************** +# End of database specific backup +# ************************************** diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index 3c3b628b7e2..4f8bfb56ecb 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -241,8 +241,19 @@ if [[ "$@" == "release" ]]; then if [[ $OSGI_BUNDLE_NAME != "" ]]; then SIMPLE_VERSION_INFO="$OSGI_BUNDLE_NAME-$HEAD_DATE" fi - + + # removing compile reports as they do not belong into a release + find $ACDIR -name soycReport | xargs rm -rf + mkdir $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO + echo "MONGODB_NAME=myspecificevent +REPLICATION_CHANNEL=myspecificevent +SERVER_STARTUP_NOTIFY=simon.marcel.pamies@sap.com +SERVER_NAME=MYSPECIFICEVENT +USE_ENVIRONMENT=live-server +INSTALL_FROM_RELEASE=$SIMPLE_VERSION_INFO + " >> $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO/amazon-launch-config.txt + `which tar` cvzf $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO/$SIMPLE_VERSION_INFO.tar.gz * cp $ACDIR/env.sh $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO cp $PROJECT_HOME/build/release-notes.txt $PROJECT_HOME/dist/$SIMPLE_VERSION_INFO diff --git a/configuration/git-commit-size-hook.sh b/configuration/git-commit-size-hook.sh new file mode 100644 index 00000000000..2442f5fc934 --- /dev/null +++ b/configuration/git-commit-size-hook.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Script to warn whenever the size of a commit exceeds +# a certain limit + +# File size limit is meant to be configured through 'hooks.filesizelimit' setting +filesizelimit=$(git config hooks.filesizelimit) + +# If we haven't configured a file size limit, use default value of about 100M +if [ -z "$filesizelimit" ]; then + filesizelimit=10000000 # 10MB +fi + +z40=0000000000000000000000000000000000000000 +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Delete + echo "" + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + biggest_checkin_normalized=$(git diff-tree -r $range | sort -k 4 -n -r | head -1 ) + file_blob_id=`echo $biggest_checkin_normalized | cut -d ' ' -f4,4` + filesize=`git cat-file -s $file_blob_id` + + if [ $filesize -gt $filesizelimit ]; then + filename=`echo $biggest_checkin_normalized | cut -d ' ' -f6,6` + echo "You are attempting to push a commit containing a file ($filename) that is larger than the allowed $filesizelimit bytes ($filesize bytes)!" + echo "Please inform at least Axel Uhl and Simon Pamies about that!" + echo "(This message comes from a script located in .git/hooks/pre-push)" + exec < /dev/tty + read -p "I will comply (yes/[no]): " input + if [ "$input" = "yes" ]; then + exit 0 + else + echo "Aborting push..." + fi + fi + fi +done diff --git a/configuration/httpprinter.py b/configuration/httpprinter.py new file mode 100644 index 00000000000..858449fc8da --- /dev/null +++ b/configuration/httpprinter.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +import socket + +host = '' +port = 50000 +backlog = 5 +size = 1024 +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.bind((host,port)) +s.listen(backlog) +while 1: + client, address = s.accept() + data = client.recv(size) + if data: + print data + client.close() diff --git a/configuration/init.d/mongodb b/configuration/init.d/mongodb new file mode 100644 index 00000000000..69fea4bd272 --- /dev/null +++ b/configuration/init.d/mongodb @@ -0,0 +1,58 @@ +#!/bin/bash +# +# mongodb Starts sailing services like MongoDB +# +# chkconfig: 2345 95 10 +# description: Sailing contains all sailing services + +# Source function library. +. /etc/init.d/functions + +RETVAL=0 + +MONGODB_BINARY=/opt/mongodb-linux-x86_64-1.8.1/bin/mongod +MONGODB_CONF_DIR=/etc/mongodb +MONGODB_START_INSTANCES="default dev live archive racecommittee_dev" + +start_mongodb() { + for conf in $MONGODB_START_INSTANCES; do + echo "Starting MongoDB server $conf" + rm -f /var/lib/mongodb/$conf/mongod.lock + su - mongodb -c "$MONGODB_BINARY --quiet --fork -f $MONGODB_CONF_DIR/mongodb-$conf.cfg" + RETVAL=$? + [ $RETVAL -eq 0 ] && success || failure + done + + # give database servers some time to start + sleep 2 +} + +stop_mongodb() { + killall mongod + sleep 2 +} + +# See how we were called. +case "$1" in + start) + start_mongodb + ;; + stop) + stop_mongodb + ;; + start-mongo) + start_mongodb + ;; + start-wiki) + start_wiki + ;; + status) + status mongod + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|status}" + RETVAL=3 +esac + +exit $RETVAL diff --git a/configuration/live-server-env.sh b/configuration/live-server-env.sh new file mode 100644 index 00000000000..c307832707a --- /dev/null +++ b/configuration/live-server-env.sh @@ -0,0 +1,16 @@ +DEPLOY_TO=server +SERVER_NAME=LIVE +MEMORY=4096m +REPLICATION_HOST=172.31.25.253 +REPLICATION_CHANNEL=sapsailinganalytics-live +TELNET_PORT=14888 +SERVER_PORT=8888 +MONGODB_HOST=172.31.25.253 +MONGODB_PORT=10201 +EXPEDITION_PORT=2010 +REPLICATE_ON_START=False +REPLICATE_MASTER_SERVLET_HOST= +REPLICATE_MASTER_SERVLET_PORT= +REPLICATE_MASTER_QUEUE_HOST= +REPLICATE_MASTER_QUEUE_PORT= +ADDITIONAL_JAVA_ARGS="-XX:+UseMembar -XX:+UseParallelGC -XX:+UseParallelOldGC -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:logs/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dorg.eclipse.jetty.LEVEL=OFF -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog" diff --git a/configuration/s3cfg.template b/configuration/s3cfg.template new file mode 100644 index 00000000000..fe56fc955d8 --- /dev/null +++ b/configuration/s3cfg.template @@ -0,0 +1,59 @@ +[default] +access_key = +secret_key = +access_token = +add_content_encoding = True +add_encoding_exts = +add_headers = +bucket_location = US +cache_file = +cloudfront_host = cloudfront.amazonaws.com +default_mime_type = binary/octet-stream +delay_updates = False +delete_after = False +delete_after_fetch = False +delete_removed = False +dry_run = False +enable_multipart = True +encoding = UTF-8 +encrypt = False +follow_symlinks = False +force = False +get_continue = False +gpg_command = /usr/bin/gpg +gpg_decrypt = %(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s +gpg_encrypt = %(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s +gpg_passphrase = sailaway +guess_mime_type = True +host_base = s3.amazonaws.com +host_bucket = %(bucket)s.s3.amazonaws.com +human_readable_sizes = False +ignore_failed_copy = False +invalidate_default_index_on_cf = False +invalidate_default_index_root_on_cf = True +invalidate_on_cf = False +list_md5 = False +log_target_prefix = +max_delete = -1 +mime_type = +multipart_chunk_size_mb = 15 +preserve_attrs = True +progress_meter = True +proxy_host = +proxy_port = 0 +put_continue = False +recursive = False +recv_chunk = 4096 +reduced_redundancy = False +send_chunk = 4096 +server_side_encryption = False +simpledb_host = sdb.amazonaws.com +skip_existing = False +socket_timeout = 300 +urlencoding_mode = normal +use_https = False +use_mime_magic = True +verbosity = WARNING +website_endpoint = http://%(bucket)s.s3-website-%(location)s.amazonaws.com/ +website_error = +website_index = index.html diff --git a/configuration/sailing b/configuration/sailing new file mode 100755 index 00000000000..9a4da0b50dc --- /dev/null +++ b/configuration/sailing @@ -0,0 +1,60 @@ +#!/bin/bash +# +# sailing Starts sailing services +# +# chkconfig: 2345 95 10 +# description: Sailing contains all sailing services + +# Source function library. +. /etc/init.d/functions + +RETVAL=0 + +JAVA_START_INSTANCES="server" +GIT_REPOSITORY=/home/sailing/code + +start_tmux() { + su - sailing -c "/home/sailing/bin/tmuxConsole.sh unattended" + success +} + +start_servers() { + for conf in $JAVA_START_INSTANCES; do + echo "Checking for amazon update..." + su - sailing -c "cd /home/sailing/servers/$conf && $GIT_REPOSITORY/java/target/refreshInstance.sh auto-install" 2>/var/log/sailing.err >/var/log/sailing.out + echo "Starting Java server $conf" + su - sailing -c "cd /home/sailing/servers/$conf && ./start" 2>/var/log/sailing.err >/var/log/sailing.out + RETVAL=$? + [ $RETVAL -eq 0 ] && success || failure + done +} + +stop_servers() { + for conf in $JAVA_START_INSTANCES; do + echo "Stopping Java server $conf" + su - sailing -c "cd /home/sailing/servers/$conf && ./stop" + RETVAL=$? + [ $RETVAL -eq 0 ] && success || failure + done + +} + +# See how we were called. +case "$1" in + start) + start_servers + ;; + stop) + stop_servers + ;; + status) + status java + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|status|stop}" + RETVAL=3 +esac + +exit $RETVAL + diff --git a/doc/TracTrac_Urls.txt b/doc/TracTrac_Urls.txt index 29cd74affcc..fbcf9ac3b8d 100644 --- a/doc/TracTrac_Urls.txt +++ b/doc/TracTrac_Urls.txt @@ -37,6 +37,9 @@ http://germanmaster.traclive.dk/events/event_20120615_KielerWoch1/jsonservice.ph Extreme Sailing Series 2012: http://germanmaster.traclive.dk/events/event_20120101_ExtremeSai/jsonservice.php +Extreme Sailing Series 2014: +http://secondary.traclive.dk/events/event_20140218_ESSSingapo/jsonservice.php + OBMR 2012: http://germanmaster.traclive.dk/events/event_20121031_OBMR/jsonservice.php diff --git a/home.md b/home.md index c84bd2926fa..865bb637bba 100644 --- a/home.md +++ b/home.md @@ -12,8 +12,9 @@ Like businesses, sailors need the latest information to make strategic decisions * [[General Project Information|wiki/general-information]] * Production Environment * [[Amazon EC2|wiki/amazon-ec2]] - * [[Production Environment|wiki/production-environment]] - * [[TracTrac Lifecycle for Event Managers|wiki/tractrac-lifecycle]] + * [[EC2 Backup Strategy|wiki/amazon-ec2-backup-strategy]] + * [[TracTrac|wiki/tractrac-lifecycle]] + * [[Analytics on a stick|wiki/analytics-on-a-stick]] * Information for Developers * [[Cook Book|wiki/cook-book]] * [[OnBoarding Information|wiki/onboarding]] @@ -24,8 +25,10 @@ Like businesses, sailors need the latest information to make strategic decisions * [[Server Replication|wiki/server-replication]] * [[Configure Races on Server|wiki/typical-development-scenarios]] * [[Development Environment|wiki/development-environment]] + * [[Working with GWT UI Binder|wiki/gwt-ui-binder]] * [[Working with GWT Locally|wiki/local-gwt]] * [[Mobile Development|wiki/mobile-development]] + * [[Java De(Serialization) and Circular Dependencies|wiki/java-de-serialization-and-circular-dependencies]] * Information for Webdesigners * [[Layout repository|wiki/webdesign]] * [[Create boat graphics for the 2D race viewer|wiki/boatGraphicsSVG]] @@ -34,7 +37,7 @@ Like businesses, sailors need the latest information to make strategic decisions * [[Architecture and Infrastructure|wiki/architecture-and-infrastructure]] * [[Sailing Domain Algorithms|wiki/sailing-domain-algorithms]] * [[Inventory|wiki/inventar-liste]] - * [[Smartphone Tracking|wiki/smartphone-tracking]] + * [[RaceLog tracking|wiki/racelog-tracking]] * [[Racecommittee App|wiki/racecommittee-app]] * [[Web Services API|wiki/sailing-webservices]] * [[Theses (Bachelor, Master, ...)|wiki/theses]] @@ -54,4 +57,4 @@ Like businesses, sailors need the latest information to make strategic decisions ### TracTrac Workshop in Kopenhagen -* [[TracTrac Workshop in October 2013 in Kopenhagen|wiki/TracTracWorkshop2013]] \ No newline at end of file +* [[TracTrac Workshop in October 2013 in Kopenhagen|wiki/TracTracWorkshop2013]] diff --git a/java/com.google.gwt.osgi/.classpath b/java/com.google.gwt.osgi/.classpath index 7d23d84433e..d3b062d279c 100755 --- a/java/com.google.gwt.osgi/.classpath +++ b/java/com.google.gwt.osgi/.classpath @@ -1,6 +1,5 @@ - diff --git a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF index 3ebccfdf386..111bfccff26 100755 --- a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF @@ -2,34 +2,12 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Osgi Bundle-SymbolicName: com.google.gwt.osgi -Bundle-Version: 2.6.0.qualifier +Bundle-Version: 2.6.1.qualifier Bundle-Vendor: GOOGLE Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Bundle-ClassPath: lib/gwt-dev.jar, - lib/gwt-user.jar, +Bundle-ClassPath: lib/gwt-user.jar, . -Export-Package: com.gargoylesoftware.htmlunit, - com.gargoylesoftware.htmlunit.attachment, - com.gargoylesoftware.htmlunit.gae, - com.gargoylesoftware.htmlunit.html, - com.gargoylesoftware.htmlunit.html.applets, - com.gargoylesoftware.htmlunit.html.impl, - com.gargoylesoftware.htmlunit.html.xpath, - com.gargoylesoftware.htmlunit.javascript, - com.gargoylesoftware.htmlunit.javascript.background, - com.gargoylesoftware.htmlunit.javascript.configuration, - com.gargoylesoftware.htmlunit.javascript.host, - com.gargoylesoftware.htmlunit.javascript.host.canvas, - com.gargoylesoftware.htmlunit.javascript.host.css, - com.gargoylesoftware.htmlunit.javascript.host.html, - com.gargoylesoftware.htmlunit.javascript.host.xml, - com.gargoylesoftware.htmlunit.javascript.regexp, - com.gargoylesoftware.htmlunit.protocol.about, - com.gargoylesoftware.htmlunit.protocol.data, - com.gargoylesoftware.htmlunit.protocol.javascript, - com.gargoylesoftware.htmlunit.util, - com.gargoylesoftware.htmlunit.xml, - com.google.gwt.activity.shared, +Export-Package: com.google.gwt.activity.shared, com.google.gwt.animation.client, com.google.gwt.animation.client.testing, com.google.gwt.aria.client, @@ -43,65 +21,9 @@ Export-Package: com.gargoylesoftware.htmlunit, com.google.gwt.core.client.impl, com.google.gwt.core.client.prefetch, com.google.gwt.core.client.testing, - com.google.gwt.core.ext, - com.google.gwt.core.ext.debug, - com.google.gwt.core.ext.linker, - com.google.gwt.core.ext.linker.impl, - com.google.gwt.core.ext.soyc, - com.google.gwt.core.ext.soyc.impl, - com.google.gwt.core.ext.typeinfo, - com.google.gwt.core.linker, com.google.gwt.core.server, com.google.gwt.core.shared, com.google.gwt.debug.client, - com.google.gwt.dev, - com.google.gwt.dev.asm, - com.google.gwt.dev.asm.commons, - com.google.gwt.dev.asm.signature, - com.google.gwt.dev.asm.tree, - com.google.gwt.dev.asm.util, - com.google.gwt.dev.cfg, - com.google.gwt.dev.generator, - com.google.gwt.dev.generator.ast, - com.google.gwt.dev.javac, - com.google.gwt.dev.javac.asm, - com.google.gwt.dev.javac.testing, - com.google.gwt.dev.javac.testing.impl, - com.google.gwt.dev.javac.typemodel, - com.google.gwt.dev.jdt, - com.google.gwt.dev.jjs, - com.google.gwt.dev.jjs.ast, - com.google.gwt.dev.jjs.ast.js, - com.google.gwt.dev.jjs.impl, - com.google.gwt.dev.jjs.impl.gflow, - com.google.gwt.dev.jjs.impl.gflow.cfg, - com.google.gwt.dev.jjs.impl.gflow.constants, - com.google.gwt.dev.jjs.impl.gflow.copy, - com.google.gwt.dev.jjs.impl.gflow.liveness, - com.google.gwt.dev.jjs.impl.gflow.unreachable, - com.google.gwt.dev.js, - com.google.gwt.dev.js.ast, - com.google.gwt.dev.js.rhino, - com.google.gwt.dev.json, - com.google.gwt.dev.protobuf, - com.google.gwt.dev.resource, - com.google.gwt.dev.resource.impl, - com.google.gwt.dev.shell, - com.google.gwt.dev.shell.ie, - com.google.gwt.dev.shell.jetty, - com.google.gwt.dev.shell.log, - com.google.gwt.dev.shell.remoteui, - com.google.gwt.dev.shell.rewrite, - com.google.gwt.dev.ui, - com.google.gwt.dev.util, - com.google.gwt.dev.util.arg, - com.google.gwt.dev.util.collect, - com.google.gwt.dev.util.editdistance, - com.google.gwt.dev.util.log, - com.google.gwt.dev.util.log.dashboard, - com.google.gwt.dev.util.log.speedtracer, - com.google.gwt.dev.util.msg, - com.google.gwt.dev.util.xml, com.google.gwt.dom.builder.client, com.google.gwt.dom.builder.shared, com.google.gwt.dom.client, @@ -146,7 +68,6 @@ Export-Package: com.gargoylesoftware.htmlunit, com.google.gwt.junit.remote, com.google.gwt.junit.server, com.google.gwt.junit.tools, - com.google.gwt.lang, com.google.gwt.layout.client, com.google.gwt.logging.client, com.google.gwt.logging.impl, @@ -176,32 +97,10 @@ Export-Package: com.gargoylesoftware.htmlunit, com.google.gwt.safehtml.client, com.google.gwt.safehtml.rebind, com.google.gwt.safehtml.shared, - com.google.gwt.soyc, - com.google.gwt.soyc.io, com.google.gwt.storage.client, com.google.gwt.text.client, com.google.gwt.text.shared, com.google.gwt.text.shared.testing, - com.google.gwt.thirdparty.debugging.sourcemap, - com.google.gwt.thirdparty.debugging.sourcemap.proto, - com.google.gwt.thirdparty.guava.common.annotations, - com.google.gwt.thirdparty.guava.common.base, - com.google.gwt.thirdparty.guava.common.base.internal, - com.google.gwt.thirdparty.guava.common.cache, - com.google.gwt.thirdparty.guava.common.collect, - com.google.gwt.thirdparty.guava.common.eventbus, - com.google.gwt.thirdparty.guava.common.io, - com.google.gwt.thirdparty.guava.common.net, - com.google.gwt.thirdparty.guava.common.primitives, - com.google.gwt.thirdparty.guava.common.util.concurrent, - com.google.gwt.thirdparty.javascript.jscomp, - com.google.gwt.thirdparty.javascript.jscomp.ant, - com.google.gwt.thirdparty.javascript.jscomp.deps, - com.google.gwt.thirdparty.javascript.jscomp.graph, - com.google.gwt.thirdparty.javascript.jscomp.parsing, - com.google.gwt.thirdparty.javascript.jscomp.regex, - com.google.gwt.thirdparty.javascript.rhino, - com.google.gwt.thirdparty.javascript.rhino.jstype, com.google.gwt.thirdparty.streamhtmlparser, com.google.gwt.thirdparty.streamhtmlparser.impl, com.google.gwt.thirdparty.streamhtmlparser.util, @@ -242,10 +141,7 @@ Export-Package: com.gargoylesoftware.htmlunit, com.google.gwt.user.tools.util, com.google.gwt.useragent.client, com.google.gwt.useragent.rebind, - com.google.gwt.util, com.google.gwt.util.regexfilter, - com.google.gwt.util.tools, - com.google.gwt.util.tools.shared, com.google.gwt.validation.client, com.google.gwt.validation.client.constraints, com.google.gwt.validation.client.impl, @@ -284,266 +180,8 @@ Export-Package: com.gargoylesoftware.htmlunit, com.google.web.bindery.requestfactory.vm, com.google.web.bindery.requestfactory.vm.impl, com.google.web.bindery.requestfactory.vm.testing, - com.ibm.icu.impl, - com.ibm.icu.impl.data, - com.ibm.icu.impl.duration, - com.ibm.icu.impl.duration.impl, - com.ibm.icu.impl.locale, - com.ibm.icu.lang, - com.ibm.icu.math, - com.ibm.icu.text, - com.ibm.icu.util, - com.steadystate.css.dom, - com.steadystate.css.parser, - com.steadystate.css.parser.selectors, - com.steadystate.css.sac, - com.steadystate.css.userdata, - java_cup, - java_cup.anttask, - java_cup.runtime, - net.sourceforge.htmlunit.corejs.classfile, - net.sourceforge.htmlunit.corejs.javascript, - net.sourceforge.htmlunit.corejs.javascript.annotations, - net.sourceforge.htmlunit.corejs.javascript.ast, - net.sourceforge.htmlunit.corejs.javascript.debug, - net.sourceforge.htmlunit.corejs.javascript.jdk13, - net.sourceforge.htmlunit.corejs.javascript.jdk15, - net.sourceforge.htmlunit.corejs.javascript.json, - net.sourceforge.htmlunit.corejs.javascript.optimizer, - net.sourceforge.htmlunit.corejs.javascript.regexp, - net.sourceforge.htmlunit.corejs.javascript.serialize, - net.sourceforge.htmlunit.corejs.javascript.tools, - net.sourceforge.htmlunit.corejs.javascript.tools.debugger, - net.sourceforge.htmlunit.corejs.javascript.tools.debugger.treetable, - net.sourceforge.htmlunit.corejs.javascript.tools.shell, - net.sourceforge.htmlunit.corejs.javascript.xml, - org.apache.bcel, - org.apache.bcel.classfile, - org.apache.bcel.generic, - org.apache.bcel.util, - org.apache.bcel.verifier, - org.apache.bcel.verifier.exc, - org.apache.bcel.verifier.statics, - org.apache.bcel.verifier.structurals, - org.apache.commons.codec, - org.apache.commons.codec.binary, - org.apache.commons.codec.digest, - org.apache.commons.codec.language, - org.apache.commons.codec.net, - org.apache.commons.collections, - org.apache.commons.collections.bag, - org.apache.commons.collections.bidimap, - org.apache.commons.collections.buffer, - org.apache.commons.collections.collection, - org.apache.commons.collections.comparators, - org.apache.commons.collections.functors, - org.apache.commons.collections.iterators, - org.apache.commons.collections.keyvalue, - org.apache.commons.collections.list, - org.apache.commons.collections.map, - org.apache.commons.collections.set, - org.apache.commons.el, - org.apache.commons.el.parser, - org.apache.commons.io, - org.apache.commons.io.comparator, - org.apache.commons.io.filefilter, - org.apache.commons.io.input, - org.apache.commons.io.output, - org.apache.commons.logging, - org.apache.commons.logging.impl, - org.apache.html.dom, - org.apache.http, - org.apache.http.annotation, - org.apache.http.auth, - org.apache.http.auth.params, - org.apache.http.client, - org.apache.http.client.entity, - org.apache.http.client.methods, - org.apache.http.client.params, - org.apache.http.client.protocol, - org.apache.http.client.utils, - org.apache.http.conn, - org.apache.http.conn.params, - org.apache.http.conn.routing, - org.apache.http.conn.scheme, - org.apache.http.conn.ssl, - org.apache.http.conn.util, - org.apache.http.cookie, - org.apache.http.cookie.params, - org.apache.http.entity, - org.apache.http.entity.mime, - org.apache.http.entity.mime.content, - org.apache.http.impl, - org.apache.http.impl.auth, - org.apache.http.impl.client, - org.apache.http.impl.conn, - org.apache.http.impl.conn.tsccm, - org.apache.http.impl.cookie, - org.apache.http.impl.entity, - org.apache.http.impl.io, - org.apache.http.io, - org.apache.http.message, - org.apache.http.params, - org.apache.http.protocol, - org.apache.http.util, - org.apache.james.mime4j, - org.apache.james.mime4j.codec, - org.apache.james.mime4j.descriptor, - org.apache.james.mime4j.field, - org.apache.james.mime4j.field.address, - org.apache.james.mime4j.field.address.parser, - org.apache.james.mime4j.field.contentdisposition.parser, - org.apache.james.mime4j.field.contenttype.parser, - org.apache.james.mime4j.field.datetime, - org.apache.james.mime4j.field.datetime.parser, - org.apache.james.mime4j.field.language.parser, - org.apache.james.mime4j.field.mimeversion.parser, - org.apache.james.mime4j.field.structured.parser, - org.apache.james.mime4j.io, - org.apache.james.mime4j.message, - org.apache.james.mime4j.parser, - org.apache.james.mime4j.storage, - org.apache.james.mime4j.util, - org.apache.jasper, - org.apache.jasper.compiler, - org.apache.jasper.compiler.tagplugin, - org.apache.jasper.runtime, - org.apache.jasper.security, - org.apache.jasper.servlet, - org.apache.jasper.tagplugins.jstl, - org.apache.jasper.util, - org.apache.jasper.xmlparser, - org.apache.regexp, - org.apache.tapestry.util.text, - org.apache.tools.ant, - org.apache.tools.ant.filters, - org.apache.tools.ant.filters.util, - org.apache.tools.ant.helper, - org.apache.tools.ant.input, - org.apache.tools.ant.listener, - org.apache.tools.ant.loader, - org.apache.tools.ant.taskdefs, - org.apache.tools.ant.taskdefs.compilers, - org.apache.tools.ant.taskdefs.condition, - org.apache.tools.ant.taskdefs.cvslib, - org.apache.tools.ant.taskdefs.email, - org.apache.tools.ant.taskdefs.rmic, - org.apache.tools.ant.types, - org.apache.tools.ant.types.mappers, - org.apache.tools.ant.types.resolver, - org.apache.tools.ant.types.selectors, - org.apache.tools.ant.types.selectors.modifiedselector, - org.apache.tools.ant.util, - org.apache.tools.ant.util.facade, - org.apache.tools.ant.util.regexp, - org.apache.tools.bzip2, - org.apache.tools.mail, - org.apache.tools.tar, - org.apache.tools.zip, - org.apache.wml, - org.apache.wml.dom, - org.apache.xalan, - org.apache.xalan.client, - org.apache.xalan.extensions, - org.apache.xalan.lib, - org.apache.xalan.lib.sql, - org.apache.xalan.processor, - org.apache.xalan.res, - org.apache.xalan.serialize, - org.apache.xalan.templates, - org.apache.xalan.trace, - org.apache.xalan.transformer, - org.apache.xalan.xslt, - org.apache.xalan.xsltc, - org.apache.xalan.xsltc.cmdline, - org.apache.xalan.xsltc.cmdline.getopt, - org.apache.xalan.xsltc.compiler, - org.apache.xalan.xsltc.compiler.util, - org.apache.xalan.xsltc.dom, - org.apache.xalan.xsltc.runtime, - org.apache.xalan.xsltc.runtime.output, - org.apache.xalan.xsltc.trax, - org.apache.xalan.xsltc.util, - org.apache.xerces.dom, - org.apache.xerces.dom.events, - org.apache.xerces.dom3.as, - org.apache.xerces.impl, - org.apache.xerces.impl.dtd, - org.apache.xerces.impl.dtd.models, - org.apache.xerces.impl.dv, - org.apache.xerces.impl.dv.dtd, - org.apache.xerces.impl.dv.util, - org.apache.xerces.impl.dv.xs, - org.apache.xerces.impl.io, - org.apache.xerces.impl.msg, - org.apache.xerces.impl.validation, - org.apache.xerces.impl.xpath, - org.apache.xerces.impl.xpath.regex, - org.apache.xerces.impl.xs, - org.apache.xerces.impl.xs.identity, - org.apache.xerces.impl.xs.models, - org.apache.xerces.impl.xs.opti, - org.apache.xerces.impl.xs.traversers, - org.apache.xerces.impl.xs.util, - org.apache.xerces.jaxp, - org.apache.xerces.jaxp.datatype, - org.apache.xerces.jaxp.validation, - org.apache.xerces.parsers, - org.apache.xerces.util, - org.apache.xerces.xinclude, - org.apache.xerces.xni, - org.apache.xerces.xni.grammars, - org.apache.xerces.xni.parser, - org.apache.xerces.xpointer, - org.apache.xerces.xs, - org.apache.xerces.xs.datatypes, - org.apache.xml.dtm, - org.apache.xml.dtm.ref, - org.apache.xml.dtm.ref.dom2dtm, - org.apache.xml.dtm.ref.sax2dtm, - org.apache.xml.res, - org.apache.xml.serialize, - org.apache.xml.serializer, - org.apache.xml.serializer.dom3, - org.apache.xml.serializer.utils, - org.apache.xml.utils, - org.apache.xml.utils.res, - org.apache.xmlcommons, - org.apache.xpath, - org.apache.xpath.axes, - org.apache.xpath.compiler, - org.apache.xpath.domapi, - org.apache.xpath.functions, - org.apache.xpath.jaxp, - org.apache.xpath.objects, - org.apache.xpath.operations, - org.apache.xpath.patterns, - org.apache.xpath.res, - org.cyberneko.html, - org.cyberneko.html.filters, - org.cyberneko.html.parsers, - org.cyberneko.html.xercesbridge, - org.eclipse.jdt.core, - org.eclipse.jdt.core.compiler, - org.eclipse.jdt.core.compiler.batch, - org.eclipse.jdt.internal.antadapter, - org.eclipse.jdt.internal.compiler, - org.eclipse.jdt.internal.compiler.ast, - org.eclipse.jdt.internal.compiler.batch, - org.eclipse.jdt.internal.compiler.classfmt, - org.eclipse.jdt.internal.compiler.codegen, - org.eclipse.jdt.internal.compiler.env, - org.eclipse.jdt.internal.compiler.flow, - org.eclipse.jdt.internal.compiler.impl, - org.eclipse.jdt.internal.compiler.lookup, - org.eclipse.jdt.internal.compiler.parser, - org.eclipse.jdt.internal.compiler.parser.diagnose, - org.eclipse.jdt.internal.compiler.problem, - org.eclipse.jdt.internal.compiler.util, - org.eclipse.jdt.internal.core.util, org.hibernate.validator, - org.hibernate.validator.engine, - org.jdesktop.swingworker + org.hibernate.validator.engine Require-Bundle: javax.validation;bundle-version="1.0.0" Import-Package: javax.servlet;version="2.6.0", javax.servlet.http;version="2.6.0" diff --git a/java/com.google.gwt.osgi/build.properties b/java/com.google.gwt.osgi/build.properties index 6c4eae4a192..551d4a7a498 100755 --- a/java/com.google.gwt.osgi/build.properties +++ b/java/com.google.gwt.osgi/build.properties @@ -1,4 +1,3 @@ bin.includes = META-INF/,\ .,\ - lib/gwt-dev.jar,\ lib/gwt-user.jar diff --git a/java/com.google.gwt.osgi/lib/gwt-dev.jar b/java/com.google.gwt.osgi/lib/gwt-dev.jar deleted file mode 100755 index a160825141c..00000000000 Binary files a/java/com.google.gwt.osgi/lib/gwt-dev.jar and /dev/null differ diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-servlet.jar b/java/com.google.gwt.osgi/lib/gwt-servlet.jar similarity index 78% rename from java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-servlet.jar rename to java/com.google.gwt.osgi/lib/gwt-servlet.jar index 2e85a1a4b52..cce1036c18b 100755 Binary files a/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-servlet.jar and b/java/com.google.gwt.osgi/lib/gwt-servlet.jar differ diff --git a/java/com.google.gwt.osgi/lib/gwt-user.jar b/java/com.google.gwt.osgi/lib/gwt-user.jar index a74abb888b4..46259293f5a 100755 Binary files a/java/com.google.gwt.osgi/lib/gwt-user.jar and b/java/com.google.gwt.osgi/lib/gwt-user.jar differ diff --git a/java/com.google.gwt.osgi/pom.xml b/java/com.google.gwt.osgi/pom.xml index 7085429e5ca..66817ec5ea3 100644 --- a/java/com.google.gwt.osgi/pom.xml +++ b/java/com.google.gwt.osgi/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.google.gwt.osgi - 2.6.0-SNAPSHOT + 2.6.1-SNAPSHOT eclipse-plugin diff --git a/java/com.sap.sailing.barbados.resultimport.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.barbados.resultimport.test/META-INF/MANIFEST.MF index 26e5ba185a6..7ed218598ed 100644 --- a/java/com.sap.sailing.barbados.resultimport.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.barbados.resultimport.test/META-INF/MANIFEST.MF @@ -11,4 +11,5 @@ Require-Bundle: org.junit4;bundle-version="4.8.2", com.sap.sailing.barbados.resultimport, com.sap.sailing.domain.common, com.sap.sailing.domain, - com.sap.sailing.resultimport + com.sap.sailing.resultimport, + com.sap.sse.common diff --git a/java/com.sap.sailing.barbados.resultimport.test/src/com/sap/sailing/barbados/resultimport/test/BarbadosResultImportTest.java b/java/com.sap.sailing.barbados.resultimport.test/src/com/sap/sailing/barbados/resultimport/test/BarbadosResultImportTest.java index 3d3997e4b2a..67eb041fbdf 100755 --- a/java/com.sap.sailing.barbados.resultimport.test/src/com/sap/sailing/barbados/resultimport/test/BarbadosResultImportTest.java +++ b/java/com.sap.sailing.barbados.resultimport.test/src/com/sap/sailing/barbados/resultimport/test/BarbadosResultImportTest.java @@ -28,11 +28,11 @@ import com.sap.sailing.domain.common.RegattaScoreCorrections; import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.resultimport.RegattaResults; import com.sap.sailing.resultimport.ResultDocumentDescriptor; import com.sap.sailing.resultimport.ResultDocumentProvider; import com.sap.sailing.resultimport.impl.ResultDocumentDescriptorImpl; +import com.sap.sse.common.Util; public class BarbadosResultImportTest { private static final MillisecondsTimePoint NOW = MillisecondsTimePoint.now(); @@ -74,16 +74,16 @@ public class BarbadosResultImportTest { @Test public void testHasResultsThroughScoreCorrectionProvider() throws Exception { - Map>> hasResultsFor = scp.getHasResultsForBoatClassFromDateByEventName(); + Map>> hasResultsFor = scp.getHasResultsForBoatClassFromDateByEventName(); assertNotNull(hasResultsFor); assertEquals(1, hasResultsFor.size()); - Entry>> entry = hasResultsFor.entrySet().iterator().next(); + Entry>> entry = hasResultsFor.entrySet().iterator().next(); assertEquals("505", entry.getValue().iterator().next().getA()); } @Test public void testResultsThroughScoreCorrectionProvider() throws Exception { - Map>> hasResultsFor = scp.getHasResultsForBoatClassFromDateByEventName(); + Map>> hasResultsFor = scp.getHasResultsForBoatClassFromDateByEventName(); String eventName = hasResultsFor.entrySet().iterator().next().getKey(); String boatClassName = hasResultsFor.entrySet().iterator().next().getValue().iterator().next().getA(); TimePoint timePoint = hasResultsFor.entrySet().iterator().next().getValue().iterator().next().getB(); @@ -125,7 +125,7 @@ public class BarbadosResultImportTest { return result; } }); - Map>> hasResultsFor = scp2.getHasResultsForBoatClassFromDateByEventName(); + Map>> hasResultsFor = scp2.getHasResultsForBoatClassFromDateByEventName(); String eventName = hasResultsFor.entrySet().iterator().next().getKey(); String boatClassName = hasResultsFor.entrySet().iterator().next().getValue().iterator().next().getA(); TimePoint timePoint = hasResultsFor.entrySet().iterator().next().getValue().iterator().next().getB(); diff --git a/java/com.sap.sailing.barbados.resultimport/META-INF/MANIFEST.MF b/java/com.sap.sailing.barbados.resultimport/META-INF/MANIFEST.MF index 656ccbc7baa..391054f4a26 100644 --- a/java/com.sap.sailing.barbados.resultimport/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.barbados.resultimport/META-INF/MANIFEST.MF @@ -15,7 +15,8 @@ Require-Bundle: com.sap.sailing.server, com.sap.sailing.domain, jcl.over.slf4j;bundle-version="1.6.4", com.sap.sailing.resultimport, - slf4j.api;bundle-version="1.6.4" + slf4j.api;bundle-version="1.6.4", + com.sap.sse.common Bundle-ClassPath: lib/com.springsource.org.apache.commons.beanutils-1.8.0.jar, lib/com.springsource.org.apache.commons.collections-3.2.1.jar, lib/com.springsource.org.apache.commons.digester-1.8.0.jar, diff --git a/java/com.sap.sailing.barbados.resultimport/src/com/sap/sailing/barbados/resultimport/impl/ScoreCorrectionProviderImpl.java b/java/com.sap.sailing.barbados.resultimport/src/com/sap/sailing/barbados/resultimport/impl/ScoreCorrectionProviderImpl.java index c2ec106043b..e662784cc3c 100644 --- a/java/com.sap.sailing.barbados.resultimport/src/com/sap/sailing/barbados/resultimport/impl/ScoreCorrectionProviderImpl.java +++ b/java/com.sap.sailing.barbados.resultimport/src/com/sap/sailing/barbados/resultimport/impl/ScoreCorrectionProviderImpl.java @@ -9,12 +9,12 @@ import java.util.logging.Logger; import com.sap.sailing.domain.common.RegattaScoreCorrections; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.resultimport.RegattaResults; import com.sap.sailing.resultimport.ResultDocumentDescriptor; import com.sap.sailing.resultimport.ResultDocumentProvider; import com.sap.sailing.resultimport.impl.AbstractDocumentBasedScoreCorrectionProvider; import com.sap.sailing.resultimport.impl.RegattaScoreCorrectionsImpl; +import com.sap.sse.common.Util; public class ScoreCorrectionProviderImpl extends AbstractDocumentBasedScoreCorrectionProvider { private static final Logger logger = Logger.getLogger(ScoreCorrectionProviderImpl.class.getName()); @@ -34,19 +34,19 @@ public class ScoreCorrectionProviderImpl extends AbstractDocumentBasedScoreCorre } @Override - public Map>> getHasResultsForBoatClassFromDateByEventName() throws Exception { - Map>> result = new HashMap>>(); + public Map>> getHasResultsForBoatClassFromDateByEventName() throws Exception { + Map>> result = new HashMap>>(); for (ResultDocumentDescriptor resultDocDescr : getResultDocumentProvider().getResultDocumentDescriptors()) { try { RegattaResults regattaResults = new BarbadosResultSpreadsheet(resultDocDescr.getInputStream()).getRegattaResults(); TimePoint lastModified = resultDocDescr.getLastModified(); final String boatClassName = getBoatClassName(regattaResults); - Set> set = result.get(boatClassName); + Set> set = result.get(boatClassName); if (set == null) { set = new HashSet<>(); result.put(/* use document name as "event" name */ resultDocDescr.getDocumentName(), set); } - set.add(new Pair(boatClassName, lastModified)); + set.add(new Util.Pair(boatClassName, lastModified)); } catch (Exception e) { logger.log(Level.WARNING, "Couldn't parse Barbados result document "+ resultDocDescr.getDocumentName(), e); } diff --git a/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/QueryDefinition.java b/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/QueryDefinitionDeprecated.java similarity index 89% rename from java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/QueryDefinition.java rename to java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/QueryDefinitionDeprecated.java index 5f6548e13c9..f04ba8bfa8c 100644 --- a/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/QueryDefinition.java +++ b/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/QueryDefinitionDeprecated.java @@ -7,7 +7,7 @@ import java.util.Map; import com.sap.sse.datamining.shared.components.AggregatorType; import com.sap.sse.datamining.shared.components.GrouperType; -public interface QueryDefinition extends Serializable { +public interface QueryDefinitionDeprecated extends Serializable { public String getLocaleInfoName(); diff --git a/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/impl/SimpleQueryDefinition.java b/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/impl/QueryDefinitionDeprecatedImpl.java similarity index 86% rename from java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/impl/SimpleQueryDefinition.java rename to java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/impl/QueryDefinitionDeprecatedImpl.java index 5547f158e17..03a8c2c60f7 100644 --- a/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/impl/SimpleQueryDefinition.java +++ b/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/impl/QueryDefinitionDeprecatedImpl.java @@ -8,12 +8,12 @@ import java.util.Map; import com.sap.sailing.datamining.shared.DataTypes; import com.sap.sailing.datamining.shared.DimensionIdentifier; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.datamining.shared.StatisticType; import com.sap.sse.datamining.shared.components.AggregatorType; import com.sap.sse.datamining.shared.components.GrouperType; -public class SimpleQueryDefinition implements QueryDefinition { +public class QueryDefinitionDeprecatedImpl implements QueryDefinitionDeprecated { private static final long serialVersionUID = 8408302866342877839L; private String localeInfoName; @@ -30,9 +30,9 @@ public class SimpleQueryDefinition implements QueryDefinition { * Constructor for the GWT-Serialization. Don't use this! */ @Deprecated - SimpleQueryDefinition() { } + QueryDefinitionDeprecatedImpl() { } - public SimpleQueryDefinition(String localeInfoName, GrouperType grouperType, StatisticType statisticType, AggregatorType aggregatorType, DataTypes dataType) { + public QueryDefinitionDeprecatedImpl(String localeInfoName, GrouperType grouperType, StatisticType statisticType, AggregatorType aggregatorType, DataTypes dataType) { this.localeInfoName = localeInfoName; this.grouperType = grouperType; this.statisticType = statisticType; diff --git a/java/com.sap.sailing.datamining.test/.classpath b/java/com.sap.sailing.datamining.test/.classpath index 5c0149e1673..ba4e0d198fd 100644 --- a/java/com.sap.sailing.datamining.test/.classpath +++ b/java/com.sap.sailing.datamining.test/.classpath @@ -4,5 +4,6 @@ + diff --git a/java/com.sap.sailing.datamining.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.datamining.test/META-INF/MANIFEST.MF index b59ee210076..493169806e6 100644 --- a/java/com.sap.sailing.datamining.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.datamining.test/META-INF/MANIFEST.MF @@ -10,4 +10,5 @@ Import-Package: junit.framework;version="4.8.2", org.junit;version="4.8.2", org.junit.runner;version="4.8.2" Require-Bundle: org.mockito.mockito-core;bundle-version="1.9.5", - org.hamcrest;bundle-version="1.1.0" + org.hamcrest;bundle-version="1.1.0", + com.sap.sse.common diff --git a/java/com.sap.sailing.datamining.test/build.properties b/java/com.sap.sailing.datamining.test/build.properties index 34d2e4d2dad..3520af99683 100644 --- a/java/com.sap.sailing.datamining.test/build.properties +++ b/java/com.sap.sailing.datamining.test/build.properties @@ -1,4 +1,5 @@ -source.. = src/ +source.. = src/,\ + resources/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_2.mtb b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_2.mtb new file mode 100644 index 00000000000..38321e1dff2 Binary files /dev/null and b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_2.mtb differ diff --git a/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_2.txt b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_2.txt new file mode 100644 index 00000000000..f797b4bd1a9 --- /dev/null +++ b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_2.txt @@ -0,0 +1,1419 @@ +stored-uri:datafiles/event_20110609_KielerWoch-505_Race_2.mtb +StartReplay:1 + +// Input from params_static: + +// Input from event_parameters: + +CompMarkerUUID:1 +CodanIsSponsor:0 +EnablePrint:1 +DefaultTailLength:60 +APzoomChangeTime:2500 +disableAutoProduce:0 +autoProduce:0 +APstartLineShowOfLeg:0.25 +APsideOfStartLine:0.30 +APstartSwitch:0.8 +APwholeLegBehindMarks:0.15 +APmarkSwitch:0.13 +APmarkZoomBehindMarks:0.10 +APmarkZoomInFrontOfMarks:0.20 +PopupMarkerUUID:2 +ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf +WeatherLat1:56 +WeatherLat2:54 +Marker1UUID:1 +Marker1Type:RGBMarkerFactory +Marker1Directional:1 +Marker1Image:markers/boat_FF0000_big.png +Marker1MetersPerPixel:0.0617 +Marker1MinScale:0.396 +Startlocation:10.197659,54.456129 +MaxDiffTime:600 +MaxMetersPerSecond:20 +PrecisionMeters:20 +ShortAvgTime:180 +LongAvgTime:900 +TimeZone:2 +CompMarkerTimeout:60 +FramesPerSecond:8 +replayspeedup:5 +MatchRaceClock:0 +MatchRaceClockNo:3 +MatchRaceClockSpacing:300 +Minimap:1 +MinimapUseLayers:1 +Minimapgeorect:9.840775,54.516538,10.3022,54.309554 +MinimapMarkerUUID:2 +Marker4UUID:2 +Marker4Type:RGBmarkerFactory +Marker4Directional:1 +Marker4Image:markers/boat_FF0000_small.png +MetersPerPixel:25 +MetersPerPixelMax:200 +MetersPerPixelMin:0.2 +isMatchRace:0 +weatherEnabled:0 +showRaceClock:1 +BackGroundImage:graphics/background.jpg +DisableRotation:0 +ControlPointMarkerRadius:13 +disableselection:0 +disableleaderline:0 +WeatherLon1:9 +WeatherLon2:12 +DisableChangeRace:0 +PopUpRow1Name: +PopUpRow1Type:Name +PopUpRow2Name:Knots +PopUpRow2Type:BoatSpeed +PopUpRow3Name:Ranking +PopUpRow3Type:RowNumber +PopUpRow4Name:Sail +PopUpRow4Type:Ini +PopUpRow5Name:After +PopUpRow5Type:BoatAfter +ResultColumn1Name:# +ResultColumn1Size:20 +ResultColumn1Type:RowNumber +ResultColumn2Size:20 +ResultColumn2Type:ColorMarker +ResultColumn3Name:sail +ResultColumn3Size:60 +ResultColumn3Type:Ini +ResultColumn4Name:name +ResultColumn4Size:110 +ResultColumn4Type:Name +ResultColumn5Name:after +ResultColumn5Size:45 +ResultColumn5Type:BoatAfter +ResultColumn6Name:kts +ResultColumn6Size:40 +ResultColumn6Type:BoatSpeed +DisableLeaderboard:0 +DisableAnalysis:0 +TableFontSize:11 +//live-uri:tcp://germanmaster.traclive.dk:4400 +//stored-uri:tcp://germanmaster.traclive.dk:4401 +compInitialsFontSize:11 +AutoRotate:0 +MassStart:0 +SegmentFile:segments.php +StartType:Mass +ReCalcDirAndSpeed:0 +RaceHandicapSystem:NONE +TeamCompetitionType:NONE +LiveDelaySecs: 180 + +//-------- AUTO GENERATED PARAMETERS START ---------------- + +EventID:f34b232a-934e-11e0-b3bb-406186cbf87c +EventName:Kieler Woche +EventDB:event_20110609_KielerWoch +EventStartTime:2011-06-15 09:00:00 +EventEndTime:2011-06-26 23:59:00 + +RaceID:357c700a-9d9a-11e0-85be-406186cbf87c +RaceName:505 Race 2 +RaceStartTime:2011-06-23 13:25:00 +RaceEndTime: +RaceTrackingStartTime:2011-06-23 13:14:31 +RaceTrackingEndTime:2011-06-23 14:58:23 +RaceDefaultRouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c +RaceHandicapSystem:NONE + +Image1UUID:0ba089bc-996f-11e0-85be-406186cbf87c +Image1Type:Logo +Image1Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/URU.gif +Image1Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/URU.gif + +Image2UUID:1d548b00-996e-11e0-85be-406186cbf87c +Image2Type:Logo +Image2Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif +Image2Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif + +Image3UUID:1ece41fa-9c49-11e0-85be-406186cbf87c +Image3Type:Logo +Image3Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif +Image3Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif + +Image4UUID:22b1a988-98e3-11e0-85be-406186cbf87c +Image4Type:Logo +Image4Description:Veolia +Image4Image: + +Image5UUID:22ba0304-98d8-11e0-85be-406186cbf87c +Image5Type:Logo +Image5Description:Kieler Woche +Image5Image: + +Image6UUID:2767b7e8-996e-11e0-85be-406186cbf87c +Image6Type:Logo +Image6Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BEL.gif +Image6Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BEL.gif + +Image7UUID:2cda6f4a-996e-11e0-85be-406186cbf87c +Image7Type:Logo +Image7Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BRA.gif +Image7Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BRA.gif + +Image8UUID:3313b588-98d8-11e0-85be-406186cbf87c +Image8Type:Logo +Image8Description:Audi +Image8Image: + +Image9UUID:33a26700-98e3-11e0-85be-406186cbf87c +Image9Type:Logo +Image9Description:HSH NORDBANK +Image9Image: + +Image10UUID:373009a0-996e-11e0-85be-406186cbf87c +Image10Type:Logo +Image10Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CHI.gif +Image10Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CHI.gif + +Image11UUID:4cc5360a-996e-11e0-85be-406186cbf87c +Image11Type:Logo +Image11Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/EST.gif +Image11Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/EST.gif + +Image12UUID:514008f8-98e3-11e0-85be-406186cbf87c +Image12Type:Logo +Image12Description:ISAF SAILING WORLDCUP +Image12Image: + +Image13UUID:5222c1c6-996e-11e0-85be-406186cbf87c +Image13Type:Logo +Image13Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FRA.gif +Image13Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FRA.gif + +Image14UUID:574e0d40-98d8-11e0-85be-406186cbf87c +Image14Type:Logo +Image14Description:SAP +Image14Image: + +Image15UUID:617e8236-996e-11e0-85be-406186cbf87c +Image15Type:Logo +Image15Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GBR.gif +Image15Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GBR.gif + +Image16UUID:6d332670-9934-11e0-85be-406186cbf87c +Image16Type:Logo +Image16Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUS.gif +Image16Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUS.gif + +Image17UUID:7252c142-9934-11e0-85be-406186cbf87c +Image17Type:Logo +Image17Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CAN.gif +Image17Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CAN.gif + +Image18UUID:77555f2e-9934-11e0-85be-406186cbf87c +Image18Type:Logo +Image18Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CRO.gif +Image18Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CRO.gif + +Image19UUID:7c87105a-9934-11e0-85be-406186cbf87c +Image19Type:Logo +Image19Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif +Image19Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif + +Image20UUID:7deb67ce-9930-11e0-85be-406186cbf87c +Image20Type:Logo +Image20Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RUS.gif +Image20Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RUS.gif + +Image21UUID:81fea498-9930-11e0-85be-406186cbf87c +Image21Type:Logo +Image21Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GER.gif +Image21Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GER.gif + +Image22UUID:93a58cc0-9930-11e0-85be-406186cbf87c +Image22Type:Logo +Image22Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/USA.gif +Image22Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/USA.gif + +Image23UUID:bc6068c4-9d77-11e0-85be-406186cbf87c +Image23Type:Logo +Image23Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UAE.gif +Image23Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UAE.gif + +Image24UUID:be8c642a-996e-11e0-85be-406186cbf87c +Image24Type:Logo +Image24Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GRE.gif +Image24Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GRE.gif + +Image25UUID:c2fdb14c-9934-11e0-85be-406186cbf87c +Image25Type:Logo +Image25Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/HUN.gif +Image25Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/HUN.gif + +Image26UUID:c3a2eaa6-996e-11e0-85be-406186cbf87c +Image26Type:Logo +Image26Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/IRL.gif +Image26Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/IRL.gif + +Image27UUID:c85b16fc-9934-11e0-85be-406186cbf87c +Image27Type:Logo +Image27Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISV.gif +Image27Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISV.gif + +Image28UUID:c8eaf36e-996e-11e0-85be-406186cbf87c +Image28Type:Logo +Image28Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MEX.gif +Image28Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MEX.gif + +Image29UUID:cd74b15c-9934-11e0-85be-406186cbf87c +Image29Type:Logo +Image29Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ITA.gif +Image29Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ITA.gif + +Image30UUID:cdf05d40-996e-11e0-85be-406186cbf87c +Image30Type:Logo +Image30Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NED.gif +Image30Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NED.gif + +Image31UUID:d3764b44-996e-11e0-85be-406186cbf87c +Image31Type:Logo +Image31Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POL.gif +Image31Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POL.gif + +Image32UUID:d77c220c-9934-11e0-85be-406186cbf87c +Image32Type:Logo +Image32Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/JPN.gif +Image32Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/JPN.gif + +Image33UUID:dcb92152-9934-11e0-85be-406186cbf87c +Image33Type:Logo +Image33Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NOR.gif +Image33Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NOR.gif + +Image34UUID:e69d47f2-9934-11e0-85be-406186cbf87c +Image34Type:Logo +Image34Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif +Image34Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif + +Image35UUID:ebb4c5da-9934-11e0-85be-406186cbf87c +Image35Type:Logo +Image35Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SWE.gif +Image35Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SWE.gif + +Image36UUID:f5cab6b0-9934-11e0-85be-406186cbf87c +Image36Type:Logo +Image36Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UKR.gif +Image36Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UKR.gif + +Class1UUID:da553a2a-9d14-11e0-85be-406186cbf87c +Class1Name:5o5 + +Team1UUID:6cf050ac-9934-11e0-85be-406186cbf87c +Team1Name:AUS +Team1Comp1UUID:de7d2ba8-9d14-11e0-85be-406186cbf87c + +Team2UUID:1d07e516-996e-11e0-85be-406186cbf87c +Team2Name:AUT + +Team3UUID:271ac230-996e-11e0-85be-406186cbf87c +Team3Name:BEL + +Team4UUID:2c8e2630-996e-11e0-85be-406186cbf87c +Team4Name:BRA + +Team5UUID:7210a622-9934-11e0-85be-406186cbf87c +Team5Name:CAN + +Team6UUID:36ebe888-996e-11e0-85be-406186cbf87c +Team6Name:CHI + +Team7UUID:77133b08-9934-11e0-85be-406186cbf87c +Team7Name:CRO + +Team8UUID:7c3a4770-9934-11e0-85be-406186cbf87c +Team8Name:DEN +Team8Comp1UUID:e7bc96f4-9d14-11e0-85be-406186cbf87c +Team8Comp2UUID:f0c9604c-9d14-11e0-85be-406186cbf87c +Team8Comp3UUID:ec336744-9d14-11e0-85be-406186cbf87c +Team8Comp4UUID:e3587876-9d14-11e0-85be-406186cbf87c + +Team9UUID:4c650fc8-996e-11e0-85be-406186cbf87c +Team9Name:EST + +Team10UUID:51d56ce6-996e-11e0-85be-406186cbf87c +Team10Name:FRA +Team10Comp1UUID:f969da74-9d14-11e0-85be-406186cbf87c +Team10Comp2UUID:f50675be-9d14-11e0-85be-406186cbf87c +Team10Comp3UUID:fda897ba-9d14-11e0-85be-406186cbf87c + +Team11UUID:613a12ea-996e-11e0-85be-406186cbf87c +Team11Name:GBR + +Team12UUID:81b951d6-9930-11e0-85be-406186cbf87c +Team12Name:GER +Team12Comp1UUID:70eb542e-9d15-11e0-85be-406186cbf87c +Team12Comp2UUID:40bd6314-9d15-11e0-85be-406186cbf87c +Team12Comp3UUID:0a917a14-9d15-11e0-85be-406186cbf87c +Team12Comp4UUID:9d4e23d4-9d15-11e0-85be-406186cbf87c +Team12Comp5UUID:498b40ec-9d15-11e0-85be-406186cbf87c +Team12Comp6UUID:7e051528-9d15-11e0-85be-406186cbf87c +Team12Comp7UUID:949b11b6-9d15-11e0-85be-406186cbf87c +Team12Comp8UUID:991ab354-9d15-11e0-85be-406186cbf87c +Team12Comp9UUID:826bdcb4-9d15-11e0-85be-406186cbf87c +Team12Comp10UUID:3c1fb294-9d15-11e0-85be-406186cbf87c +Team12Comp11UUID:4e00eaa0-9d15-11e0-85be-406186cbf87c +Team12Comp12UUID:a1f9ca46-9d15-11e0-85be-406186cbf87c +Team12Comp13UUID:1cd7dcc2-9d15-11e0-85be-406186cbf87c +Team12Comp14UUID:5b14f704-9d15-11e0-85be-406186cbf87c +Team12Comp15UUID:5f520bea-9d15-11e0-85be-406186cbf87c +Team12Comp16UUID:86d2fb5c-9d15-11e0-85be-406186cbf87c +Team12Comp17UUID:13e98db8-9d15-11e0-85be-406186cbf87c +Team12Comp18UUID:21584926-9d15-11e0-85be-406186cbf87c +Team12Comp19UUID:450c5a92-9d15-11e0-85be-406186cbf87c +Team12Comp20UUID:8b618332-9d15-11e0-85be-406186cbf87c +Team12Comp21UUID:75325a1e-9d15-11e0-85be-406186cbf87c +Team12Comp22UUID:37b2223c-9d15-11e0-85be-406186cbf87c +Team12Comp23UUID:062ea47e-9d15-11e0-85be-406186cbf87c +Team12Comp24UUID:2a8fbab0-9d15-11e0-85be-406186cbf87c +Team12Comp25UUID:56b22d94-9d15-11e0-85be-406186cbf87c +Team12Comp26UUID:63855546-9d15-11e0-85be-406186cbf87c +Team12Comp27UUID:25ea8cce-9d15-11e0-85be-406186cbf87c +Team12Comp28UUID:68315036-9d15-11e0-85be-406186cbf87c +Team12Comp29UUID:aa852246-9d15-11e0-85be-406186cbf87c +Team12Comp30UUID:0f4448e8-9d15-11e0-85be-406186cbf87c +Team12Comp31UUID:1859e280-9d15-11e0-85be-406186cbf87c + +Team13UUID:be35f1ee-996e-11e0-85be-406186cbf87c +Team13Name:GRE + +Team14UUID:c2b1d6a0-9934-11e0-85be-406186cbf87c +Team14Name:HUN + +Team15UUID:c3601ba4-996e-11e0-85be-406186cbf87c +Team15Name:IRL + +Team16UUID:c818eb6a-9934-11e0-85be-406186cbf87c +Team16Name:ISV + +Team17UUID:cd2435f6-9934-11e0-85be-406186cbf87c +Team17Name:ITA + +Team18UUID:d737d124-9934-11e0-85be-406186cbf87c +Team18Name:JPN + +Team19UUID:c8a808d8-996e-11e0-85be-406186cbf87c +Team19Name:MEX + +Team20UUID:cdaadaae-996e-11e0-85be-406186cbf87c +Team20Name:NED + +Team21UUID:dc76ea44-9934-11e0-85be-406186cbf87c +Team21Name:NOR + +Team22UUID:1e8f7452-9c49-11e0-85be-406186cbf87c +Team22Name:NZL + +Team23UUID:d3297b7a-996e-11e0-85be-406186cbf87c +Team23Name:POL + +Team24UUID:7d9ca8fa-9930-11e0-85be-406186cbf87c +Team24Name:RUS + +Team25UUID:e6510b62-9934-11e0-85be-406186cbf87c +Team25Name:SUI + +Team26UUID:eb727d9c-9934-11e0-85be-406186cbf87c +Team26Name:SWE +Team26Comp1UUID:aec14cd6-9d15-11e0-85be-406186cbf87c + +Team27UUID:ffb184b0-98e4-11e0-85be-406186cbf87c +Team27Name:Test + +Team28UUID:bc22780c-9d77-11e0-85be-406186cbf87c +Team28Name:UAE + +Team29UUID:f57e7ea8-9934-11e0-85be-406186cbf87c +Team29Name:UKR + +Team30UUID:0b5469f6-996f-11e0-85be-406186cbf87c +Team30Name:URU + +Team31UUID:9361c468-9930-11e0-85be-406186cbf87c +Team31Name:USA +SELECT Competitors.UUID,FirstName,LastName,Initials,Color,Nationality,Description,Icon,Picture,ClassId,Handicap,HandicapDistance,StartTime FROM event_20110609_KielerWoch.RaceObject INNER JOIN event_20110609_KielerWoch.Competitors ON event_20110609_KielerWoch.RaceObject.TrackedObjectId = event_20110609_KielerWoch.Competitors.UUID WHERE RaceId='357c700a-9d9a-11e0-85be-406186cbf87c' ORDER BY LID,StartTime,Competitors.UUID + + +Comp1UUID:de7d2ba8-9d14-11e0-85be-406186cbf87c +Comp1Name:Kevin +Comp1NameShort:AUS9061 +Comp1Color:FF0000 +Comp1Nationality:AUS +Comp1Description: +Comp1Handicap:1.0000000000 +Comp1HandicapDistance:0.00000 +Comp1StartTime:2011-06-23 13:25:00 +Comp1PictureUUID:6d332670-9934-11e0-85be-406186cbf87c +Comp1ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp1RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp2UUID:e3587876-9d14-11e0-85be-406186cbf87c +Comp2Name:van Wonterghem +Comp2NameShort:DEN8828 +Comp2Color:808000 +Comp2Nationality:DEN +Comp2Description: +Comp2Handicap:1.0000000000 +Comp2HandicapDistance:0.00000 +Comp2StartTime:2011-06-23 13:25:00 +Comp2PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp2ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp2RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp3UUID:e7bc96f4-9d14-11e0-85be-406186cbf87c +Comp3Name:Buhl +Comp3NameShort:DEN8958 +Comp3Color:FFFFFF +Comp3Nationality:DEN +Comp3Description: +Comp3Handicap:1.0000000000 +Comp3HandicapDistance:0.00000 +Comp3StartTime:2011-06-23 13:25:00 +Comp3PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp3ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp3RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp4UUID:ec336744-9d14-11e0-85be-406186cbf87c +Comp4Name:Saugmann +Comp4NameShort:DEN8964 +Comp4Color:FFFF00 +Comp4Nationality:DEN +Comp4Description: +Comp4Handicap:1.0000000000 +Comp4HandicapDistance:0.00000 +Comp4StartTime:2011-06-23 13:25:00 +Comp4PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp4ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp4RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp5UUID:f0c9604c-9d14-11e0-85be-406186cbf87c +Comp5Name:Bøjland +Comp5NameShort:DEN9023 +Comp5Color:800080 +Comp5Nationality:DEN +Comp5Description: +Comp5Handicap:1.0000000000 +Comp5HandicapDistance:0.00000 +Comp5StartTime:2011-06-23 13:25:00 +Comp5PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp5ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp5RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp6UUID:f50675be-9d14-11e0-85be-406186cbf87c +Comp6Name:Broise +Comp6NameShort:FRA8844 +Comp6Color:008080 +Comp6Nationality:FRA +Comp6Description: +Comp6Handicap:1.0000000000 +Comp6HandicapDistance:0.00000 +Comp6StartTime:2011-06-23 13:25:00 +Comp6PictureUUID:5222c1c6-996e-11e0-85be-406186cbf87c +Comp6ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp6RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp7UUID:f969da74-9d14-11e0-85be-406186cbf87c +Comp7Name:boite +Comp7NameShort:FRA8902 +Comp7Color:800000 +Comp7Nationality:FRA +Comp7Description: +Comp7Handicap:1.0000000000 +Comp7HandicapDistance:0.00000 +Comp7StartTime:2011-06-23 13:25:00 +Comp7PictureUUID:5222c1c6-996e-11e0-85be-406186cbf87c +Comp7ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp7RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp8UUID:fda897ba-9d14-11e0-85be-406186cbf87c +Comp8Name:de Lisle +Comp8NameShort:FRA9068 +Comp8Color:000080 +Comp8Nationality:FRA +Comp8Description: +Comp8Handicap:1.0000000000 +Comp8HandicapDistance:0.00000 +Comp8StartTime:2011-06-23 13:25:00 +Comp8PictureUUID:5222c1c6-996e-11e0-85be-406186cbf87c +Comp8ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp8RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp9UUID:062ea47e-9d15-11e0-85be-406186cbf87c +Comp9Name:Menge +Comp9NameShort:GER7861 +Comp9Color:000000 +Comp9Nationality:GER +Comp9Description: +Comp9Handicap:1.0000000000 +Comp9HandicapDistance:0.00000 +Comp9StartTime:2011-06-23 13:25:00 +Comp9PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp9ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp9RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp10UUID:0a917a14-9d15-11e0-85be-406186cbf87c +Comp10Name:Barop +Comp10NameShort:GER8392 +Comp10Color:0000FF +Comp10Nationality:GER +Comp10Description: +Comp10Handicap:1.0000000000 +Comp10HandicapDistance:0.00000 +Comp10StartTime:2011-06-23 13:25:00 +Comp10PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp10ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp10RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp11UUID:0f4448e8-9d15-11e0-85be-406186cbf87c +Comp11Name:Steffen +Comp11NameShort:GER8437 +Comp11Color:FF00FF +Comp11Nationality:GER +Comp11Description: +Comp11Handicap:1.0000000000 +Comp11HandicapDistance:0.00000 +Comp11StartTime:2011-06-23 13:25:00 +Comp11PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp11ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp11RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp12UUID:13e98db8-9d15-11e0-85be-406186cbf87c +Comp12Name:Katja +Comp12NameShort:GER8500 +Comp12Color:008000 +Comp12Nationality:GER +Comp12Description: +Comp12Handicap:1.0000000000 +Comp12HandicapDistance:0.00000 +Comp12StartTime:2011-06-23 13:25:00 +Comp12PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp12ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp12RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp13UUID:1859e280-9d15-11e0-85be-406186cbf87c +Comp13Name:von Nerée +Comp13NameShort:GER8522 +Comp13Color:00FF00 +Comp13Nationality:GER +Comp13Description: +Comp13Handicap:1.0000000000 +Comp13HandicapDistance:0.00000 +Comp13StartTime:2011-06-23 13:25:00 +Comp13PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp13ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp13RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp14UUID:1cd7dcc2-9d15-11e0-85be-406186cbf87c +Comp14Name:Goedeking +Comp14NameShort:GER8622 +Comp14Color:FF0000 +Comp14Nationality:GER +Comp14Description: +Comp14Handicap:1.0000000000 +Comp14HandicapDistance:0.00000 +Comp14StartTime:2011-06-23 13:25:00 +Comp14PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp14ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp14RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp15UUID:21584926-9d15-11e0-85be-406186cbf87c +Comp15Name:Kellner +Comp15NameShort:GER8651 +Comp15Color:808000 +Comp15Nationality:GER +Comp15Description: +Comp15Handicap:1.0000000000 +Comp15HandicapDistance:0.00000 +Comp15StartTime:2011-06-23 13:25:00 +Comp15PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp15ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp15RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp16UUID:25ea8cce-9d15-11e0-85be-406186cbf87c +Comp16Name:Reincke +Comp16NameShort:GER8656 +Comp16Color:FFFFFF +Comp16Nationality:GER +Comp16Description: +Comp16Handicap:1.0000000000 +Comp16HandicapDistance:0.00000 +Comp16StartTime:2011-06-23 13:25:00 +Comp16PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp16ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp16RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp17UUID:2a8fbab0-9d15-11e0-85be-406186cbf87c +Comp17Name:Neulen +Comp17NameShort:GER8657 +Comp17Color:FFFF00 +Comp17Nationality:GER +Comp17Description: +Comp17Handicap:1.0000000000 +Comp17HandicapDistance:0.00000 +Comp17StartTime:2011-06-23 13:25:00 +Comp17PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp17ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp17RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp18UUID:37b2223c-9d15-11e0-85be-406186cbf87c +Comp18Name:Lietz +Comp18NameShort:GER8721 +Comp18Color:800000 +Comp18Nationality:GER +Comp18Description: +Comp18Handicap:1.0000000000 +Comp18HandicapDistance:0.00000 +Comp18StartTime:2011-06-23 13:25:00 +Comp18PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp18ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp18RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp19UUID:3c1fb294-9d15-11e0-85be-406186cbf87c +Comp19Name:Feldmann +Comp19NameShort:GER8730 +Comp19Color:000080 +Comp19Nationality:GER +Comp19Description: +Comp19Handicap:1.0000000000 +Comp19HandicapDistance:0.00000 +Comp19StartTime:2011-06-23 13:25:00 +Comp19PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp19ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp19RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp20UUID:40bd6314-9d15-11e0-85be-406186cbf87c +Comp20Name:Anton +Comp20NameShort:GER8741 +Comp20Color:00FFFF +Comp20Nationality:GER +Comp20Description: +Comp20Handicap:1.0000000000 +Comp20HandicapDistance:0.00000 +Comp20StartTime:2011-06-23 13:25:00 +Comp20PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp20ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp20RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp21UUID:450c5a92-9d15-11e0-85be-406186cbf87c +Comp21Name:Kraft +Comp21NameShort:GER8843 +Comp21Color:000000 +Comp21Nationality:GER +Comp21Description: +Comp21Handicap:1.0000000000 +Comp21HandicapDistance:0.00000 +Comp21StartTime:2011-06-23 13:25:00 +Comp21PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp21ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp21RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp22UUID:498b40ec-9d15-11e0-85be-406186cbf87c +Comp22Name:Brill +Comp22NameShort:GER8874 +Comp22Color:0000FF +Comp22Nationality:GER +Comp22Description: +Comp22Handicap:1.0000000000 +Comp22HandicapDistance:0.00000 +Comp22StartTime:2011-06-23 13:25:00 +Comp22PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp22ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp22RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp23UUID:4e00eaa0-9d15-11e0-85be-406186cbf87c +Comp23Name:Findel +Comp23NameShort:GER8875 +Comp23Color:FF00FF +Comp23Nationality:GER +Comp23Description: +Comp23Handicap:1.0000000000 +Comp23HandicapDistance:0.00000 +Comp23StartTime:2011-06-23 13:25:00 +Comp23PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp23ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp23RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp24UUID:56b22d94-9d15-11e0-85be-406186cbf87c +Comp24Name:Pleßmann +Comp24NameShort:GER8927 +Comp24Color:00FF00 +Comp24Nationality:GER +Comp24Description: +Comp24Handicap:1.0000000000 +Comp24HandicapDistance:0.00000 +Comp24StartTime:2011-06-23 13:25:00 +Comp24PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp24ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp24RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp25UUID:5b14f704-9d15-11e0-85be-406186cbf87c +Comp25Name:Gosch +Comp25NameShort:GER8929 +Comp25Color:FF0000 +Comp25Nationality:GER +Comp25Description: +Comp25Handicap:1.0000000000 +Comp25HandicapDistance:0.00000 +Comp25StartTime:2011-06-23 13:25:00 +Comp25PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp25ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp25RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp26UUID:5f520bea-9d15-11e0-85be-406186cbf87c +Comp26Name:Henge +Comp26NameShort:GER8932 +Comp26Color:808000 +Comp26Nationality:GER +Comp26Description: +Comp26Handicap:1.0000000000 +Comp26HandicapDistance:0.00000 +Comp26StartTime:2011-06-23 13:25:00 +Comp26PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp26ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp26RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp27UUID:63855546-9d15-11e0-85be-406186cbf87c +Comp27Name:Rasenack +Comp27NameShort:GER8956 +Comp27Color:FFFFFF +Comp27Nationality:GER +Comp27Description: +Comp27Handicap:1.0000000000 +Comp27HandicapDistance:0.00000 +Comp27StartTime:2011-06-23 13:25:00 +Comp27PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp27ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp27RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp28UUID:68315036-9d15-11e0-85be-406186cbf87c +Comp28Name:Roos +Comp28NameShort:GER8972 +Comp28Color:FFFF00 +Comp28Nationality:GER +Comp28Description: +Comp28Handicap:1.0000000000 +Comp28HandicapDistance:0.00000 +Comp28StartTime:2011-06-23 13:25:00 +Comp28PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp28ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp28RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp29UUID:70eb542e-9d15-11e0-85be-406186cbf87c +Comp29Name:Achterberg +Comp29NameShort:GER8990 +Comp29Color:008080 +Comp29Nationality:GER +Comp29Description: +Comp29Handicap:1.0000000000 +Comp29HandicapDistance:0.00000 +Comp29StartTime:2011-06-23 13:25:00 +Comp29PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp29ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp29RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp30UUID:75325a1e-9d15-11e0-85be-406186cbf87c +Comp30Name:Lehmann +Comp30NameShort:GER8992 +Comp30Color:800000 +Comp30Nationality:GER +Comp30Description: +Comp30Handicap:1.0000000000 +Comp30HandicapDistance:0.00000 +Comp30StartTime:2011-06-23 13:25:00 +Comp30PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp30ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp30RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp31UUID:7e051528-9d15-11e0-85be-406186cbf87c +Comp31Name:Böger +Comp31NameShort:GER902 +Comp31Color:00FFFF +Comp31Nationality:GER +Comp31Description: +Comp31Handicap:1.0000000000 +Comp31HandicapDistance:0.00000 +Comp31StartTime:2011-06-23 13:25:00 +Comp31PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp31ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp31RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp32UUID:826bdcb4-9d15-11e0-85be-406186cbf87c +Comp32Name:Dr.Plattner +Comp32NameShort:GER9026 +Comp32Color:000000 +Comp32Nationality:GER +Comp32Description: +Comp32Handicap:1.0000000000 +Comp32HandicapDistance:0.00000 +Comp32StartTime:2011-06-23 13:25:00 +Comp32PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp32ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp32RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp33UUID:86d2fb5c-9d15-11e0-85be-406186cbf87c +Comp33Name:Hunger +Comp33NameShort:GER9027 +Comp33Color:0000FF +Comp33Nationality:GER +Comp33Description: +Comp33Handicap:1.0000000000 +Comp33HandicapDistance:0.00000 +Comp33StartTime:2011-06-23 13:25:00 +Comp33PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp33ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp33RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp34UUID:8b618332-9d15-11e0-85be-406186cbf87c +Comp34Name:Köchlin +Comp34NameShort:GER9028 +Comp34Color:FF00FF +Comp34Nationality:GER +Comp34Description: +Comp34Handicap:1.0000000000 +Comp34HandicapDistance:0.00000 +Comp34StartTime:2011-06-23 13:25:00 +Comp34PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp34ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp34RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp35UUID:949b11b6-9d15-11e0-85be-406186cbf87c +Comp35Name:Böhm +Comp35NameShort:GER9035 +Comp35Color:00FF00 +Comp35Nationality:GER +Comp35Description: +Comp35Handicap:1.0000000000 +Comp35HandicapDistance:0.00000 +Comp35StartTime:2011-06-23 13:25:00 +Comp35PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp35ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp35RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp36UUID:991ab354-9d15-11e0-85be-406186cbf87c +Comp36Name:Dasenbrook +Comp36NameShort:GER9040 +Comp36Color:FF0000 +Comp36Nationality:GER +Comp36Description: +Comp36Handicap:1.0000000000 +Comp36HandicapDistance:0.00000 +Comp36StartTime:2011-06-23 13:25:00 +Comp36PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp36ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp36RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp37UUID:9d4e23d4-9d15-11e0-85be-406186cbf87c +Comp37Name:Birkner +Comp37NameShort:GER9043 +Comp37Color:808000 +Comp37Nationality:GER +Comp37Description: +Comp37Handicap:1.0000000000 +Comp37HandicapDistance:0.00000 +Comp37StartTime:2011-06-23 13:25:00 +Comp37PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp37ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp37RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp38UUID:a1f9ca46-9d15-11e0-85be-406186cbf87c +Comp38Name:Fischer +Comp38NameShort:GER9054 +Comp38Color:FFFFFF +Comp38Nationality:GER +Comp38Description: +Comp38Handicap:1.0000000000 +Comp38HandicapDistance:0.00000 +Comp38StartTime:2011-06-23 13:25:00 +Comp38PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp38ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp38RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp39UUID:aa852246-9d15-11e0-85be-406186cbf87c +Comp39Name:Schomäker +Comp39NameShort:GER9070 +Comp39Color:800080 +Comp39Nationality:GER +Comp39Description: +Comp39Handicap:1.0000000000 +Comp39HandicapDistance:0.00000 +Comp39StartTime:2011-06-23 13:25:00 +Comp39PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp39ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp39RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +Comp40UUID:aec14cd6-9d15-11e0-85be-406186cbf87c +Comp40Name:Hastenpflug +Comp40NameShort:SWE8912 +Comp40Color:008080 +Comp40Nationality:SWE +Comp40Description: +Comp40Handicap:1.0000000000 +Comp40HandicapDistance:0.00000 +Comp40StartTime:2011-06-23 13:25:00 +Comp40PictureUUID:ebb4c5da-9934-11e0-85be-406186cbf87c +Comp40ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp40RouteUUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c + +ControlPoint1UUID:07d84022-9951-11e0-85be-406186cbf87c +ControlPoint1Name:D Mark4 +ControlPoint1HasTwoPoints:1 +ControlPoint1ShortName:4 +ControlPoint1Mark1Lon:10.169475 +ControlPoint1Mark1Lat:54.428818 +ControlPoint1Mark2Lon:10.169460 +ControlPoint1Mark2Lat:54.428757 + +ControlPoint2UUID:0807af7c-9b42-11e0-85be-406186cbf87c +ControlPoint2Name:Star-R6-1 +ControlPoint2HasTwoPoints:0 +ControlPoint2ShortName:1 +ControlPoint2Mark1Lon:10.235563 +ControlPoint2Mark1Lat:54.504244 + +ControlPoint3UUID:0beb273c-992f-11e0-85be-406186cbf87c +ControlPoint3Name:Finish Silbernes Band +ControlPoint3HasTwoPoints:1 +ControlPoint3ShortName:F +ControlPoint3Mark1Lon:10.000465 +ControlPoint3Mark1Lat:54.273018 +ControlPoint3Mark2Lon:10.273137 +ControlPoint3Mark2Lat:54.500737 + +ControlPoint4UUID:144f44f4-9dac-11e0-85be-406186cbf87c +ControlPoint4Name:SB-Mark H5 (y) (WP 33) +ControlPoint4HasTwoPoints:0 +ControlPoint4ShortName:H5 +ControlPoint4Mark1Lon:10.666667 +ControlPoint4Mark1Lat:54.548333 + +ControlPoint5UUID:18e98784-9930-11e0-85be-406186cbf87c +ControlPoint5Name:Start Silbernes Band +ControlPoint5HasTwoPoints:1 +ControlPoint5ShortName:S +ControlPoint5Mark1Lon:10.171790 +ControlPoint5Mark1Lat:54.436000 +ControlPoint5Mark2Lon:10.172257 +ControlPoint5Mark2Lat:54.436363 + +ControlPoint6UUID:1d8536e6-9a91-11e0-85be-406186cbf87c +ControlPoint6Name:Star1-4 +ControlPoint6HasTwoPoints:1 +ControlPoint6ShortName:4 +ControlPoint6Mark1Lon:10.219525 +ControlPoint6Mark1Lat:54.500618 +ControlPoint6Mark2Lon:10.220421 +ControlPoint6Mark2Lat:54.500744 + +ControlPoint7UUID:22475d30-9951-11e0-85be-406186cbf87c +ControlPoint7Name:Audi Start +ControlPoint7HasTwoPoints:1 +ControlPoint7ShortName:S +ControlPoint7Mark1Lon:10.169428 +ControlPoint7Mark1Lat:54.428623 +ControlPoint7Mark2Lon:10.168688 +ControlPoint7Mark2Lat:54.346428 + +ControlPoint8UUID:2307bec6-992f-11e0-85be-406186cbf87c +ControlPoint8Name:Mark KO2 red/white +ControlPoint8HasTwoPoints:0 +ControlPoint8ShortName:KO +ControlPoint8Mark1Lon:10.450000 +ControlPoint8Mark1Lat:54.533333 + +ControlPoint9UUID:2497f822-9ab1-11e0-85be-406186cbf87c +ControlPoint9Name:49Y-R7-1 +ControlPoint9HasTwoPoints:0 +ControlPoint9ShortName:1 +ControlPoint9Mark1Lon:10.207426 +ControlPoint9Mark1Lat:54.435751 + +ControlPoint10UUID:27c13096-9a8d-11e0-85be-406186cbf87c +ControlPoint10Name:49b4-4 +ControlPoint10HasTwoPoints:1 +ControlPoint10ShortName:4 +ControlPoint10Mark1Lon:10.208914 +ControlPoint10Mark1Lat:54.445496 +ControlPoint10Mark2Lon:10.209630 +ControlPoint10Mark2Lat:54.444822 + +ControlPoint11UUID:2c8584ca-9dac-11e0-85be-406186cbf87c +ControlPoint11Name:SB-Finish (Ziel) at Kiel Lighthouse +ControlPoint11HasTwoPoints:0 +ControlPoint11ShortName:KL +ControlPoint11Mark1Lon:10.278333 +ControlPoint11Mark1Lat:54.498333 + +ControlPoint12UUID:32ad8a14-992e-11e0-85be-406186cbf87c +ControlPoint12Name:Mark 14 red (Fahrwassertonne 14) +ControlPoint12HasTwoPoints:0 +ControlPoint12ShortName:14 +ControlPoint12Mark1Lon:10.185000 +ControlPoint12Mark1Lat:54.371667 + +ControlPoint13UUID:32fb2850-9951-11e0-85be-406186cbf87c +ControlPoint13Name:Audi Mark1 +ControlPoint13HasTwoPoints:0 +ControlPoint13ShortName:1 +ControlPoint13Mark1Lon:10.169383 +ControlPoint13Mark1Lat:54.428848 + +ControlPoint14UUID:34feeaea-9c32-11e0-85be-406186cbf87c +ControlPoint14Name:Star7-1a +ControlPoint14HasTwoPoints:0 +ControlPoint14ShortName:1a +ControlPoint14Mark1Lon:10.230608 +ControlPoint14Mark1Lat:54.508407 + +ControlPoint15UUID:3ecf9ade-992f-11e0-85be-406186cbf87c +ControlPoint15Name:Schlei Safe Water Mark red/white +ControlPoint15HasTwoPoints:0 +ControlPoint15ShortName:SS +ControlPoint15Mark1Lon:10.055000 +ControlPoint15Mark1Lat:54.668333 + +ControlPoint16UUID:405f346c-99e9-11e0-85be-406186cbf87c +ControlPoint16Name:49erYR2-1 +ControlPoint16HasTwoPoints:0 +ControlPoint16ShortName:1 +ControlPoint16Mark1Lon:10.200182 +ControlPoint16Mark1Lat:54.427670 + +ControlPoint17UUID:4123b6ea-9951-11e0-85be-406186cbf87c +ControlPoint17Name:Audi Mark2 +ControlPoint17HasTwoPoints:0 +ControlPoint17ShortName:2 +ControlPoint17Mark1Lon:10.169350 +ControlPoint17Mark1Lat:54.428940 + +ControlPoint18UUID:4bac3eca-992e-11e0-85be-406186cbf87c +ControlPoint18Name:Three yellow marks* (3 gelbe Tonnen) +ControlPoint18HasTwoPoints:0 +ControlPoint18ShortName:3y +ControlPoint18Mark1Lon:10.198333 +ControlPoint18Mark1Lat:54.386667 + +ControlPoint19UUID:535fb3f4-9951-11e0-85be-406186cbf87c +ControlPoint19Name:Audi Finish +ControlPoint19HasTwoPoints:1 +ControlPoint19ShortName:F +ControlPoint19Mark1Lon:10.159173 +ControlPoint19Mark1Lat:54.341737 +ControlPoint19Mark2Lon:10.169527 +ControlPoint19Mark2Lat:54.429047 + +ControlPoint20UUID:56f63cf8-99e8-11e0-85be-406186cbf87c +ControlPoint20Name:49erYR1-T +ControlPoint20HasTwoPoints:0 +ControlPoint20ShortName:T +ControlPoint20Mark1Lon:10.199148 +ControlPoint20Mark1Lat:54.429370 + +ControlPoint21UUID:5933cdca-9950-11e0-85be-406186cbf87c +ControlPoint21Name:H Start +ControlPoint21HasTwoPoints:1 +ControlPoint21ShortName:S +ControlPoint21Mark1Lon:10.169285 +ControlPoint21Mark1Lat:54.428848 +ControlPoint21Mark2Lon:10.169413 +ControlPoint21Mark2Lat:54.428753 + +ControlPoint22UUID:65b2dfbc-9b64-11e0-85be-406186cbf87c +ControlPoint22Name:K Start +ControlPoint22HasTwoPoints:1 +ControlPoint22ShortName:S +ControlPoint22Mark1Lon:10.205912 +ControlPoint22Mark1Lat:54.497498 +ControlPoint22Mark2Lon:10.203480 +ControlPoint22Mark2Lat:54.498760 + +ControlPoint23UUID:65c3a9ec-992e-11e0-85be-406186cbf87c +ControlPoint23Name:Mark 4 red (Fahrwassertonne 4) +ControlPoint23HasTwoPoints:0 +ControlPoint23ShortName:4 +ControlPoint23Mark1Lon:10.233333 +ControlPoint23Mark1Lat:54.428333 + +ControlPoint24UUID:69a347b2-9950-11e0-85be-406186cbf87c +ControlPoint24Name:H Mark1 +ControlPoint24HasTwoPoints:0 +ControlPoint24ShortName:1 +ControlPoint24Mark1Lon:10.169690 +ControlPoint24Mark1Lat:54.428840 + +ControlPoint25UUID:6c30e118-9a8d-11e0-85be-406186cbf87c +ControlPoint25Name:49b4-F +ControlPoint25HasTwoPoints:1 +ControlPoint25ShortName:F +ControlPoint25Mark1Lon:10.209712 +ControlPoint25Mark1Lat:54.445952 +ControlPoint25Mark2Lon:10.210658 +ControlPoint25Mark2Lat:54.445241 + +ControlPoint26UUID:7634bbfe-9dac-11e0-85be-406186cbf87c +ControlPoint26Name:SB-Mark Red (WP 124) +ControlPoint26HasTwoPoints:0 +ControlPoint26ShortName:WP +ControlPoint26Mark1Lon:10.236667 +ControlPoint26Mark1Lat:55.045000 + +ControlPoint27UUID:79dab2ea-9aaf-11e0-85be-406186cbf87c +ControlPoint27Name:49erB5-4 +ControlPoint27HasTwoPoints:1 +ControlPoint27ShortName:4 +ControlPoint27Mark1Lon:10.201355 +ControlPoint27Mark1Lat:54.446073 +ControlPoint27Mark2Lon:10.197830 +ControlPoint27Mark2Lat:54.446886 + +ControlPoint28UUID:7a218886-9b64-11e0-85be-406186cbf87c +ControlPoint28Name:K Mark1 +ControlPoint28HasTwoPoints:0 +ControlPoint28ShortName:1 +ControlPoint28Mark1Lon:10.174787 +ControlPoint28Mark1Lat:54.492633 + +ControlPoint29UUID:7c639ea8-9da9-11e0-85be-406186cbf87c +ControlPoint29Name:SB Mark1 (Schleimünde Approach (r/w) ) +ControlPoint29HasTwoPoints:0 +ControlPoint29ShortName:1 +ControlPoint29Mark1Lon:10.060000 +ControlPoint29Mark1Lat:54.665000 + +ControlPoint30UUID:8145342e-992e-11e0-85be-406186cbf87c +ControlPoint30Name:Mark 2 red (Fahrwassertonne 2) +ControlPoint30HasTwoPoints:0 +ControlPoint30ShortName:2 +ControlPoint30Mark1Lon:10.283333 +ControlPoint30Mark1Lat:54.465000 + +ControlPoint31UUID:84009c94-98e3-11e0-85be-406186cbf87c +ControlPoint31Name:Test - Top Mark +ControlPoint31HasTwoPoints:0 +ControlPoint31ShortName:1 +ControlPoint31Mark1Lon:10.169028 +ControlPoint31Mark1Lat:54.428993 + +ControlPoint32UUID:86c90a6e-9a9c-11e0-85be-406186cbf87c +ControlPoint32Name:S3-4 +ControlPoint32HasTwoPoints:1 +ControlPoint32ShortName:4 +ControlPoint32Mark1Lon:10.218663 +ControlPoint32Mark1Lat:54.515525 +ControlPoint32Mark2Lon:10.218214 +ControlPoint32Mark2Lat:54.515649 + +ControlPoint33UUID:8d1c4750-9b64-11e0-85be-406186cbf87c +ControlPoint33Name:K Mark2 +ControlPoint33HasTwoPoints:0 +ControlPoint33ShortName:2 +ControlPoint33Mark1Lon:10.170753 +ControlPoint33Mark1Lat:54.429023 + +ControlPoint34UUID:99cae4d0-992e-11e0-85be-406186cbf87c +ControlPoint34Name:Mark 1 green (Fahrwassertonne 1) +ControlPoint34HasTwoPoints:0 +ControlPoint34ShortName:1 +ControlPoint34Mark1Lon:10.251667 +ControlPoint34Mark1Lat:54.475000 + +ControlPoint35UUID:9beca160-9a8f-11e0-85be-406186cbf87c +ControlPoint35Name:49erb1-1 +ControlPoint35HasTwoPoints:0 +ControlPoint35ShortName:T +ControlPoint35Mark1Lon:10.196778 +ControlPoint35Mark1Lat:54.439621 + +ControlPoint36UUID:9c4e4984-9a93-11e0-85be-406186cbf87c +ControlPoint36Name:Star2-4 +ControlPoint36HasTwoPoints:1 +ControlPoint36ShortName:4 +ControlPoint36Mark1Lon:10.230194 +ControlPoint36Mark1Lat:54.504793 +ControlPoint36Mark2Lon:10.229448 +ControlPoint36Mark2Lat:54.505033 + +ControlPoint37UUID:9caa2f3a-98e3-11e0-85be-406186cbf87c +ControlPoint37Name:Test - start +ControlPoint37HasTwoPoints:1 +ControlPoint37ShortName:S +ControlPoint37Mark1Lon:10.169440 +ControlPoint37Mark1Lat:54.428712 +ControlPoint37Mark2Lon:10.169403 +ControlPoint37Mark2Lat:54.428872 + +ControlPoint38UUID:9da911ea-9950-11e0-85be-406186cbf87c +ControlPoint38Name:H Mark4 +ControlPoint38HasTwoPoints:1 +ControlPoint38ShortName:4 +ControlPoint38Mark1Lon:10.199247 +ControlPoint38Mark1Lat:54.426952 +ControlPoint38Mark2Lon:10.169390 +ControlPoint38Mark2Lat:54.428780 + +ControlPoint39UUID:a0cbd40c-9dab-11e0-85be-406186cbf87c +ControlPoint39Name:SB-Mark Schleimünde Approach (r/w) +ControlPoint39HasTwoPoints:0 +ControlPoint39ShortName:SA +ControlPoint39Mark1Lon:10.060000 +ControlPoint39Mark1Lat:54.665000 + +ControlPoint40UUID:a68cc03e-9b64-11e0-85be-406186cbf87c +ControlPoint40Name:K Mark3 +ControlPoint40HasTwoPoints:1 +ControlPoint40ShortName:3 +ControlPoint40Mark1Lon:10.169493 +ControlPoint40Mark1Lat:54.428673 +ControlPoint40Mark2Lon:10.242970 +ControlPoint40Mark2Lat:54.475930 + +ControlPoint41UUID:a8c75c3e-9a8c-11e0-85be-406186cbf87c +ControlPoint41Name:49b4-Start +ControlPoint41HasTwoPoints:1 +ControlPoint41ShortName:S +ControlPoint41Mark1Lon:10.209545 +ControlPoint41Mark1Lat:54.446065 +ControlPoint41Mark2Lon:10.211504 +ControlPoint41Mark2Lat:54.444520 + +ControlPoint42UUID:a91da4ea-9dac-11e0-85be-406186cbf87c +ControlPoint42Name:SB-Mark Thurö Rev (Y/B) (WP 277) +ControlPoint42HasTwoPoints:0 +ControlPoint42ShortName:Th +ControlPoint42Mark1Lon:10.735000 +ControlPoint42Mark1Lat:55.020000 + +ControlPoint43UUID:b2ad1adc-9950-11e0-85be-406186cbf87c +ControlPoint43Name:H Finish +ControlPoint43HasTwoPoints:1 +ControlPoint43ShortName:F +ControlPoint43Mark1Lon:10.169407 +ControlPoint43Mark1Lat:54.428787 +ControlPoint43Mark2Lon:10.169665 +ControlPoint43Mark2Lat:54.428878 + +ControlPoint44UUID:b31eaad0-9dab-11e0-85be-406186cbf87c +ControlPoint44Name:SB-Mark Spodsbjerg-SE +ControlPoint44HasTwoPoints:0 +ControlPoint44ShortName:Sp +ControlPoint44Mark1Lon:10.845000 +ControlPoint44Mark1Lat:54.918333 + +ControlPoint45UUID:b3af1998-992e-11e0-85be-406186cbf87c +ControlPoint45Name:Mark K yellow (Kabeltonne) +ControlPoint45HasTwoPoints:0 +ControlPoint45ShortName:K +ControlPoint45Mark1Lon:10.255000 +ControlPoint45Mark1Lat:54.488333 + +ControlPoint46UUID:bfe4dc42-9b64-11e0-85be-406186cbf87c +ControlPoint46Name:K Mark4 +ControlPoint46HasTwoPoints:1 +ControlPoint46ShortName:4 +ControlPoint46Mark1Lon:10.200983 +ControlPoint46Mark1Lat:54.499038 +ControlPoint46Mark2Lon:10.200347 +ControlPoint46Mark2Lat:54.499482 + +ControlPoint47UUID:c2199936-9dac-11e0-85be-406186cbf87c +ControlPoint47Name:SB-Mark Black Yellow (WP 253) +ControlPoint47HasTwoPoints:0 +ControlPoint47ShortName:BY +ControlPoint47Mark1Lon:10.936667 +ControlPoint47Mark1Lat:55.185000 + +ControlPoint48UUID:c583e438-9950-11e0-85be-406186cbf87c +ControlPoint48Name:D Start +ControlPoint48HasTwoPoints:1 +ControlPoint48ShortName:S +ControlPoint48Mark1Lon:10.169282 +ControlPoint48Mark1Lat:54.428715 +ControlPoint48Mark2Lon:10.169437 +ControlPoint48Mark2Lat:54.428597 + +ControlPoint49UUID:cd57b5f8-992e-11e0-85be-406186cbf87c +ControlPoint49Name:Stollergrund-N (black/yellow) +ControlPoint49HasTwoPoints:0 +ControlPoint49ShortName:SN +ControlPoint49Mark1Lon:10.200000 +ControlPoint49Mark1Lat:54.536667 + +ControlPoint50UUID:d2b4c5d2-9950-11e0-85be-406186cbf87c +ControlPoint50Name:D Mark1 +ControlPoint50HasTwoPoints:0 +ControlPoint50ShortName:1 +ControlPoint50Mark1Lon:10.166928 +ControlPoint50Mark1Lat:54.345997 + +ControlPoint51UUID:de21cff2-9ab6-11e0-85be-406186cbf87c +ControlPoint51Name:49Y-R8-1a +ControlPoint51HasTwoPoints:0 +ControlPoint51ShortName:1 +ControlPoint51Mark1Lon:10.207476 +ControlPoint51Mark1Lat:54.435762 + +ControlPoint52UUID:e299f688-9dac-11e0-85be-406186cbf87c +ControlPoint52Name:SB-Gate DW 59 green and DW 56 red +ControlPoint52HasTwoPoints:0 +ControlPoint52ShortName:59 +ControlPoint52Mark1Lon:10.780000 +ControlPoint52Mark1Lat:54.736667 + +ControlPoint53UUID:e59815c6-9b64-11e0-85be-406186cbf87c +ControlPoint53Name:K Finish +ControlPoint53HasTwoPoints:1 +ControlPoint53ShortName:F +ControlPoint53Mark1Lon:10.169688 +ControlPoint53Mark1Lat:54.489167 +ControlPoint53Mark2Lon:10.170013 +ControlPoint53Mark2Lat:54.488803 + +ControlPoint54UUID:e663db6c-9a8c-11e0-85be-406186cbf87c +ControlPoint54Name:49b4-1 +ControlPoint54HasTwoPoints:0 +ControlPoint54ShortName:1 +ControlPoint54Mark1Lon:10.196146 +ControlPoint54Mark1Lat:54.441407 + +ControlPoint55UUID:f214ea50-992e-11e0-85be-406186cbf87c +ControlPoint55Name:Gate between Mittelgrund-N +ControlPoint55HasTwoPoints:0 +ControlPoint55ShortName:MN +ControlPoint55Mark1Lon:10.055000 +ControlPoint55Mark1Lat:54.513333 + +ControlPoint56UUID:f641d92e-9ab6-11e0-85be-406186cbf87c +ControlPoint56Name:49Y-R8-1b +ControlPoint56HasTwoPoints:0 +ControlPoint56ShortName:1 +ControlPoint56Mark1Lon:10.203712 +ControlPoint56Mark1Lat:54.434916 + +ControlPoint57UUID:f76bea2c-9a95-11e0-85be-406186cbf87c +ControlPoint57Name:DIM1-F +ControlPoint57HasTwoPoints:1 +ControlPoint57ShortName:F +ControlPoint57Mark1Lon:9.850912 +ControlPoint57Mark1Lat:54.474097 +ControlPoint57Mark2Lon:9.849851 +ControlPoint57Mark2Lat:54.472319 + + +SegmentFile:segments.php +Route1UUID:09ba5a4a-9d9a-11e0-85be-406186cbf87c +Route1Description:505 Race 2 +Route1Length:0 +Route1ControlPoint1UUID:65b2dfbc-9b64-11e0-85be-406186cbf87c +Route1ControlPoint2UUID:7a218886-9b64-11e0-85be-406186cbf87c +Route1ControlPoint3UUID:bfe4dc42-9b64-11e0-85be-406186cbf87c +Route1ControlPoint4UUID:7a218886-9b64-11e0-85be-406186cbf87c +Route1ControlPoint5UUID:bfe4dc42-9b64-11e0-85be-406186cbf87c +Route1ControlPoint6UUID:e59815c6-9b64-11e0-85be-406186cbf87c + +//-------- AUTO GENERATED PARAMETERS END ------------------ diff --git a/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_3.mtb b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_3.mtb new file mode 100644 index 00000000000..900aff34e17 Binary files /dev/null and b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_3.mtb differ diff --git a/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_3.txt b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_3.txt new file mode 100644 index 00000000000..b5231fd4c60 --- /dev/null +++ b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_Race_3.txt @@ -0,0 +1,1419 @@ +stored-uri:datafiles/event_20110609_KielerWoch-505_Race_3.mtb +StartReplay:1 + +// Input from params_static: + +// Input from event_parameters: + +CompMarkerUUID:1 +CodanIsSponsor:0 +EnablePrint:1 +DefaultTailLength:60 +APzoomChangeTime:2500 +disableAutoProduce:0 +autoProduce:0 +APstartLineShowOfLeg:0.25 +APsideOfStartLine:0.30 +APstartSwitch:0.8 +APwholeLegBehindMarks:0.15 +APmarkSwitch:0.13 +APmarkZoomBehindMarks:0.10 +APmarkZoomInFrontOfMarks:0.20 +PopupMarkerUUID:2 +ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf +WeatherLat1:56 +WeatherLat2:54 +Marker1UUID:1 +Marker1Type:RGBMarkerFactory +Marker1Directional:1 +Marker1Image:markers/boat_FF0000_big.png +Marker1MetersPerPixel:0.0617 +Marker1MinScale:0.396 +Startlocation:10.197659,54.456129 +MaxDiffTime:600 +MaxMetersPerSecond:20 +PrecisionMeters:20 +ShortAvgTime:180 +LongAvgTime:900 +TimeZone:2 +CompMarkerTimeout:60 +FramesPerSecond:8 +replayspeedup:5 +MatchRaceClock:0 +MatchRaceClockNo:3 +MatchRaceClockSpacing:300 +Minimap:1 +MinimapUseLayers:1 +Minimapgeorect:9.840775,54.516538,10.3022,54.309554 +MinimapMarkerUUID:2 +Marker4UUID:2 +Marker4Type:RGBmarkerFactory +Marker4Directional:1 +Marker4Image:markers/boat_FF0000_small.png +MetersPerPixel:25 +MetersPerPixelMax:200 +MetersPerPixelMin:0.2 +isMatchRace:0 +weatherEnabled:0 +showRaceClock:1 +BackGroundImage:graphics/background.jpg +DisableRotation:0 +ControlPointMarkerRadius:13 +disableselection:0 +disableleaderline:0 +WeatherLon1:9 +WeatherLon2:12 +DisableChangeRace:0 +PopUpRow1Name: +PopUpRow1Type:Name +PopUpRow2Name:Knots +PopUpRow2Type:BoatSpeed +PopUpRow3Name:Ranking +PopUpRow3Type:RowNumber +PopUpRow4Name:Sail +PopUpRow4Type:Ini +PopUpRow5Name:After +PopUpRow5Type:BoatAfter +ResultColumn1Name:# +ResultColumn1Size:20 +ResultColumn1Type:RowNumber +ResultColumn2Size:20 +ResultColumn2Type:ColorMarker +ResultColumn3Name:sail +ResultColumn3Size:60 +ResultColumn3Type:Ini +ResultColumn4Name:name +ResultColumn4Size:110 +ResultColumn4Type:Name +ResultColumn5Name:after +ResultColumn5Size:45 +ResultColumn5Type:BoatAfter +ResultColumn6Name:kts +ResultColumn6Size:40 +ResultColumn6Type:BoatSpeed +DisableLeaderboard:0 +DisableAnalysis:0 +TableFontSize:11 +//live-uri:tcp://germanmaster.traclive.dk:4400 +//stored-uri:tcp://germanmaster.traclive.dk:4401 +compInitialsFontSize:11 +AutoRotate:0 +MassStart:0 +SegmentFile:segments.php +StartType:Mass +ReCalcDirAndSpeed:0 +RaceHandicapSystem:NONE +TeamCompetitionType:NONE +LiveDelaySecs: 180 + +//-------- AUTO GENERATED PARAMETERS START ---------------- + +EventID:f34b232a-934e-11e0-b3bb-406186cbf87c +EventName:Kieler Woche +EventDB:event_20110609_KielerWoch +EventStartTime:2011-06-15 09:00:00 +EventEndTime:2011-06-26 23:59:00 + +RaceID:e876c3a0-9da8-11e0-85be-406186cbf87c +RaceName:505 Race 3 +RaceStartTime:2011-06-23 15:13:00 +RaceEndTime: +RaceTrackingStartTime:2011-06-23 14:58:33 +RaceTrackingEndTime:2011-06-23 17:00:00 +RaceDefaultRouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c +RaceHandicapSystem:NONE + +Image1UUID:0ba089bc-996f-11e0-85be-406186cbf87c +Image1Type:Logo +Image1Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/URU.gif +Image1Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/URU.gif + +Image2UUID:1d548b00-996e-11e0-85be-406186cbf87c +Image2Type:Logo +Image2Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif +Image2Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif + +Image3UUID:1ece41fa-9c49-11e0-85be-406186cbf87c +Image3Type:Logo +Image3Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif +Image3Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif + +Image4UUID:22b1a988-98e3-11e0-85be-406186cbf87c +Image4Type:Logo +Image4Description:Veolia +Image4Image: + +Image5UUID:22ba0304-98d8-11e0-85be-406186cbf87c +Image5Type:Logo +Image5Description:Kieler Woche +Image5Image: + +Image6UUID:2767b7e8-996e-11e0-85be-406186cbf87c +Image6Type:Logo +Image6Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BEL.gif +Image6Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BEL.gif + +Image7UUID:2cda6f4a-996e-11e0-85be-406186cbf87c +Image7Type:Logo +Image7Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BRA.gif +Image7Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BRA.gif + +Image8UUID:3313b588-98d8-11e0-85be-406186cbf87c +Image8Type:Logo +Image8Description:Audi +Image8Image: + +Image9UUID:33a26700-98e3-11e0-85be-406186cbf87c +Image9Type:Logo +Image9Description:HSH NORDBANK +Image9Image: + +Image10UUID:373009a0-996e-11e0-85be-406186cbf87c +Image10Type:Logo +Image10Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CHI.gif +Image10Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CHI.gif + +Image11UUID:4cc5360a-996e-11e0-85be-406186cbf87c +Image11Type:Logo +Image11Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/EST.gif +Image11Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/EST.gif + +Image12UUID:514008f8-98e3-11e0-85be-406186cbf87c +Image12Type:Logo +Image12Description:ISAF SAILING WORLDCUP +Image12Image: + +Image13UUID:5222c1c6-996e-11e0-85be-406186cbf87c +Image13Type:Logo +Image13Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FRA.gif +Image13Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FRA.gif + +Image14UUID:574e0d40-98d8-11e0-85be-406186cbf87c +Image14Type:Logo +Image14Description:SAP +Image14Image: + +Image15UUID:617e8236-996e-11e0-85be-406186cbf87c +Image15Type:Logo +Image15Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GBR.gif +Image15Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GBR.gif + +Image16UUID:6d332670-9934-11e0-85be-406186cbf87c +Image16Type:Logo +Image16Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUS.gif +Image16Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUS.gif + +Image17UUID:7252c142-9934-11e0-85be-406186cbf87c +Image17Type:Logo +Image17Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CAN.gif +Image17Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CAN.gif + +Image18UUID:77555f2e-9934-11e0-85be-406186cbf87c +Image18Type:Logo +Image18Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CRO.gif +Image18Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CRO.gif + +Image19UUID:7c87105a-9934-11e0-85be-406186cbf87c +Image19Type:Logo +Image19Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif +Image19Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif + +Image20UUID:7deb67ce-9930-11e0-85be-406186cbf87c +Image20Type:Logo +Image20Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RUS.gif +Image20Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RUS.gif + +Image21UUID:81fea498-9930-11e0-85be-406186cbf87c +Image21Type:Logo +Image21Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GER.gif +Image21Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GER.gif + +Image22UUID:93a58cc0-9930-11e0-85be-406186cbf87c +Image22Type:Logo +Image22Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/USA.gif +Image22Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/USA.gif + +Image23UUID:bc6068c4-9d77-11e0-85be-406186cbf87c +Image23Type:Logo +Image23Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UAE.gif +Image23Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UAE.gif + +Image24UUID:be8c642a-996e-11e0-85be-406186cbf87c +Image24Type:Logo +Image24Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GRE.gif +Image24Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GRE.gif + +Image25UUID:c2fdb14c-9934-11e0-85be-406186cbf87c +Image25Type:Logo +Image25Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/HUN.gif +Image25Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/HUN.gif + +Image26UUID:c3a2eaa6-996e-11e0-85be-406186cbf87c +Image26Type:Logo +Image26Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/IRL.gif +Image26Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/IRL.gif + +Image27UUID:c85b16fc-9934-11e0-85be-406186cbf87c +Image27Type:Logo +Image27Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISV.gif +Image27Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISV.gif + +Image28UUID:c8eaf36e-996e-11e0-85be-406186cbf87c +Image28Type:Logo +Image28Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MEX.gif +Image28Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MEX.gif + +Image29UUID:cd74b15c-9934-11e0-85be-406186cbf87c +Image29Type:Logo +Image29Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ITA.gif +Image29Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ITA.gif + +Image30UUID:cdf05d40-996e-11e0-85be-406186cbf87c +Image30Type:Logo +Image30Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NED.gif +Image30Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NED.gif + +Image31UUID:d3764b44-996e-11e0-85be-406186cbf87c +Image31Type:Logo +Image31Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POL.gif +Image31Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POL.gif + +Image32UUID:d77c220c-9934-11e0-85be-406186cbf87c +Image32Type:Logo +Image32Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/JPN.gif +Image32Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/JPN.gif + +Image33UUID:dcb92152-9934-11e0-85be-406186cbf87c +Image33Type:Logo +Image33Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NOR.gif +Image33Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NOR.gif + +Image34UUID:e69d47f2-9934-11e0-85be-406186cbf87c +Image34Type:Logo +Image34Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif +Image34Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif + +Image35UUID:ebb4c5da-9934-11e0-85be-406186cbf87c +Image35Type:Logo +Image35Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SWE.gif +Image35Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SWE.gif + +Image36UUID:f5cab6b0-9934-11e0-85be-406186cbf87c +Image36Type:Logo +Image36Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UKR.gif +Image36Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UKR.gif + +Class1UUID:da553a2a-9d14-11e0-85be-406186cbf87c +Class1Name:5o5 + +Team1UUID:6cf050ac-9934-11e0-85be-406186cbf87c +Team1Name:AUS +Team1Comp1UUID:de7d2ba8-9d14-11e0-85be-406186cbf87c + +Team2UUID:1d07e516-996e-11e0-85be-406186cbf87c +Team2Name:AUT + +Team3UUID:271ac230-996e-11e0-85be-406186cbf87c +Team3Name:BEL + +Team4UUID:2c8e2630-996e-11e0-85be-406186cbf87c +Team4Name:BRA + +Team5UUID:7210a622-9934-11e0-85be-406186cbf87c +Team5Name:CAN + +Team6UUID:36ebe888-996e-11e0-85be-406186cbf87c +Team6Name:CHI + +Team7UUID:77133b08-9934-11e0-85be-406186cbf87c +Team7Name:CRO + +Team8UUID:7c3a4770-9934-11e0-85be-406186cbf87c +Team8Name:DEN +Team8Comp1UUID:e7bc96f4-9d14-11e0-85be-406186cbf87c +Team8Comp2UUID:f0c9604c-9d14-11e0-85be-406186cbf87c +Team8Comp3UUID:ec336744-9d14-11e0-85be-406186cbf87c +Team8Comp4UUID:e3587876-9d14-11e0-85be-406186cbf87c + +Team9UUID:4c650fc8-996e-11e0-85be-406186cbf87c +Team9Name:EST + +Team10UUID:51d56ce6-996e-11e0-85be-406186cbf87c +Team10Name:FRA +Team10Comp1UUID:f969da74-9d14-11e0-85be-406186cbf87c +Team10Comp2UUID:f50675be-9d14-11e0-85be-406186cbf87c +Team10Comp3UUID:fda897ba-9d14-11e0-85be-406186cbf87c + +Team11UUID:613a12ea-996e-11e0-85be-406186cbf87c +Team11Name:GBR + +Team12UUID:81b951d6-9930-11e0-85be-406186cbf87c +Team12Name:GER +Team12Comp1UUID:70eb542e-9d15-11e0-85be-406186cbf87c +Team12Comp2UUID:40bd6314-9d15-11e0-85be-406186cbf87c +Team12Comp3UUID:0a917a14-9d15-11e0-85be-406186cbf87c +Team12Comp4UUID:9d4e23d4-9d15-11e0-85be-406186cbf87c +Team12Comp5UUID:498b40ec-9d15-11e0-85be-406186cbf87c +Team12Comp6UUID:7e051528-9d15-11e0-85be-406186cbf87c +Team12Comp7UUID:949b11b6-9d15-11e0-85be-406186cbf87c +Team12Comp8UUID:991ab354-9d15-11e0-85be-406186cbf87c +Team12Comp9UUID:826bdcb4-9d15-11e0-85be-406186cbf87c +Team12Comp10UUID:3c1fb294-9d15-11e0-85be-406186cbf87c +Team12Comp11UUID:4e00eaa0-9d15-11e0-85be-406186cbf87c +Team12Comp12UUID:a1f9ca46-9d15-11e0-85be-406186cbf87c +Team12Comp13UUID:1cd7dcc2-9d15-11e0-85be-406186cbf87c +Team12Comp14UUID:5b14f704-9d15-11e0-85be-406186cbf87c +Team12Comp15UUID:5f520bea-9d15-11e0-85be-406186cbf87c +Team12Comp16UUID:86d2fb5c-9d15-11e0-85be-406186cbf87c +Team12Comp17UUID:13e98db8-9d15-11e0-85be-406186cbf87c +Team12Comp18UUID:21584926-9d15-11e0-85be-406186cbf87c +Team12Comp19UUID:450c5a92-9d15-11e0-85be-406186cbf87c +Team12Comp20UUID:8b618332-9d15-11e0-85be-406186cbf87c +Team12Comp21UUID:75325a1e-9d15-11e0-85be-406186cbf87c +Team12Comp22UUID:37b2223c-9d15-11e0-85be-406186cbf87c +Team12Comp23UUID:062ea47e-9d15-11e0-85be-406186cbf87c +Team12Comp24UUID:2a8fbab0-9d15-11e0-85be-406186cbf87c +Team12Comp25UUID:56b22d94-9d15-11e0-85be-406186cbf87c +Team12Comp26UUID:63855546-9d15-11e0-85be-406186cbf87c +Team12Comp27UUID:25ea8cce-9d15-11e0-85be-406186cbf87c +Team12Comp28UUID:68315036-9d15-11e0-85be-406186cbf87c +Team12Comp29UUID:aa852246-9d15-11e0-85be-406186cbf87c +Team12Comp30UUID:0f4448e8-9d15-11e0-85be-406186cbf87c +Team12Comp31UUID:1859e280-9d15-11e0-85be-406186cbf87c + +Team13UUID:be35f1ee-996e-11e0-85be-406186cbf87c +Team13Name:GRE + +Team14UUID:c2b1d6a0-9934-11e0-85be-406186cbf87c +Team14Name:HUN + +Team15UUID:c3601ba4-996e-11e0-85be-406186cbf87c +Team15Name:IRL + +Team16UUID:c818eb6a-9934-11e0-85be-406186cbf87c +Team16Name:ISV + +Team17UUID:cd2435f6-9934-11e0-85be-406186cbf87c +Team17Name:ITA + +Team18UUID:d737d124-9934-11e0-85be-406186cbf87c +Team18Name:JPN + +Team19UUID:c8a808d8-996e-11e0-85be-406186cbf87c +Team19Name:MEX + +Team20UUID:cdaadaae-996e-11e0-85be-406186cbf87c +Team20Name:NED + +Team21UUID:dc76ea44-9934-11e0-85be-406186cbf87c +Team21Name:NOR + +Team22UUID:1e8f7452-9c49-11e0-85be-406186cbf87c +Team22Name:NZL + +Team23UUID:d3297b7a-996e-11e0-85be-406186cbf87c +Team23Name:POL + +Team24UUID:7d9ca8fa-9930-11e0-85be-406186cbf87c +Team24Name:RUS + +Team25UUID:e6510b62-9934-11e0-85be-406186cbf87c +Team25Name:SUI + +Team26UUID:eb727d9c-9934-11e0-85be-406186cbf87c +Team26Name:SWE +Team26Comp1UUID:aec14cd6-9d15-11e0-85be-406186cbf87c + +Team27UUID:ffb184b0-98e4-11e0-85be-406186cbf87c +Team27Name:Test + +Team28UUID:bc22780c-9d77-11e0-85be-406186cbf87c +Team28Name:UAE + +Team29UUID:f57e7ea8-9934-11e0-85be-406186cbf87c +Team29Name:UKR + +Team30UUID:0b5469f6-996f-11e0-85be-406186cbf87c +Team30Name:URU + +Team31UUID:9361c468-9930-11e0-85be-406186cbf87c +Team31Name:USA +SELECT Competitors.UUID,FirstName,LastName,Initials,Color,Nationality,Description,Icon,Picture,ClassId,Handicap,HandicapDistance,StartTime FROM event_20110609_KielerWoch.RaceObject INNER JOIN event_20110609_KielerWoch.Competitors ON event_20110609_KielerWoch.RaceObject.TrackedObjectId = event_20110609_KielerWoch.Competitors.UUID WHERE RaceId='e876c3a0-9da8-11e0-85be-406186cbf87c' ORDER BY LID,StartTime,Competitors.UUID + + +Comp1UUID:de7d2ba8-9d14-11e0-85be-406186cbf87c +Comp1Name:Kevin +Comp1NameShort:AUS9061 +Comp1Color:FF0000 +Comp1Nationality:AUS +Comp1Description: +Comp1Handicap:1.0000000000 +Comp1HandicapDistance:0.00000 +Comp1StartTime:2011-06-23 15:13:00 +Comp1PictureUUID:6d332670-9934-11e0-85be-406186cbf87c +Comp1ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp1RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp2UUID:e3587876-9d14-11e0-85be-406186cbf87c +Comp2Name:van Wonterghem +Comp2NameShort:DEN8828 +Comp2Color:808000 +Comp2Nationality:DEN +Comp2Description: +Comp2Handicap:1.0000000000 +Comp2HandicapDistance:0.00000 +Comp2StartTime:2011-06-23 15:13:00 +Comp2PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp2ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp2RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp3UUID:e7bc96f4-9d14-11e0-85be-406186cbf87c +Comp3Name:Buhl +Comp3NameShort:DEN8958 +Comp3Color:FFFFFF +Comp3Nationality:DEN +Comp3Description: +Comp3Handicap:1.0000000000 +Comp3HandicapDistance:0.00000 +Comp3StartTime:2011-06-23 15:13:00 +Comp3PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp3ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp3RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp4UUID:ec336744-9d14-11e0-85be-406186cbf87c +Comp4Name:Saugmann +Comp4NameShort:DEN8964 +Comp4Color:FFFF00 +Comp4Nationality:DEN +Comp4Description: +Comp4Handicap:1.0000000000 +Comp4HandicapDistance:0.00000 +Comp4StartTime:2011-06-23 15:13:00 +Comp4PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp4ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp4RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp5UUID:f0c9604c-9d14-11e0-85be-406186cbf87c +Comp5Name:Bøjland +Comp5NameShort:DEN9023 +Comp5Color:800080 +Comp5Nationality:DEN +Comp5Description: +Comp5Handicap:1.0000000000 +Comp5HandicapDistance:0.00000 +Comp5StartTime:2011-06-23 15:13:00 +Comp5PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp5ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp5RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp6UUID:f50675be-9d14-11e0-85be-406186cbf87c +Comp6Name:Broise +Comp6NameShort:FRA8844 +Comp6Color:008080 +Comp6Nationality:FRA +Comp6Description: +Comp6Handicap:1.0000000000 +Comp6HandicapDistance:0.00000 +Comp6StartTime:2011-06-23 15:13:00 +Comp6PictureUUID:5222c1c6-996e-11e0-85be-406186cbf87c +Comp6ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp6RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp7UUID:f969da74-9d14-11e0-85be-406186cbf87c +Comp7Name:boite +Comp7NameShort:FRA8902 +Comp7Color:800000 +Comp7Nationality:FRA +Comp7Description: +Comp7Handicap:1.0000000000 +Comp7HandicapDistance:0.00000 +Comp7StartTime:2011-06-23 15:13:00 +Comp7PictureUUID:5222c1c6-996e-11e0-85be-406186cbf87c +Comp7ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp7RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp8UUID:fda897ba-9d14-11e0-85be-406186cbf87c +Comp8Name:de Lisle +Comp8NameShort:FRA9068 +Comp8Color:000080 +Comp8Nationality:FRA +Comp8Description: +Comp8Handicap:1.0000000000 +Comp8HandicapDistance:0.00000 +Comp8StartTime:2011-06-23 15:13:00 +Comp8PictureUUID:5222c1c6-996e-11e0-85be-406186cbf87c +Comp8ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp8RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp9UUID:062ea47e-9d15-11e0-85be-406186cbf87c +Comp9Name:Menge +Comp9NameShort:GER7861 +Comp9Color:000000 +Comp9Nationality:GER +Comp9Description: +Comp9Handicap:1.0000000000 +Comp9HandicapDistance:0.00000 +Comp9StartTime:2011-06-23 15:13:00 +Comp9PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp9ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp9RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp10UUID:0a917a14-9d15-11e0-85be-406186cbf87c +Comp10Name:Barop +Comp10NameShort:GER8392 +Comp10Color:0000FF +Comp10Nationality:GER +Comp10Description: +Comp10Handicap:1.0000000000 +Comp10HandicapDistance:0.00000 +Comp10StartTime:2011-06-23 15:13:00 +Comp10PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp10ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp10RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp11UUID:0f4448e8-9d15-11e0-85be-406186cbf87c +Comp11Name:Steffen +Comp11NameShort:GER8437 +Comp11Color:FF00FF +Comp11Nationality:GER +Comp11Description: +Comp11Handicap:1.0000000000 +Comp11HandicapDistance:0.00000 +Comp11StartTime:2011-06-23 15:13:00 +Comp11PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp11ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp11RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp12UUID:13e98db8-9d15-11e0-85be-406186cbf87c +Comp12Name:Katja +Comp12NameShort:GER8500 +Comp12Color:008000 +Comp12Nationality:GER +Comp12Description: +Comp12Handicap:1.0000000000 +Comp12HandicapDistance:0.00000 +Comp12StartTime:2011-06-23 15:13:00 +Comp12PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp12ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp12RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp13UUID:1859e280-9d15-11e0-85be-406186cbf87c +Comp13Name:von Nerée +Comp13NameShort:GER8522 +Comp13Color:00FF00 +Comp13Nationality:GER +Comp13Description: +Comp13Handicap:1.0000000000 +Comp13HandicapDistance:0.00000 +Comp13StartTime:2011-06-23 15:13:00 +Comp13PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp13ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp13RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp14UUID:1cd7dcc2-9d15-11e0-85be-406186cbf87c +Comp14Name:Goedeking +Comp14NameShort:GER8622 +Comp14Color:FF0000 +Comp14Nationality:GER +Comp14Description: +Comp14Handicap:1.0000000000 +Comp14HandicapDistance:0.00000 +Comp14StartTime:2011-06-23 15:13:00 +Comp14PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp14ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp14RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp15UUID:21584926-9d15-11e0-85be-406186cbf87c +Comp15Name:Kellner +Comp15NameShort:GER8651 +Comp15Color:808000 +Comp15Nationality:GER +Comp15Description: +Comp15Handicap:1.0000000000 +Comp15HandicapDistance:0.00000 +Comp15StartTime:2011-06-23 15:13:00 +Comp15PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp15ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp15RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp16UUID:25ea8cce-9d15-11e0-85be-406186cbf87c +Comp16Name:Reincke +Comp16NameShort:GER8656 +Comp16Color:FFFFFF +Comp16Nationality:GER +Comp16Description: +Comp16Handicap:1.0000000000 +Comp16HandicapDistance:0.00000 +Comp16StartTime:2011-06-23 15:13:00 +Comp16PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp16ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp16RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp17UUID:2a8fbab0-9d15-11e0-85be-406186cbf87c +Comp17Name:Neulen +Comp17NameShort:GER8657 +Comp17Color:FFFF00 +Comp17Nationality:GER +Comp17Description: +Comp17Handicap:1.0000000000 +Comp17HandicapDistance:0.00000 +Comp17StartTime:2011-06-23 15:13:00 +Comp17PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp17ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp17RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp18UUID:37b2223c-9d15-11e0-85be-406186cbf87c +Comp18Name:Lietz +Comp18NameShort:GER8721 +Comp18Color:800000 +Comp18Nationality:GER +Comp18Description: +Comp18Handicap:1.0000000000 +Comp18HandicapDistance:0.00000 +Comp18StartTime:2011-06-23 15:13:00 +Comp18PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp18ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp18RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp19UUID:3c1fb294-9d15-11e0-85be-406186cbf87c +Comp19Name:Feldmann +Comp19NameShort:GER8730 +Comp19Color:000080 +Comp19Nationality:GER +Comp19Description: +Comp19Handicap:1.0000000000 +Comp19HandicapDistance:0.00000 +Comp19StartTime:2011-06-23 15:13:00 +Comp19PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp19ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp19RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp20UUID:40bd6314-9d15-11e0-85be-406186cbf87c +Comp20Name:Anton +Comp20NameShort:GER8741 +Comp20Color:00FFFF +Comp20Nationality:GER +Comp20Description: +Comp20Handicap:1.0000000000 +Comp20HandicapDistance:0.00000 +Comp20StartTime:2011-06-23 15:13:00 +Comp20PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp20ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp20RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp21UUID:450c5a92-9d15-11e0-85be-406186cbf87c +Comp21Name:Kraft +Comp21NameShort:GER8843 +Comp21Color:000000 +Comp21Nationality:GER +Comp21Description: +Comp21Handicap:1.0000000000 +Comp21HandicapDistance:0.00000 +Comp21StartTime:2011-06-23 15:13:00 +Comp21PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp21ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp21RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp22UUID:498b40ec-9d15-11e0-85be-406186cbf87c +Comp22Name:Brill +Comp22NameShort:GER8874 +Comp22Color:0000FF +Comp22Nationality:GER +Comp22Description: +Comp22Handicap:1.0000000000 +Comp22HandicapDistance:0.00000 +Comp22StartTime:2011-06-23 15:13:00 +Comp22PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp22ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp22RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp23UUID:4e00eaa0-9d15-11e0-85be-406186cbf87c +Comp23Name:Findel +Comp23NameShort:GER8875 +Comp23Color:FF00FF +Comp23Nationality:GER +Comp23Description: +Comp23Handicap:1.0000000000 +Comp23HandicapDistance:0.00000 +Comp23StartTime:2011-06-23 15:13:00 +Comp23PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp23ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp23RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp24UUID:56b22d94-9d15-11e0-85be-406186cbf87c +Comp24Name:Pleßmann +Comp24NameShort:GER8927 +Comp24Color:00FF00 +Comp24Nationality:GER +Comp24Description: +Comp24Handicap:1.0000000000 +Comp24HandicapDistance:0.00000 +Comp24StartTime:2011-06-23 15:13:00 +Comp24PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp24ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp24RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp25UUID:5b14f704-9d15-11e0-85be-406186cbf87c +Comp25Name:Gosch +Comp25NameShort:GER8929 +Comp25Color:FF0000 +Comp25Nationality:GER +Comp25Description: +Comp25Handicap:1.0000000000 +Comp25HandicapDistance:0.00000 +Comp25StartTime:2011-06-23 15:13:00 +Comp25PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp25ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp25RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp26UUID:5f520bea-9d15-11e0-85be-406186cbf87c +Comp26Name:Henge +Comp26NameShort:GER8932 +Comp26Color:808000 +Comp26Nationality:GER +Comp26Description: +Comp26Handicap:1.0000000000 +Comp26HandicapDistance:0.00000 +Comp26StartTime:2011-06-23 15:13:00 +Comp26PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp26ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp26RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp27UUID:63855546-9d15-11e0-85be-406186cbf87c +Comp27Name:Rasenack +Comp27NameShort:GER8956 +Comp27Color:FFFFFF +Comp27Nationality:GER +Comp27Description: +Comp27Handicap:1.0000000000 +Comp27HandicapDistance:0.00000 +Comp27StartTime:2011-06-23 15:13:00 +Comp27PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp27ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp27RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp28UUID:68315036-9d15-11e0-85be-406186cbf87c +Comp28Name:Roos +Comp28NameShort:GER8972 +Comp28Color:FFFF00 +Comp28Nationality:GER +Comp28Description: +Comp28Handicap:1.0000000000 +Comp28HandicapDistance:0.00000 +Comp28StartTime:2011-06-23 15:13:00 +Comp28PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp28ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp28RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp29UUID:70eb542e-9d15-11e0-85be-406186cbf87c +Comp29Name:Achterberg +Comp29NameShort:GER8990 +Comp29Color:008080 +Comp29Nationality:GER +Comp29Description: +Comp29Handicap:1.0000000000 +Comp29HandicapDistance:0.00000 +Comp29StartTime:2011-06-23 15:13:00 +Comp29PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp29ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp29RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp30UUID:75325a1e-9d15-11e0-85be-406186cbf87c +Comp30Name:Lehmann +Comp30NameShort:GER8992 +Comp30Color:800000 +Comp30Nationality:GER +Comp30Description: +Comp30Handicap:1.0000000000 +Comp30HandicapDistance:0.00000 +Comp30StartTime:2011-06-23 15:13:00 +Comp30PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp30ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp30RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp31UUID:7e051528-9d15-11e0-85be-406186cbf87c +Comp31Name:Böger +Comp31NameShort:GER902 +Comp31Color:00FFFF +Comp31Nationality:GER +Comp31Description: +Comp31Handicap:1.0000000000 +Comp31HandicapDistance:0.00000 +Comp31StartTime:2011-06-23 15:13:00 +Comp31PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp31ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp31RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp32UUID:826bdcb4-9d15-11e0-85be-406186cbf87c +Comp32Name:Dr.Plattner +Comp32NameShort:GER9026 +Comp32Color:000000 +Comp32Nationality:GER +Comp32Description: +Comp32Handicap:1.0000000000 +Comp32HandicapDistance:0.00000 +Comp32StartTime:2011-06-23 15:13:00 +Comp32PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp32ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp32RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp33UUID:86d2fb5c-9d15-11e0-85be-406186cbf87c +Comp33Name:Hunger +Comp33NameShort:GER9027 +Comp33Color:0000FF +Comp33Nationality:GER +Comp33Description: +Comp33Handicap:1.0000000000 +Comp33HandicapDistance:0.00000 +Comp33StartTime:2011-06-23 15:13:00 +Comp33PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp33ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp33RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp34UUID:8b618332-9d15-11e0-85be-406186cbf87c +Comp34Name:Köchlin +Comp34NameShort:GER9028 +Comp34Color:FF00FF +Comp34Nationality:GER +Comp34Description: +Comp34Handicap:1.0000000000 +Comp34HandicapDistance:0.00000 +Comp34StartTime:2011-06-23 15:13:00 +Comp34PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp34ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp34RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp35UUID:949b11b6-9d15-11e0-85be-406186cbf87c +Comp35Name:Böhm +Comp35NameShort:GER9035 +Comp35Color:00FF00 +Comp35Nationality:GER +Comp35Description: +Comp35Handicap:1.0000000000 +Comp35HandicapDistance:0.00000 +Comp35StartTime:2011-06-23 15:13:00 +Comp35PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp35ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp35RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp36UUID:991ab354-9d15-11e0-85be-406186cbf87c +Comp36Name:Dasenbrook +Comp36NameShort:GER9040 +Comp36Color:FF0000 +Comp36Nationality:GER +Comp36Description: +Comp36Handicap:1.0000000000 +Comp36HandicapDistance:0.00000 +Comp36StartTime:2011-06-23 15:13:00 +Comp36PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp36ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp36RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp37UUID:9d4e23d4-9d15-11e0-85be-406186cbf87c +Comp37Name:Birkner +Comp37NameShort:GER9043 +Comp37Color:808000 +Comp37Nationality:GER +Comp37Description: +Comp37Handicap:1.0000000000 +Comp37HandicapDistance:0.00000 +Comp37StartTime:2011-06-23 15:13:00 +Comp37PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp37ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp37RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp38UUID:a1f9ca46-9d15-11e0-85be-406186cbf87c +Comp38Name:Fischer +Comp38NameShort:GER9054 +Comp38Color:FFFFFF +Comp38Nationality:GER +Comp38Description: +Comp38Handicap:1.0000000000 +Comp38HandicapDistance:0.00000 +Comp38StartTime:2011-06-23 15:13:00 +Comp38PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp38ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp38RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp39UUID:aa852246-9d15-11e0-85be-406186cbf87c +Comp39Name:Schomäker +Comp39NameShort:GER9070 +Comp39Color:800080 +Comp39Nationality:GER +Comp39Description: +Comp39Handicap:1.0000000000 +Comp39HandicapDistance:0.00000 +Comp39StartTime:2011-06-23 15:13:00 +Comp39PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp39ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp39RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +Comp40UUID:aec14cd6-9d15-11e0-85be-406186cbf87c +Comp40Name:Hastenpflug +Comp40NameShort:SWE8912 +Comp40Color:008080 +Comp40Nationality:SWE +Comp40Description: +Comp40Handicap:1.0000000000 +Comp40HandicapDistance:0.00000 +Comp40StartTime:2011-06-23 15:13:00 +Comp40PictureUUID:ebb4c5da-9934-11e0-85be-406186cbf87c +Comp40ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp40RouteUUID:ab327b10-9da8-11e0-85be-406186cbf87c + +ControlPoint1UUID:07d84022-9951-11e0-85be-406186cbf87c +ControlPoint1Name:D Mark4 +ControlPoint1HasTwoPoints:1 +ControlPoint1ShortName:4 +ControlPoint1Mark1Lon:10.169383 +ControlPoint1Mark1Lat:54.428738 +ControlPoint1Mark2Lon:10.169408 +ControlPoint1Mark2Lat:54.428738 + +ControlPoint2UUID:0807af7c-9b42-11e0-85be-406186cbf87c +ControlPoint2Name:Star-R6-1 +ControlPoint2HasTwoPoints:0 +ControlPoint2ShortName:1 +ControlPoint2Mark1Lon:10.235563 +ControlPoint2Mark1Lat:54.504244 + +ControlPoint3UUID:0beb273c-992f-11e0-85be-406186cbf87c +ControlPoint3Name:Finish Silbernes Band +ControlPoint3HasTwoPoints:1 +ControlPoint3ShortName:F +ControlPoint3Mark1Lon:10.000465 +ControlPoint3Mark1Lat:54.273018 +ControlPoint3Mark2Lon:10.273137 +ControlPoint3Mark2Lat:54.500737 + +ControlPoint4UUID:144f44f4-9dac-11e0-85be-406186cbf87c +ControlPoint4Name:SB-Mark H5 (y) (WP 33) +ControlPoint4HasTwoPoints:0 +ControlPoint4ShortName:H5 +ControlPoint4Mark1Lon:10.666667 +ControlPoint4Mark1Lat:54.548333 + +ControlPoint5UUID:18e98784-9930-11e0-85be-406186cbf87c +ControlPoint5Name:Start Silbernes Band +ControlPoint5HasTwoPoints:1 +ControlPoint5ShortName:S +ControlPoint5Mark1Lon:10.172258 +ControlPoint5Mark1Lat:54.436310 +ControlPoint5Mark2Lon:10.172103 +ControlPoint5Mark2Lat:54.436035 + +ControlPoint6UUID:1d8536e6-9a91-11e0-85be-406186cbf87c +ControlPoint6Name:Star1-4 +ControlPoint6HasTwoPoints:1 +ControlPoint6ShortName:4 +ControlPoint6Mark1Lon:10.219525 +ControlPoint6Mark1Lat:54.500618 +ControlPoint6Mark2Lon:10.220421 +ControlPoint6Mark2Lat:54.500744 + +ControlPoint7UUID:22475d30-9951-11e0-85be-406186cbf87c +ControlPoint7Name:Audi Start +ControlPoint7HasTwoPoints:1 +ControlPoint7ShortName:S +ControlPoint7Mark1Lon:10.169497 +ControlPoint7Mark1Lat:54.428780 +ControlPoint7Mark2Lon:10.168688 +ControlPoint7Mark2Lat:54.346428 + +ControlPoint8UUID:2307bec6-992f-11e0-85be-406186cbf87c +ControlPoint8Name:Mark KO2 red/white +ControlPoint8HasTwoPoints:0 +ControlPoint8ShortName:KO +ControlPoint8Mark1Lon:10.450000 +ControlPoint8Mark1Lat:54.533333 + +ControlPoint9UUID:2497f822-9ab1-11e0-85be-406186cbf87c +ControlPoint9Name:49Y-R7-1 +ControlPoint9HasTwoPoints:0 +ControlPoint9ShortName:1 +ControlPoint9Mark1Lon:10.207426 +ControlPoint9Mark1Lat:54.435751 + +ControlPoint10UUID:27c13096-9a8d-11e0-85be-406186cbf87c +ControlPoint10Name:49b4-4 +ControlPoint10HasTwoPoints:1 +ControlPoint10ShortName:4 +ControlPoint10Mark1Lon:10.208914 +ControlPoint10Mark1Lat:54.445496 +ControlPoint10Mark2Lon:10.209630 +ControlPoint10Mark2Lat:54.444822 + +ControlPoint11UUID:2c8584ca-9dac-11e0-85be-406186cbf87c +ControlPoint11Name:SB-Finish (Ziel) at Kiel Lighthouse +ControlPoint11HasTwoPoints:0 +ControlPoint11ShortName:KL +ControlPoint11Mark1Lon:10.278333 +ControlPoint11Mark1Lat:54.498333 + +ControlPoint12UUID:32ad8a14-992e-11e0-85be-406186cbf87c +ControlPoint12Name:Mark 14 red (Fahrwassertonne 14) +ControlPoint12HasTwoPoints:0 +ControlPoint12ShortName:14 +ControlPoint12Mark1Lon:10.185000 +ControlPoint12Mark1Lat:54.371667 + +ControlPoint13UUID:32fb2850-9951-11e0-85be-406186cbf87c +ControlPoint13Name:Audi Mark1 +ControlPoint13HasTwoPoints:0 +ControlPoint13ShortName:1 +ControlPoint13Mark1Lon:10.169377 +ControlPoint13Mark1Lat:54.428920 + +ControlPoint14UUID:34feeaea-9c32-11e0-85be-406186cbf87c +ControlPoint14Name:Star7-1a +ControlPoint14HasTwoPoints:0 +ControlPoint14ShortName:1a +ControlPoint14Mark1Lon:10.230608 +ControlPoint14Mark1Lat:54.508407 + +ControlPoint15UUID:3ecf9ade-992f-11e0-85be-406186cbf87c +ControlPoint15Name:Schlei Safe Water Mark red/white +ControlPoint15HasTwoPoints:0 +ControlPoint15ShortName:SS +ControlPoint15Mark1Lon:10.055000 +ControlPoint15Mark1Lat:54.668333 + +ControlPoint16UUID:405f346c-99e9-11e0-85be-406186cbf87c +ControlPoint16Name:49erYR2-1 +ControlPoint16HasTwoPoints:0 +ControlPoint16ShortName:1 +ControlPoint16Mark1Lon:10.200182 +ControlPoint16Mark1Lat:54.427670 + +ControlPoint17UUID:4123b6ea-9951-11e0-85be-406186cbf87c +ControlPoint17Name:Audi Mark2 +ControlPoint17HasTwoPoints:0 +ControlPoint17ShortName:2 +ControlPoint17Mark1Lon:10.169438 +ControlPoint17Mark1Lat:54.428952 + +ControlPoint18UUID:4bac3eca-992e-11e0-85be-406186cbf87c +ControlPoint18Name:Three yellow marks* (3 gelbe Tonnen) +ControlPoint18HasTwoPoints:0 +ControlPoint18ShortName:3y +ControlPoint18Mark1Lon:10.198333 +ControlPoint18Mark1Lat:54.386667 + +ControlPoint19UUID:535fb3f4-9951-11e0-85be-406186cbf87c +ControlPoint19Name:Audi Finish +ControlPoint19HasTwoPoints:1 +ControlPoint19ShortName:F +ControlPoint19Mark1Lon:10.159173 +ControlPoint19Mark1Lat:54.341737 +ControlPoint19Mark2Lon:10.169527 +ControlPoint19Mark2Lat:54.429047 + +ControlPoint20UUID:56f63cf8-99e8-11e0-85be-406186cbf87c +ControlPoint20Name:49erYR1-T +ControlPoint20HasTwoPoints:0 +ControlPoint20ShortName:T +ControlPoint20Mark1Lon:10.199148 +ControlPoint20Mark1Lat:54.429370 + +ControlPoint21UUID:5933cdca-9950-11e0-85be-406186cbf87c +ControlPoint21Name:H Start +ControlPoint21HasTwoPoints:1 +ControlPoint21ShortName:S +ControlPoint21Mark1Lon:10.169343 +ControlPoint21Mark1Lat:54.428722 +ControlPoint21Mark2Lon:10.169463 +ControlPoint21Mark2Lat:54.428802 + +ControlPoint22UUID:65b2dfbc-9b64-11e0-85be-406186cbf87c +ControlPoint22Name:K Start +ControlPoint22HasTwoPoints:1 +ControlPoint22ShortName:S +ControlPoint22Mark1Lon:10.169233 +ControlPoint22Mark1Lat:54.429363 +ControlPoint22Mark2Lon:10.169218 +ControlPoint22Mark2Lat:54.429263 + +ControlPoint23UUID:65c3a9ec-992e-11e0-85be-406186cbf87c +ControlPoint23Name:Mark 4 red (Fahrwassertonne 4) +ControlPoint23HasTwoPoints:0 +ControlPoint23ShortName:4 +ControlPoint23Mark1Lon:10.233333 +ControlPoint23Mark1Lat:54.428333 + +ControlPoint24UUID:69a347b2-9950-11e0-85be-406186cbf87c +ControlPoint24Name:H Mark1 +ControlPoint24HasTwoPoints:0 +ControlPoint24ShortName:1 +ControlPoint24Mark1Lon:10.169395 +ControlPoint24Mark1Lat:54.428688 + +ControlPoint25UUID:6c30e118-9a8d-11e0-85be-406186cbf87c +ControlPoint25Name:49b4-F +ControlPoint25HasTwoPoints:1 +ControlPoint25ShortName:F +ControlPoint25Mark1Lon:10.209712 +ControlPoint25Mark1Lat:54.445952 +ControlPoint25Mark2Lon:10.210658 +ControlPoint25Mark2Lat:54.445241 + +ControlPoint26UUID:7634bbfe-9dac-11e0-85be-406186cbf87c +ControlPoint26Name:SB-Mark Red (WP 124) +ControlPoint26HasTwoPoints:0 +ControlPoint26ShortName:WP +ControlPoint26Mark1Lon:10.236667 +ControlPoint26Mark1Lat:55.045000 + +ControlPoint27UUID:79dab2ea-9aaf-11e0-85be-406186cbf87c +ControlPoint27Name:49erB5-4 +ControlPoint27HasTwoPoints:1 +ControlPoint27ShortName:4 +ControlPoint27Mark1Lon:10.201355 +ControlPoint27Mark1Lat:54.446073 +ControlPoint27Mark2Lon:10.197830 +ControlPoint27Mark2Lat:54.446886 + +ControlPoint28UUID:7a218886-9b64-11e0-85be-406186cbf87c +ControlPoint28Name:K Mark1 +ControlPoint28HasTwoPoints:0 +ControlPoint28ShortName:1 +ControlPoint28Mark1Lon:10.169215 +ControlPoint28Mark1Lat:54.429115 + +ControlPoint29UUID:7c639ea8-9da9-11e0-85be-406186cbf87c +ControlPoint29Name:SB Mark1 (Schleimünde Approach (r/w) ) +ControlPoint29HasTwoPoints:0 +ControlPoint29ShortName:1 +ControlPoint29Mark1Lon:10.060000 +ControlPoint29Mark1Lat:54.665000 + +ControlPoint30UUID:8145342e-992e-11e0-85be-406186cbf87c +ControlPoint30Name:Mark 2 red (Fahrwassertonne 2) +ControlPoint30HasTwoPoints:0 +ControlPoint30ShortName:2 +ControlPoint30Mark1Lon:10.283333 +ControlPoint30Mark1Lat:54.465000 + +ControlPoint31UUID:84009c94-98e3-11e0-85be-406186cbf87c +ControlPoint31Name:Test - Top Mark +ControlPoint31HasTwoPoints:0 +ControlPoint31ShortName:1 +ControlPoint31Mark1Lon:10.169028 +ControlPoint31Mark1Lat:54.428993 + +ControlPoint32UUID:86c90a6e-9a9c-11e0-85be-406186cbf87c +ControlPoint32Name:S3-4 +ControlPoint32HasTwoPoints:1 +ControlPoint32ShortName:4 +ControlPoint32Mark1Lon:10.218663 +ControlPoint32Mark1Lat:54.515525 +ControlPoint32Mark2Lon:10.218214 +ControlPoint32Mark2Lat:54.515649 + +ControlPoint33UUID:8d1c4750-9b64-11e0-85be-406186cbf87c +ControlPoint33Name:K Mark2 +ControlPoint33HasTwoPoints:0 +ControlPoint33ShortName:2 +ControlPoint33Mark1Lon:10.170753 +ControlPoint33Mark1Lat:54.429023 + +ControlPoint34UUID:99cae4d0-992e-11e0-85be-406186cbf87c +ControlPoint34Name:Mark 1 green (Fahrwassertonne 1) +ControlPoint34HasTwoPoints:0 +ControlPoint34ShortName:1 +ControlPoint34Mark1Lon:10.251667 +ControlPoint34Mark1Lat:54.475000 + +ControlPoint35UUID:9beca160-9a8f-11e0-85be-406186cbf87c +ControlPoint35Name:49erb1-1 +ControlPoint35HasTwoPoints:0 +ControlPoint35ShortName:T +ControlPoint35Mark1Lon:10.196778 +ControlPoint35Mark1Lat:54.439621 + +ControlPoint36UUID:9c4e4984-9a93-11e0-85be-406186cbf87c +ControlPoint36Name:Star2-4 +ControlPoint36HasTwoPoints:1 +ControlPoint36ShortName:4 +ControlPoint36Mark1Lon:10.230194 +ControlPoint36Mark1Lat:54.504793 +ControlPoint36Mark2Lon:10.229448 +ControlPoint36Mark2Lat:54.505033 + +ControlPoint37UUID:9caa2f3a-98e3-11e0-85be-406186cbf87c +ControlPoint37Name:Test - start +ControlPoint37HasTwoPoints:1 +ControlPoint37ShortName:S +ControlPoint37Mark1Lon:10.169440 +ControlPoint37Mark1Lat:54.428712 +ControlPoint37Mark2Lon:10.169403 +ControlPoint37Mark2Lat:54.428872 + +ControlPoint38UUID:9da911ea-9950-11e0-85be-406186cbf87c +ControlPoint38Name:H Mark4 +ControlPoint38HasTwoPoints:1 +ControlPoint38ShortName:4 +ControlPoint38Mark1Lon:10.199247 +ControlPoint38Mark1Lat:54.426952 +ControlPoint38Mark2Lon:10.169460 +ControlPoint38Mark2Lat:54.428810 + +ControlPoint39UUID:a0cbd40c-9dab-11e0-85be-406186cbf87c +ControlPoint39Name:SB-Mark Schleimünde Approach (r/w) +ControlPoint39HasTwoPoints:0 +ControlPoint39ShortName:SA +ControlPoint39Mark1Lon:10.060000 +ControlPoint39Mark1Lat:54.665000 + +ControlPoint40UUID:a68cc03e-9b64-11e0-85be-406186cbf87c +ControlPoint40Name:K Mark3 +ControlPoint40HasTwoPoints:1 +ControlPoint40ShortName:3 +ControlPoint40Mark1Lon:10.169493 +ControlPoint40Mark1Lat:54.428673 +ControlPoint40Mark2Lon:10.242970 +ControlPoint40Mark2Lat:54.475930 + +ControlPoint41UUID:a8c75c3e-9a8c-11e0-85be-406186cbf87c +ControlPoint41Name:49b4-Start +ControlPoint41HasTwoPoints:1 +ControlPoint41ShortName:S +ControlPoint41Mark1Lon:10.209545 +ControlPoint41Mark1Lat:54.446065 +ControlPoint41Mark2Lon:10.211504 +ControlPoint41Mark2Lat:54.444520 + +ControlPoint42UUID:a91da4ea-9dac-11e0-85be-406186cbf87c +ControlPoint42Name:SB-Mark Thurö Rev (Y/B) (WP 277) +ControlPoint42HasTwoPoints:0 +ControlPoint42ShortName:Th +ControlPoint42Mark1Lon:10.735000 +ControlPoint42Mark1Lat:55.020000 + +ControlPoint43UUID:b2ad1adc-9950-11e0-85be-406186cbf87c +ControlPoint43Name:H Finish +ControlPoint43HasTwoPoints:1 +ControlPoint43ShortName:F +ControlPoint43Mark1Lon:10.169410 +ControlPoint43Mark1Lat:54.428802 +ControlPoint43Mark2Lon:10.169568 +ControlPoint43Mark2Lat:54.428810 + +ControlPoint44UUID:b31eaad0-9dab-11e0-85be-406186cbf87c +ControlPoint44Name:SB-Mark Spodsbjerg-SE +ControlPoint44HasTwoPoints:0 +ControlPoint44ShortName:Sp +ControlPoint44Mark1Lon:10.845000 +ControlPoint44Mark1Lat:54.918333 + +ControlPoint45UUID:b3af1998-992e-11e0-85be-406186cbf87c +ControlPoint45Name:Mark K yellow (Kabeltonne) +ControlPoint45HasTwoPoints:0 +ControlPoint45ShortName:K +ControlPoint45Mark1Lon:10.255000 +ControlPoint45Mark1Lat:54.488333 + +ControlPoint46UUID:bfe4dc42-9b64-11e0-85be-406186cbf87c +ControlPoint46Name:K Mark4 +ControlPoint46HasTwoPoints:1 +ControlPoint46ShortName:4 +ControlPoint46Mark1Lon:10.169245 +ControlPoint46Mark1Lat:54.429253 +ControlPoint46Mark2Lon:10.169258 +ControlPoint46Mark2Lat:54.429408 + +ControlPoint47UUID:c2199936-9dac-11e0-85be-406186cbf87c +ControlPoint47Name:SB-Mark Black Yellow (WP 253) +ControlPoint47HasTwoPoints:0 +ControlPoint47ShortName:BY +ControlPoint47Mark1Lon:10.936667 +ControlPoint47Mark1Lat:55.185000 + +ControlPoint48UUID:c583e438-9950-11e0-85be-406186cbf87c +ControlPoint48Name:D Start +ControlPoint48HasTwoPoints:1 +ControlPoint48ShortName:S +ControlPoint48Mark1Lon:10.169380 +ControlPoint48Mark1Lat:54.428668 +ControlPoint48Mark2Lon:10.169187 +ControlPoint48Mark2Lat:54.428523 + +ControlPoint49UUID:cd57b5f8-992e-11e0-85be-406186cbf87c +ControlPoint49Name:Stollergrund-N (black/yellow) +ControlPoint49HasTwoPoints:0 +ControlPoint49ShortName:SN +ControlPoint49Mark1Lon:10.200000 +ControlPoint49Mark1Lat:54.536667 + +ControlPoint50UUID:d2b4c5d2-9950-11e0-85be-406186cbf87c +ControlPoint50Name:D Mark1 +ControlPoint50HasTwoPoints:0 +ControlPoint50ShortName:1 +ControlPoint50Mark1Lon:10.166928 +ControlPoint50Mark1Lat:54.345997 + +ControlPoint51UUID:de21cff2-9ab6-11e0-85be-406186cbf87c +ControlPoint51Name:49Y-R8-1a +ControlPoint51HasTwoPoints:0 +ControlPoint51ShortName:1 +ControlPoint51Mark1Lon:10.207476 +ControlPoint51Mark1Lat:54.435762 + +ControlPoint52UUID:e299f688-9dac-11e0-85be-406186cbf87c +ControlPoint52Name:SB-Gate DW 59 green and DW 56 red +ControlPoint52HasTwoPoints:0 +ControlPoint52ShortName:59 +ControlPoint52Mark1Lon:10.780000 +ControlPoint52Mark1Lat:54.736667 + +ControlPoint53UUID:e59815c6-9b64-11e0-85be-406186cbf87c +ControlPoint53Name:K Finish +ControlPoint53HasTwoPoints:1 +ControlPoint53ShortName:F +ControlPoint53Mark1Lon:10.170700 +ControlPoint53Mark1Lat:54.429200 +ControlPoint53Mark2Lon:10.170648 +ControlPoint53Mark2Lat:54.429165 + +ControlPoint54UUID:e663db6c-9a8c-11e0-85be-406186cbf87c +ControlPoint54Name:49b4-1 +ControlPoint54HasTwoPoints:0 +ControlPoint54ShortName:1 +ControlPoint54Mark1Lon:10.196146 +ControlPoint54Mark1Lat:54.441407 + +ControlPoint55UUID:f214ea50-992e-11e0-85be-406186cbf87c +ControlPoint55Name:Gate between Mittelgrund-N +ControlPoint55HasTwoPoints:0 +ControlPoint55ShortName:MN +ControlPoint55Mark1Lon:10.055000 +ControlPoint55Mark1Lat:54.513333 + +ControlPoint56UUID:f641d92e-9ab6-11e0-85be-406186cbf87c +ControlPoint56Name:49Y-R8-1b +ControlPoint56HasTwoPoints:0 +ControlPoint56ShortName:1 +ControlPoint56Mark1Lon:10.203712 +ControlPoint56Mark1Lat:54.434916 + +ControlPoint57UUID:f76bea2c-9a95-11e0-85be-406186cbf87c +ControlPoint57Name:DIM1-F +ControlPoint57HasTwoPoints:1 +ControlPoint57ShortName:F +ControlPoint57Mark1Lon:9.850912 +ControlPoint57Mark1Lat:54.474097 +ControlPoint57Mark2Lon:9.849851 +ControlPoint57Mark2Lat:54.472319 + + +SegmentFile:segments.php +Route1UUID:ab327b10-9da8-11e0-85be-406186cbf87c +Route1Description:5o5 Race 3 +Route1Length:0 +Route1ControlPoint1UUID:65b2dfbc-9b64-11e0-85be-406186cbf87c +Route1ControlPoint2UUID:7a218886-9b64-11e0-85be-406186cbf87c +Route1ControlPoint3UUID:bfe4dc42-9b64-11e0-85be-406186cbf87c +Route1ControlPoint4UUID:7a218886-9b64-11e0-85be-406186cbf87c +Route1ControlPoint5UUID:bfe4dc42-9b64-11e0-85be-406186cbf87c +Route1ControlPoint6UUID:e59815c6-9b64-11e0-85be-406186cbf87c + +//-------- AUTO GENERATED PARAMETERS END ------------------ diff --git a/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_race_4.mtb b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_race_4.mtb new file mode 100644 index 00000000000..ac73897c272 Binary files /dev/null and b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_race_4.mtb differ diff --git a/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_race_4.txt b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_race_4.txt new file mode 100644 index 00000000000..d21f271f239 --- /dev/null +++ b/java/com.sap.sailing.datamining.test/resources/event_20110609_KielerWoch-505_race_4.txt @@ -0,0 +1,1547 @@ +stored-uri:datafiles/event_20110609_KielerWoch-505_race_4.mtb +StartReplay:1 + +// Input from params_static: + +// Input from event_parameters: + +CompMarkerUUID:1 +CodanIsSponsor:0 +EnablePrint:1 +DefaultTailLength:60 +APzoomChangeTime:2500 +disableAutoProduce:0 +autoProduce:0 +APstartLineShowOfLeg:0.25 +APsideOfStartLine:0.30 +APstartSwitch:0.8 +APwholeLegBehindMarks:0.15 +APmarkSwitch:0.13 +APmarkZoomBehindMarks:0.10 +APmarkZoomInFrontOfMarks:0.20 +PopupMarkerUUID:2 +ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf +WeatherLat1:56 +WeatherLat2:54 +Marker1UUID:1 +Marker1Type:RGBMarkerFactory +Marker1Directional:1 +Marker1Image:markers/boat_FF0000_big.png +Marker1MetersPerPixel:0.0617 +Marker1MinScale:0.396 +Startlocation:10.197659,54.456129 +MaxDiffTime:600 +MaxMetersPerSecond:20 +PrecisionMeters:20 +ShortAvgTime:180 +LongAvgTime:900 +TimeZone:2 +CompMarkerTimeout:60 +FramesPerSecond:8 +replayspeedup:5 +MatchRaceClock:0 +MatchRaceClockNo:3 +MatchRaceClockSpacing:300 +Minimap:1 +MinimapUseLayers:1 +Minimapgeorect:9.840775,54.516538,10.3022,54.309554 +MinimapMarkerUUID:2 +Marker4UUID:2 +Marker4Type:RGBmarkerFactory +Marker4Directional:1 +Marker4Image:markers/boat_FF0000_small.png +MetersPerPixel:25 +MetersPerPixelMax:200 +MetersPerPixelMin:0.2 +isMatchRace:0 +weatherEnabled:0 +showRaceClock:1 +BackGroundImage:graphics/background.jpg +DisableRotation:0 +ControlPointMarkerRadius:13 +disableselection:0 +disableleaderline:0 +WeatherLon1:9 +WeatherLon2:12 +DisableChangeRace:0 +PopUpRow1Name: +PopUpRow1Type:Name +PopUpRow2Name:Knots +PopUpRow2Type:BoatSpeed +PopUpRow3Name:Ranking +PopUpRow3Type:RowNumber +PopUpRow4Name:Sail +PopUpRow4Type:Ini +PopUpRow5Name:After +PopUpRow5Type:BoatAfter +ResultColumn1Name:# +ResultColumn1Size:25 +ResultColumn1Type:RowNumber +ResultColumn2Size:20 +ResultColumn2Type:ColorMarker +ResultColumn3Name:sail +ResultColumn3Size:60 +ResultColumn3Type:Ini +ResultColumn4Name:name +ResultColumn4Size:110 +ResultColumn4Type:Name +ResultColumn5Name:after +ResultColumn5Size:45 +ResultColumn5Type:BoatAfter +ResultColumn6Name:kts +ResultColumn6Size:40 +ResultColumn6Type:BoatSpeed +DisableLeaderboard:0 +DisableAnalysis:0 +TableFontSize:11 +//live-uri:tcp://germanmaster.traclive.dk:4400 +//stored-uri:tcp://germanmaster.traclive.dk:4401 +compInitialsFontSize:11 +AutoRotate:0 +MassStart:0 +SegmentFile:segments.php +StartType:Mass +ReCalcDirAndSpeed:0 +RaceHandicapSystem:NONE +TeamCompetitionType:NONE +LiveDelaySecs: 180 + +//-------- AUTO GENERATED PARAMETERS START ---------------- + +EventID:f34b232a-934e-11e0-b3bb-406186cbf87c +EventName:Kieler Woche +EventDB:event_20110609_KielerWoch +EventStartTime:2011-06-15 09:00:00 +EventEndTime:2011-06-26 23:59:00 + +RaceID:7c666e50-9dde-11e0-85be-406186cbf87c +RaceName:505 race 4 +RaceStartTime:2011-06-24 09:15:00 +RaceEndTime: +RaceTrackingStartTime:2011-06-24 08:39:54 +RaceTrackingEndTime:2011-06-24 10:47:32 +RaceDefaultRouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c +RaceHandicapSystem:NONE + +Image1UUID:0ba089bc-996f-11e0-85be-406186cbf87c +Image1Type:Logo +Image1Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/URU.gif +Image1Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/URU.gif + +Image2UUID:1d548b00-996e-11e0-85be-406186cbf87c +Image2Type:Logo +Image2Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif +Image2Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif + +Image3UUID:1ece41fa-9c49-11e0-85be-406186cbf87c +Image3Type:Logo +Image3Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif +Image3Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif + +Image4UUID:22b1a988-98e3-11e0-85be-406186cbf87c +Image4Type:Logo +Image4Description:Veolia +Image4Image: + +Image5UUID:22ba0304-98d8-11e0-85be-406186cbf87c +Image5Type:Logo +Image5Description:Kieler Woche +Image5Image: + +Image6UUID:2767b7e8-996e-11e0-85be-406186cbf87c +Image6Type:Logo +Image6Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BEL.gif +Image6Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BEL.gif + +Image7UUID:2cda6f4a-996e-11e0-85be-406186cbf87c +Image7Type:Logo +Image7Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BRA.gif +Image7Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BRA.gif + +Image8UUID:3313b588-98d8-11e0-85be-406186cbf87c +Image8Type:Logo +Image8Description:Audi +Image8Image: + +Image9UUID:33a26700-98e3-11e0-85be-406186cbf87c +Image9Type:Logo +Image9Description:HSH NORDBANK +Image9Image: + +Image10UUID:373009a0-996e-11e0-85be-406186cbf87c +Image10Type:Logo +Image10Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CHI.gif +Image10Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CHI.gif + +Image11UUID:4cc5360a-996e-11e0-85be-406186cbf87c +Image11Type:Logo +Image11Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/EST.gif +Image11Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/EST.gif + +Image12UUID:514008f8-98e3-11e0-85be-406186cbf87c +Image12Type:Logo +Image12Description:ISAF SAILING WORLDCUP +Image12Image: + +Image13UUID:5222c1c6-996e-11e0-85be-406186cbf87c +Image13Type:Logo +Image13Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FRA.gif +Image13Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FRA.gif + +Image14UUID:574e0d40-98d8-11e0-85be-406186cbf87c +Image14Type:Logo +Image14Description:SAP +Image14Image: + +Image15UUID:617e8236-996e-11e0-85be-406186cbf87c +Image15Type:Logo +Image15Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GBR.gif +Image15Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GBR.gif + +Image16UUID:6d332670-9934-11e0-85be-406186cbf87c +Image16Type:Logo +Image16Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUS.gif +Image16Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUS.gif + +Image17UUID:6de07500-9e2a-11e0-85be-406186cbf87c +Image17Type:Logo +Image17Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SIN.gif +Image17Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SIN.gif + +Image18UUID:7252c142-9934-11e0-85be-406186cbf87c +Image18Type:Logo +Image18Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CAN.gif +Image18Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CAN.gif + +Image19UUID:77555f2e-9934-11e0-85be-406186cbf87c +Image19Type:Logo +Image19Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CRO.gif +Image19Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CRO.gif + +Image20UUID:7c87105a-9934-11e0-85be-406186cbf87c +Image20Type:Logo +Image20Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif +Image20Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif + +Image21UUID:7deb67ce-9930-11e0-85be-406186cbf87c +Image21Type:Logo +Image21Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RUS.gif +Image21Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RUS.gif + +Image22UUID:81fea498-9930-11e0-85be-406186cbf87c +Image22Type:Logo +Image22Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GER.gif +Image22Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GER.gif + +Image23UUID:93a58cc0-9930-11e0-85be-406186cbf87c +Image23Type:Logo +Image23Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/USA.gif +Image23Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/USA.gif + +Image24UUID:bc6068c4-9d77-11e0-85be-406186cbf87c +Image24Type:Logo +Image24Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UAE.gif +Image24Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UAE.gif + +Image25UUID:be8c642a-996e-11e0-85be-406186cbf87c +Image25Type:Logo +Image25Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GRE.gif +Image25Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GRE.gif + +Image26UUID:c2fdb14c-9934-11e0-85be-406186cbf87c +Image26Type:Logo +Image26Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/HUN.gif +Image26Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/HUN.gif + +Image27UUID:c3a2eaa6-996e-11e0-85be-406186cbf87c +Image27Type:Logo +Image27Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/IRL.gif +Image27Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/IRL.gif + +Image28UUID:c85b16fc-9934-11e0-85be-406186cbf87c +Image28Type:Logo +Image28Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISV.gif +Image28Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISV.gif + +Image29UUID:c8eaf36e-996e-11e0-85be-406186cbf87c +Image29Type:Logo +Image29Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MEX.gif +Image29Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MEX.gif + +Image30UUID:cd74b15c-9934-11e0-85be-406186cbf87c +Image30Type:Logo +Image30Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ITA.gif +Image30Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ITA.gif + +Image31UUID:cdf05d40-996e-11e0-85be-406186cbf87c +Image31Type:Logo +Image31Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NED.gif +Image31Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NED.gif + +Image32UUID:d3764b44-996e-11e0-85be-406186cbf87c +Image32Type:Logo +Image32Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POL.gif +Image32Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POL.gif + +Image33UUID:d77c220c-9934-11e0-85be-406186cbf87c +Image33Type:Logo +Image33Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/JPN.gif +Image33Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/JPN.gif + +Image34UUID:dcb92152-9934-11e0-85be-406186cbf87c +Image34Type:Logo +Image34Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NOR.gif +Image34Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NOR.gif + +Image35UUID:dff55d64-9e29-11e0-85be-406186cbf87c +Image35Type:Logo +Image35Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FIN.gif +Image35Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FIN.gif + +Image36UUID:e69d47f2-9934-11e0-85be-406186cbf87c +Image36Type:Logo +Image36Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif +Image36Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif + +Image37UUID:ebb4c5da-9934-11e0-85be-406186cbf87c +Image37Type:Logo +Image37Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SWE.gif +Image37Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SWE.gif + +Image38UUID:f5cab6b0-9934-11e0-85be-406186cbf87c +Image38Type:Logo +Image38Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UKR.gif +Image38Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/UKR.gif + +Class1UUID:da553a2a-9d14-11e0-85be-406186cbf87c +Class1Name:5o5 + +Team1UUID:6cf050ac-9934-11e0-85be-406186cbf87c +Team1Name:AUS +Team1Comp1UUID:de7d2ba8-9d14-11e0-85be-406186cbf87c + +Team2UUID:1d07e516-996e-11e0-85be-406186cbf87c +Team2Name:AUT + +Team3UUID:271ac230-996e-11e0-85be-406186cbf87c +Team3Name:BEL + +Team4UUID:2c8e2630-996e-11e0-85be-406186cbf87c +Team4Name:BRA + +Team5UUID:7210a622-9934-11e0-85be-406186cbf87c +Team5Name:CAN + +Team6UUID:36ebe888-996e-11e0-85be-406186cbf87c +Team6Name:CHI + +Team7UUID:77133b08-9934-11e0-85be-406186cbf87c +Team7Name:CRO + +Team8UUID:7c3a4770-9934-11e0-85be-406186cbf87c +Team8Name:DEN +Team8Comp1UUID:e7bc96f4-9d14-11e0-85be-406186cbf87c +Team8Comp2UUID:f0c9604c-9d14-11e0-85be-406186cbf87c +Team8Comp3UUID:ec336744-9d14-11e0-85be-406186cbf87c +Team8Comp4UUID:e3587876-9d14-11e0-85be-406186cbf87c + +Team9UUID:4c650fc8-996e-11e0-85be-406186cbf87c +Team9Name:EST + +Team10UUID:dfacd47c-9e29-11e0-85be-406186cbf87c +Team10Name:FIN + +Team11UUID:51d56ce6-996e-11e0-85be-406186cbf87c +Team11Name:FRA +Team11Comp1UUID:f969da74-9d14-11e0-85be-406186cbf87c +Team11Comp2UUID:f50675be-9d14-11e0-85be-406186cbf87c +Team11Comp3UUID:fda897ba-9d14-11e0-85be-406186cbf87c + +Team12UUID:613a12ea-996e-11e0-85be-406186cbf87c +Team12Name:GBR + +Team13UUID:81b951d6-9930-11e0-85be-406186cbf87c +Team13Name:GER +Team13Comp1UUID:70eb542e-9d15-11e0-85be-406186cbf87c +Team13Comp2UUID:40bd6314-9d15-11e0-85be-406186cbf87c +Team13Comp3UUID:0a917a14-9d15-11e0-85be-406186cbf87c +Team13Comp4UUID:9d4e23d4-9d15-11e0-85be-406186cbf87c +Team13Comp5UUID:498b40ec-9d15-11e0-85be-406186cbf87c +Team13Comp6UUID:7e051528-9d15-11e0-85be-406186cbf87c +Team13Comp7UUID:949b11b6-9d15-11e0-85be-406186cbf87c +Team13Comp8UUID:991ab354-9d15-11e0-85be-406186cbf87c +Team13Comp9UUID:826bdcb4-9d15-11e0-85be-406186cbf87c +Team13Comp10UUID:3c1fb294-9d15-11e0-85be-406186cbf87c +Team13Comp11UUID:4e00eaa0-9d15-11e0-85be-406186cbf87c +Team13Comp12UUID:a1f9ca46-9d15-11e0-85be-406186cbf87c +Team13Comp13UUID:1cd7dcc2-9d15-11e0-85be-406186cbf87c +Team13Comp14UUID:5b14f704-9d15-11e0-85be-406186cbf87c +Team13Comp15UUID:5f520bea-9d15-11e0-85be-406186cbf87c +Team13Comp16UUID:86d2fb5c-9d15-11e0-85be-406186cbf87c +Team13Comp17UUID:6c9514c8-9d15-11e0-85be-406186cbf87c +Team13Comp18UUID:13e98db8-9d15-11e0-85be-406186cbf87c +Team13Comp19UUID:21584926-9d15-11e0-85be-406186cbf87c +Team13Comp20UUID:2f1009e6-9d15-11e0-85be-406186cbf87c +Team13Comp21UUID:450c5a92-9d15-11e0-85be-406186cbf87c +Team13Comp22UUID:8b618332-9d15-11e0-85be-406186cbf87c +Team13Comp23UUID:75325a1e-9d15-11e0-85be-406186cbf87c +Team13Comp24UUID:37b2223c-9d15-11e0-85be-406186cbf87c +Team13Comp25UUID:062ea47e-9d15-11e0-85be-406186cbf87c +Team13Comp26UUID:525a0dc0-9d15-11e0-85be-406186cbf87c +Team13Comp27UUID:2a8fbab0-9d15-11e0-85be-406186cbf87c +Team13Comp28UUID:56b22d94-9d15-11e0-85be-406186cbf87c +Team13Comp29UUID:63855546-9d15-11e0-85be-406186cbf87c +Team13Comp30UUID:25ea8cce-9d15-11e0-85be-406186cbf87c +Team13Comp31UUID:68315036-9d15-11e0-85be-406186cbf87c +Team13Comp32UUID:a65d72a4-9d15-11e0-85be-406186cbf87c +Team13Comp33UUID:01f09174-9d15-11e0-85be-406186cbf87c +Team13Comp34UUID:aa852246-9d15-11e0-85be-406186cbf87c +Team13Comp35UUID:0f4448e8-9d15-11e0-85be-406186cbf87c +Team13Comp36UUID:7980a4cc-9d15-11e0-85be-406186cbf87c +Team13Comp37UUID:33500f24-9d15-11e0-85be-406186cbf87c +Team13Comp38UUID:1859e280-9d15-11e0-85be-406186cbf87c +Team13Comp39UUID:9006c3d4-9d15-11e0-85be-406186cbf87c + +Team14UUID:be35f1ee-996e-11e0-85be-406186cbf87c +Team14Name:GRE + +Team15UUID:c2b1d6a0-9934-11e0-85be-406186cbf87c +Team15Name:HUN + +Team16UUID:c3601ba4-996e-11e0-85be-406186cbf87c +Team16Name:IRL + +Team17UUID:c818eb6a-9934-11e0-85be-406186cbf87c +Team17Name:ISV + +Team18UUID:cd2435f6-9934-11e0-85be-406186cbf87c +Team18Name:ITA + +Team19UUID:d737d124-9934-11e0-85be-406186cbf87c +Team19Name:JPN + +Team20UUID:c8a808d8-996e-11e0-85be-406186cbf87c +Team20Name:MEX + +Team21UUID:cdaadaae-996e-11e0-85be-406186cbf87c +Team21Name:NED + +Team22UUID:dc76ea44-9934-11e0-85be-406186cbf87c +Team22Name:NOR + +Team23UUID:1e8f7452-9c49-11e0-85be-406186cbf87c +Team23Name:NZL + +Team24UUID:d3297b7a-996e-11e0-85be-406186cbf87c +Team24Name:POL + +Team25UUID:7d9ca8fa-9930-11e0-85be-406186cbf87c +Team25Name:RUS + +Team26UUID:6d98ab8a-9e2a-11e0-85be-406186cbf87c +Team26Name:SIN + +Team27UUID:e6510b62-9934-11e0-85be-406186cbf87c +Team27Name:SUI + +Team28UUID:eb727d9c-9934-11e0-85be-406186cbf87c +Team28Name:SWE +Team28Comp1UUID:aec14cd6-9d15-11e0-85be-406186cbf87c + +Team29UUID:ffb184b0-98e4-11e0-85be-406186cbf87c +Team29Name:Test + +Team30UUID:bc22780c-9d77-11e0-85be-406186cbf87c +Team30Name:UAE + +Team31UUID:f57e7ea8-9934-11e0-85be-406186cbf87c +Team31Name:UKR + +Team32UUID:0b5469f6-996f-11e0-85be-406186cbf87c +Team32Name:URU + +Team33UUID:9361c468-9930-11e0-85be-406186cbf87c +Team33Name:USA +SELECT Competitors.UUID,FirstName,LastName,Initials,Color,Nationality,Description,Icon,Picture,ClassId,Handicap,HandicapDistance,StartTime FROM event_20110609_KielerWoch.RaceObject INNER JOIN event_20110609_KielerWoch.Competitors ON event_20110609_KielerWoch.RaceObject.TrackedObjectId = event_20110609_KielerWoch.Competitors.UUID WHERE RaceId='7c666e50-9dde-11e0-85be-406186cbf87c' ORDER BY LID,StartTime,Competitors.UUID + + +Comp1UUID:de7d2ba8-9d14-11e0-85be-406186cbf87c +Comp1Name:Kevin +Comp1NameShort:AUS9061 +Comp1Color:FF0000 +Comp1Nationality:AUS +Comp1Description: +Comp1Handicap:1.0000000000 +Comp1HandicapDistance:0.00000 +Comp1StartTime:2011-06-24 09:15:00 +Comp1PictureUUID:6d332670-9934-11e0-85be-406186cbf87c +Comp1ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp1RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp2UUID:e3587876-9d14-11e0-85be-406186cbf87c +Comp2Name:van Wonterghem +Comp2NameShort:DEN8828 +Comp2Color:808000 +Comp2Nationality:DEN +Comp2Description: +Comp2Handicap:1.0000000000 +Comp2HandicapDistance:0.00000 +Comp2StartTime:2011-06-24 09:15:00 +Comp2PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp2ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp2RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp3UUID:e7bc96f4-9d14-11e0-85be-406186cbf87c +Comp3Name:Buhl +Comp3NameShort:DEN8958 +Comp3Color:FFFFFF +Comp3Nationality:DEN +Comp3Description: +Comp3Handicap:1.0000000000 +Comp3HandicapDistance:0.00000 +Comp3StartTime:2011-06-24 09:15:00 +Comp3PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp3ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp3RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp4UUID:ec336744-9d14-11e0-85be-406186cbf87c +Comp4Name:Saugmann +Comp4NameShort:DEN8964 +Comp4Color:FFFF00 +Comp4Nationality:DEN +Comp4Description: +Comp4Handicap:1.0000000000 +Comp4HandicapDistance:0.00000 +Comp4StartTime:2011-06-24 09:15:00 +Comp4PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp4ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp4RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp5UUID:f0c9604c-9d14-11e0-85be-406186cbf87c +Comp5Name:Bøjland +Comp5NameShort:DEN9023 +Comp5Color:800080 +Comp5Nationality:DEN +Comp5Description: +Comp5Handicap:1.0000000000 +Comp5HandicapDistance:0.00000 +Comp5StartTime:2011-06-24 09:15:00 +Comp5PictureUUID:7c87105a-9934-11e0-85be-406186cbf87c +Comp5ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp5RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp6UUID:f50675be-9d14-11e0-85be-406186cbf87c +Comp6Name:Broise +Comp6NameShort:FRA8844 +Comp6Color:008080 +Comp6Nationality:FRA +Comp6Description: +Comp6Handicap:1.0000000000 +Comp6HandicapDistance:0.00000 +Comp6StartTime:2011-06-24 09:15:00 +Comp6PictureUUID:5222c1c6-996e-11e0-85be-406186cbf87c +Comp6ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp6RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp7UUID:f969da74-9d14-11e0-85be-406186cbf87c +Comp7Name:boite +Comp7NameShort:FRA8902 +Comp7Color:800000 +Comp7Nationality:FRA +Comp7Description: +Comp7Handicap:1.0000000000 +Comp7HandicapDistance:0.00000 +Comp7StartTime:2011-06-24 09:15:00 +Comp7PictureUUID:5222c1c6-996e-11e0-85be-406186cbf87c +Comp7ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp7RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp8UUID:fda897ba-9d14-11e0-85be-406186cbf87c +Comp8Name:de Lisle +Comp8NameShort:FRA9068 +Comp8Color:000080 +Comp8Nationality:FRA +Comp8Description: +Comp8Handicap:1.0000000000 +Comp8HandicapDistance:0.00000 +Comp8StartTime:2011-06-24 09:15:00 +Comp8PictureUUID:5222c1c6-996e-11e0-85be-406186cbf87c +Comp8ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp8RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp9UUID:01f09174-9d15-11e0-85be-406186cbf87c +Comp9Name:Schneidewind +Comp9NameShort:GER6817 +Comp9Color:00FFFF +Comp9Nationality:GER +Comp9Description: +Comp9Handicap:1.0000000000 +Comp9HandicapDistance:0.00000 +Comp9StartTime:2011-06-24 09:15:00 +Comp9PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp9ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp9RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp10UUID:062ea47e-9d15-11e0-85be-406186cbf87c +Comp10Name:Menge +Comp10NameShort:GER7861 +Comp10Color:000000 +Comp10Nationality:GER +Comp10Description: +Comp10Handicap:1.0000000000 +Comp10HandicapDistance:0.00000 +Comp10StartTime:2011-06-24 09:15:00 +Comp10PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp10ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp10RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp11UUID:0a917a14-9d15-11e0-85be-406186cbf87c +Comp11Name:Barop +Comp11NameShort:GER8392 +Comp11Color:0000FF +Comp11Nationality:GER +Comp11Description: +Comp11Handicap:1.0000000000 +Comp11HandicapDistance:0.00000 +Comp11StartTime:2011-06-24 09:15:00 +Comp11PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp11ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp11RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp12UUID:0f4448e8-9d15-11e0-85be-406186cbf87c +Comp12Name:Steffen +Comp12NameShort:GER8437 +Comp12Color:FF00FF +Comp12Nationality:GER +Comp12Description: +Comp12Handicap:1.0000000000 +Comp12HandicapDistance:0.00000 +Comp12StartTime:2011-06-24 09:15:00 +Comp12PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp12ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp12RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp13UUID:13e98db8-9d15-11e0-85be-406186cbf87c +Comp13Name:Katja +Comp13NameShort:GER8500 +Comp13Color:008000 +Comp13Nationality:GER +Comp13Description: +Comp13Handicap:1.0000000000 +Comp13HandicapDistance:0.00000 +Comp13StartTime:2011-06-24 09:15:00 +Comp13PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp13ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp13RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp14UUID:1859e280-9d15-11e0-85be-406186cbf87c +Comp14Name:von Nerée +Comp14NameShort:GER8522 +Comp14Color:00FF00 +Comp14Nationality:GER +Comp14Description: +Comp14Handicap:1.0000000000 +Comp14HandicapDistance:0.00000 +Comp14StartTime:2011-06-24 09:15:00 +Comp14PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp14ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp14RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp15UUID:1cd7dcc2-9d15-11e0-85be-406186cbf87c +Comp15Name:Goedeking +Comp15NameShort:GER8622 +Comp15Color:FF0000 +Comp15Nationality:GER +Comp15Description: +Comp15Handicap:1.0000000000 +Comp15HandicapDistance:0.00000 +Comp15StartTime:2011-06-24 09:15:00 +Comp15PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp15ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp15RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp16UUID:21584926-9d15-11e0-85be-406186cbf87c +Comp16Name:Kellner +Comp16NameShort:GER8651 +Comp16Color:808000 +Comp16Nationality:GER +Comp16Description: +Comp16Handicap:1.0000000000 +Comp16HandicapDistance:0.00000 +Comp16StartTime:2011-06-24 09:15:00 +Comp16PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp16ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp16RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp17UUID:25ea8cce-9d15-11e0-85be-406186cbf87c +Comp17Name:Reincke +Comp17NameShort:GER8656 +Comp17Color:FFFFFF +Comp17Nationality:GER +Comp17Description: +Comp17Handicap:1.0000000000 +Comp17HandicapDistance:0.00000 +Comp17StartTime:2011-06-24 09:15:00 +Comp17PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp17ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp17RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp18UUID:2a8fbab0-9d15-11e0-85be-406186cbf87c +Comp18Name:Neulen +Comp18NameShort:GER8657 +Comp18Color:FFFF00 +Comp18Nationality:GER +Comp18Description: +Comp18Handicap:1.0000000000 +Comp18HandicapDistance:0.00000 +Comp18StartTime:2011-06-24 09:15:00 +Comp18PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp18ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp18RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp19UUID:2f1009e6-9d15-11e0-85be-406186cbf87c +Comp19Name:Kohl +Comp19NameShort:GER8670 +Comp19Color:800080 +Comp19Nationality:GER +Comp19Description: +Comp19Handicap:1.0000000000 +Comp19HandicapDistance:0.00000 +Comp19StartTime:2011-06-24 09:15:00 +Comp19PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp19ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp19RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp20UUID:33500f24-9d15-11e0-85be-406186cbf87c +Comp20Name:Tasche +Comp20NameShort:GER8720 +Comp20Color:008080 +Comp20Nationality:GER +Comp20Description: +Comp20Handicap:1.0000000000 +Comp20HandicapDistance:0.00000 +Comp20StartTime:2011-06-24 09:15:00 +Comp20PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp20ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp20RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp21UUID:37b2223c-9d15-11e0-85be-406186cbf87c +Comp21Name:Lietz +Comp21NameShort:GER8721 +Comp21Color:800000 +Comp21Nationality:GER +Comp21Description: +Comp21Handicap:1.0000000000 +Comp21HandicapDistance:0.00000 +Comp21StartTime:2011-06-24 09:15:00 +Comp21PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp21ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp21RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp22UUID:3c1fb294-9d15-11e0-85be-406186cbf87c +Comp22Name:Feldmann +Comp22NameShort:GER8730 +Comp22Color:000080 +Comp22Nationality:GER +Comp22Description: +Comp22Handicap:1.0000000000 +Comp22HandicapDistance:0.00000 +Comp22StartTime:2011-06-24 09:15:00 +Comp22PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp22ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp22RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp23UUID:40bd6314-9d15-11e0-85be-406186cbf87c +Comp23Name:Anton +Comp23NameShort:GER8741 +Comp23Color:00FFFF +Comp23Nationality:GER +Comp23Description: +Comp23Handicap:1.0000000000 +Comp23HandicapDistance:0.00000 +Comp23StartTime:2011-06-24 09:15:00 +Comp23PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp23ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp23RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp24UUID:450c5a92-9d15-11e0-85be-406186cbf87c +Comp24Name:Kraft +Comp24NameShort:GER8843 +Comp24Color:000000 +Comp24Nationality:GER +Comp24Description: +Comp24Handicap:1.0000000000 +Comp24HandicapDistance:0.00000 +Comp24StartTime:2011-06-24 09:15:00 +Comp24PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp24ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp24RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp25UUID:498b40ec-9d15-11e0-85be-406186cbf87c +Comp25Name:Brill +Comp25NameShort:GER8874 +Comp25Color:0000FF +Comp25Nationality:GER +Comp25Description: +Comp25Handicap:1.0000000000 +Comp25HandicapDistance:0.00000 +Comp25StartTime:2011-06-24 09:15:00 +Comp25PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp25ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp25RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp26UUID:4e00eaa0-9d15-11e0-85be-406186cbf87c +Comp26Name:Findel +Comp26NameShort:GER8875 +Comp26Color:FF00FF +Comp26Nationality:GER +Comp26Description: +Comp26Handicap:1.0000000000 +Comp26HandicapDistance:0.00000 +Comp26StartTime:2011-06-24 09:15:00 +Comp26PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp26ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp26RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp27UUID:525a0dc0-9d15-11e0-85be-406186cbf87c +Comp27Name:Mäge +Comp27NameShort:GER8901 +Comp27Color:008000 +Comp27Nationality:GER +Comp27Description: +Comp27Handicap:1.0000000000 +Comp27HandicapDistance:0.00000 +Comp27StartTime:2011-06-24 09:15:00 +Comp27PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp27ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp27RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp28UUID:56b22d94-9d15-11e0-85be-406186cbf87c +Comp28Name:Pleßmann +Comp28NameShort:GER8927 +Comp28Color:00FF00 +Comp28Nationality:GER +Comp28Description: +Comp28Handicap:1.0000000000 +Comp28HandicapDistance:0.00000 +Comp28StartTime:2011-06-24 09:15:00 +Comp28PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp28ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp28RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp29UUID:5b14f704-9d15-11e0-85be-406186cbf87c +Comp29Name:Gosch +Comp29NameShort:GER8929 +Comp29Color:FF0000 +Comp29Nationality:GER +Comp29Description: +Comp29Handicap:1.0000000000 +Comp29HandicapDistance:0.00000 +Comp29StartTime:2011-06-24 09:15:00 +Comp29PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp29ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp29RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp30UUID:5f520bea-9d15-11e0-85be-406186cbf87c +Comp30Name:Henge +Comp30NameShort:GER8932 +Comp30Color:808000 +Comp30Nationality:GER +Comp30Description: +Comp30Handicap:1.0000000000 +Comp30HandicapDistance:0.00000 +Comp30StartTime:2011-06-24 09:15:00 +Comp30PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp30ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp30RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp31UUID:63855546-9d15-11e0-85be-406186cbf87c +Comp31Name:Rasenack +Comp31NameShort:GER8956 +Comp31Color:FFFFFF +Comp31Nationality:GER +Comp31Description: +Comp31Handicap:1.0000000000 +Comp31HandicapDistance:0.00000 +Comp31StartTime:2011-06-24 09:15:00 +Comp31PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp31ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp31RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp32UUID:68315036-9d15-11e0-85be-406186cbf87c +Comp32Name:Roos +Comp32NameShort:GER8972 +Comp32Color:FFFF00 +Comp32Nationality:GER +Comp32Description: +Comp32Handicap:1.0000000000 +Comp32HandicapDistance:0.00000 +Comp32StartTime:2011-06-24 09:15:00 +Comp32PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp32ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp32RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp33UUID:6c9514c8-9d15-11e0-85be-406186cbf87c +Comp33Name:Jungclaus +Comp33NameShort:GER8989 +Comp33Color:800080 +Comp33Nationality:GER +Comp33Description: +Comp33Handicap:1.0000000000 +Comp33HandicapDistance:0.00000 +Comp33StartTime:2011-06-24 09:15:00 +Comp33PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp33ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp33RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp34UUID:70eb542e-9d15-11e0-85be-406186cbf87c +Comp34Name:Achterberg +Comp34NameShort:GER8990 +Comp34Color:008080 +Comp34Nationality:GER +Comp34Description: +Comp34Handicap:1.0000000000 +Comp34HandicapDistance:0.00000 +Comp34StartTime:2011-06-24 09:15:00 +Comp34PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp34ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp34RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp35UUID:75325a1e-9d15-11e0-85be-406186cbf87c +Comp35Name:Lehmann +Comp35NameShort:GER8992 +Comp35Color:800000 +Comp35Nationality:GER +Comp35Description: +Comp35Handicap:1.0000000000 +Comp35HandicapDistance:0.00000 +Comp35StartTime:2011-06-24 09:15:00 +Comp35PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp35ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp35RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp36UUID:7980a4cc-9d15-11e0-85be-406186cbf87c +Comp36Name:Stuempel +Comp36NameShort:GER8999 +Comp36Color:000080 +Comp36Nationality:GER +Comp36Description: +Comp36Handicap:1.0000000000 +Comp36HandicapDistance:0.00000 +Comp36StartTime:2011-06-24 09:15:00 +Comp36PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp36ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp36RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp37UUID:7e051528-9d15-11e0-85be-406186cbf87c +Comp37Name:Böger +Comp37NameShort:GER902 +Comp37Color:00FFFF +Comp37Nationality:GER +Comp37Description: +Comp37Handicap:1.0000000000 +Comp37HandicapDistance:0.00000 +Comp37StartTime:2011-06-24 09:15:00 +Comp37PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp37ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp37RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp38UUID:826bdcb4-9d15-11e0-85be-406186cbf87c +Comp38Name:Dr.Plattner +Comp38NameShort:GER9026 +Comp38Color:000000 +Comp38Nationality:GER +Comp38Description: +Comp38Handicap:1.0000000000 +Comp38HandicapDistance:0.00000 +Comp38StartTime:2011-06-24 09:15:00 +Comp38PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp38ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp38RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp39UUID:86d2fb5c-9d15-11e0-85be-406186cbf87c +Comp39Name:Hunger +Comp39NameShort:GER9027 +Comp39Color:0000FF +Comp39Nationality:GER +Comp39Description: +Comp39Handicap:1.0000000000 +Comp39HandicapDistance:0.00000 +Comp39StartTime:2011-06-24 09:15:00 +Comp39PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp39ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp39RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp40UUID:8b618332-9d15-11e0-85be-406186cbf87c +Comp40Name:Köchlin +Comp40NameShort:GER9028 +Comp40Color:FF00FF +Comp40Nationality:GER +Comp40Description: +Comp40Handicap:1.0000000000 +Comp40HandicapDistance:0.00000 +Comp40StartTime:2011-06-24 09:15:00 +Comp40PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp40ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp40RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp41UUID:9006c3d4-9d15-11e0-85be-406186cbf87c +Comp41Name:Waldheim +Comp41NameShort:GER9029 +Comp41Color:008000 +Comp41Nationality:GER +Comp41Description: +Comp41Handicap:1.0000000000 +Comp41HandicapDistance:0.00000 +Comp41StartTime:2011-06-24 09:15:00 +Comp41PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp41ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp41RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp42UUID:949b11b6-9d15-11e0-85be-406186cbf87c +Comp42Name:Böhm +Comp42NameShort:GER9035 +Comp42Color:00FF00 +Comp42Nationality:GER +Comp42Description: +Comp42Handicap:1.0000000000 +Comp42HandicapDistance:0.00000 +Comp42StartTime:2011-06-24 09:15:00 +Comp42PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp42ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp42RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp43UUID:991ab354-9d15-11e0-85be-406186cbf87c +Comp43Name:Dasenbrook +Comp43NameShort:GER9040 +Comp43Color:FF0000 +Comp43Nationality:GER +Comp43Description: +Comp43Handicap:1.0000000000 +Comp43HandicapDistance:0.00000 +Comp43StartTime:2011-06-24 09:15:00 +Comp43PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp43ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp43RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp44UUID:9d4e23d4-9d15-11e0-85be-406186cbf87c +Comp44Name:Birkner +Comp44NameShort:GER9043 +Comp44Color:808000 +Comp44Nationality:GER +Comp44Description: +Comp44Handicap:1.0000000000 +Comp44HandicapDistance:0.00000 +Comp44StartTime:2011-06-24 09:15:00 +Comp44PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp44ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp44RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp45UUID:a1f9ca46-9d15-11e0-85be-406186cbf87c +Comp45Name:Fischer +Comp45NameShort:GER9054 +Comp45Color:FFFFFF +Comp45Nationality:GER +Comp45Description: +Comp45Handicap:1.0000000000 +Comp45HandicapDistance:0.00000 +Comp45StartTime:2011-06-24 09:15:00 +Comp45PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp45ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp45RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp46UUID:a65d72a4-9d15-11e0-85be-406186cbf87c +Comp46Name:Salein +Comp46NameShort:GER9059 +Comp46Color:FFFF00 +Comp46Nationality:GER +Comp46Description: +Comp46Handicap:1.0000000000 +Comp46HandicapDistance:0.00000 +Comp46StartTime:2011-06-24 09:15:00 +Comp46PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp46ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp46RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp47UUID:aa852246-9d15-11e0-85be-406186cbf87c +Comp47Name:Schomäker +Comp47NameShort:GER9070 +Comp47Color:800080 +Comp47Nationality:GER +Comp47Description: +Comp47Handicap:1.0000000000 +Comp47HandicapDistance:0.00000 +Comp47StartTime:2011-06-24 09:15:00 +Comp47PictureUUID:81fea498-9930-11e0-85be-406186cbf87c +Comp47ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp47RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +Comp48UUID:aec14cd6-9d15-11e0-85be-406186cbf87c +Comp48Name:Hastenpflug +Comp48NameShort:SWE8912 +Comp48Color:008080 +Comp48Nationality:SWE +Comp48Description: +Comp48Handicap:1.0000000000 +Comp48HandicapDistance:0.00000 +Comp48StartTime:2011-06-24 09:15:00 +Comp48PictureUUID:ebb4c5da-9934-11e0-85be-406186cbf87c +Comp48ClassUUID:da553a2a-9d14-11e0-85be-406186cbf87c +Comp48RouteUUID:2a75ee72-9dde-11e0-85be-406186cbf87c + +ControlPoint1UUID:07d84022-9951-11e0-85be-406186cbf87c +ControlPoint1Name:D Mark4 +ControlPoint1HasTwoPoints:1 +ControlPoint1ShortName:4 +ControlPoint1Mark1Lon:10.169555 +ControlPoint1Mark1Lat:54.428753 +ControlPoint1Mark2Lon:10.169408 +ControlPoint1Mark2Lat:54.428653 + +ControlPoint2UUID:0807af7c-9b42-11e0-85be-406186cbf87c +ControlPoint2Name:Star-R6-1 +ControlPoint2HasTwoPoints:0 +ControlPoint2ShortName:1 +ControlPoint2Mark1Lon:10.235563 +ControlPoint2Mark1Lat:54.504244 + +ControlPoint3UUID:0beb273c-992f-11e0-85be-406186cbf87c +ControlPoint3Name:Finish Silbernes Band +ControlPoint3HasTwoPoints:1 +ControlPoint3ShortName:F +ControlPoint3Mark1Lon:10.273520 +ControlPoint3Mark1Lat:54.499905 +ControlPoint3Mark2Lon:10.273137 +ControlPoint3Mark2Lat:54.500737 + +ControlPoint4UUID:144f44f4-9dac-11e0-85be-406186cbf87c +ControlPoint4Name:SB-Mark H5 (y) (WP 33) +ControlPoint4HasTwoPoints:0 +ControlPoint4ShortName:H5 +ControlPoint4Mark1Lon:10.666667 +ControlPoint4Mark1Lat:54.548333 + +ControlPoint5UUID:18e98784-9930-11e0-85be-406186cbf87c +ControlPoint5Name:Start Silbernes Band +ControlPoint5HasTwoPoints:1 +ControlPoint5ShortName:S +ControlPoint5Mark1Lon:10.171855 +ControlPoint5Mark1Lat:54.428460 +ControlPoint5Mark2Lon:10.422222 +ControlPoint5Mark2Lat:54.564262 + +ControlPoint6UUID:1d8536e6-9a91-11e0-85be-406186cbf87c +ControlPoint6Name:Star1-4 +ControlPoint6HasTwoPoints:1 +ControlPoint6ShortName:4 +ControlPoint6Mark1Lon:10.219525 +ControlPoint6Mark1Lat:54.500618 +ControlPoint6Mark2Lon:10.220421 +ControlPoint6Mark2Lat:54.500744 + +ControlPoint7UUID:22475d30-9951-11e0-85be-406186cbf87c +ControlPoint7Name:Audi Start +ControlPoint7HasTwoPoints:1 +ControlPoint7ShortName:S +ControlPoint7Mark1Lon:10.170118 +ControlPoint7Mark1Lat:54.342563 +ControlPoint7Mark2Lon:10.170157 +ControlPoint7Mark2Lat:54.341457 + +ControlPoint8UUID:2307bec6-992f-11e0-85be-406186cbf87c +ControlPoint8Name:Mark KO2 red/white +ControlPoint8HasTwoPoints:0 +ControlPoint8ShortName:KO +ControlPoint8Mark1Lon:10.450000 +ControlPoint8Mark1Lat:54.533333 + +ControlPoint9UUID:2497f822-9ab1-11e0-85be-406186cbf87c +ControlPoint9Name:49Y-R7-1 +ControlPoint9HasTwoPoints:0 +ControlPoint9ShortName:1 +ControlPoint9Mark1Lon:10.207426 +ControlPoint9Mark1Lat:54.435751 + +ControlPoint10UUID:27c13096-9a8d-11e0-85be-406186cbf87c +ControlPoint10Name:49b4-4 +ControlPoint10HasTwoPoints:1 +ControlPoint10ShortName:4 +ControlPoint10Mark1Lon:10.208914 +ControlPoint10Mark1Lat:54.445496 +ControlPoint10Mark2Lon:10.209630 +ControlPoint10Mark2Lat:54.444822 + +ControlPoint11UUID:2c8584ca-9dac-11e0-85be-406186cbf87c +ControlPoint11Name:SB-Finish (Ziel) at Kiel Lighthouse +ControlPoint11HasTwoPoints:0 +ControlPoint11ShortName:KL +ControlPoint11Mark1Lon:10.278333 +ControlPoint11Mark1Lat:54.498333 + +ControlPoint12UUID:32ad8a14-992e-11e0-85be-406186cbf87c +ControlPoint12Name:Mark 14 red (Fahrwassertonne 14) +ControlPoint12HasTwoPoints:0 +ControlPoint12ShortName:14 +ControlPoint12Mark1Lon:10.185000 +ControlPoint12Mark1Lat:54.371667 + +ControlPoint13UUID:32fb2850-9951-11e0-85be-406186cbf87c +ControlPoint13Name:Audi Mark1 +ControlPoint13HasTwoPoints:0 +ControlPoint13ShortName:1 +ControlPoint13Mark1Lon:10.159550 +ControlPoint13Mark1Lat:54.341178 + +ControlPoint14UUID:34feeaea-9c32-11e0-85be-406186cbf87c +ControlPoint14Name:Star7-1a +ControlPoint14HasTwoPoints:0 +ControlPoint14ShortName:1a +ControlPoint14Mark1Lon:10.230608 +ControlPoint14Mark1Lat:54.508407 + +ControlPoint15UUID:3ecf9ade-992f-11e0-85be-406186cbf87c +ControlPoint15Name:Schlei Safe Water Mark red/white +ControlPoint15HasTwoPoints:0 +ControlPoint15ShortName:SS +ControlPoint15Mark1Lon:10.055000 +ControlPoint15Mark1Lat:54.668333 + +ControlPoint16UUID:405f346c-99e9-11e0-85be-406186cbf87c +ControlPoint16Name:49erYR2-1 +ControlPoint16HasTwoPoints:0 +ControlPoint16ShortName:1 +ControlPoint16Mark1Lon:10.200182 +ControlPoint16Mark1Lat:54.427670 + +ControlPoint17UUID:4123b6ea-9951-11e0-85be-406186cbf87c +ControlPoint17Name:Audi Mark2 +ControlPoint17HasTwoPoints:0 +ControlPoint17ShortName:2 +ControlPoint17Mark1Lon:10.170157 +ControlPoint17Mark1Lat:54.341457 + +ControlPoint18UUID:4bac3eca-992e-11e0-85be-406186cbf87c +ControlPoint18Name:Three yellow marks* (3 gelbe Tonnen) +ControlPoint18HasTwoPoints:0 +ControlPoint18ShortName:3y +ControlPoint18Mark1Lon:10.198333 +ControlPoint18Mark1Lat:54.386667 + +ControlPoint19UUID:535fb3f4-9951-11e0-85be-406186cbf87c +ControlPoint19Name:Audi Finish +ControlPoint19HasTwoPoints:1 +ControlPoint19ShortName:F +ControlPoint19Mark1Lon:10.159708 +ControlPoint19Mark1Lat:54.340965 +ControlPoint19Mark2Lon:10.159503 +ControlPoint19Mark2Lat:54.341232 + +ControlPoint20UUID:56f63cf8-99e8-11e0-85be-406186cbf87c +ControlPoint20Name:49erYR1-T +ControlPoint20HasTwoPoints:0 +ControlPoint20ShortName:T +ControlPoint20Mark1Lon:10.199148 +ControlPoint20Mark1Lat:54.429370 + +ControlPoint21UUID:5933cdca-9950-11e0-85be-406186cbf87c +ControlPoint21Name:H Start +ControlPoint21HasTwoPoints:1 +ControlPoint21ShortName:S +ControlPoint21Mark1Lon:10.169470 +ControlPoint21Mark1Lat:54.428662 +ControlPoint21Mark2Lon:10.169382 +ControlPoint21Mark2Lat:54.428875 + +ControlPoint22UUID:65b2dfbc-9b64-11e0-85be-406186cbf87c +ControlPoint22Name:K Start +ControlPoint22HasTwoPoints:1 +ControlPoint22ShortName:S +ControlPoint22Mark1Lon:10.200427 +ControlPoint22Mark1Lat:54.494328 +ControlPoint22Mark2Lon:10.198325 +ControlPoint22Mark2Lat:54.497162 + +ControlPoint23UUID:65c3a9ec-992e-11e0-85be-406186cbf87c +ControlPoint23Name:Mark 4 red (Fahrwassertonne 4) +ControlPoint23HasTwoPoints:0 +ControlPoint23ShortName:4 +ControlPoint23Mark1Lon:10.233333 +ControlPoint23Mark1Lat:54.428333 + +ControlPoint24UUID:69a347b2-9950-11e0-85be-406186cbf87c +ControlPoint24Name:H Mark1 +ControlPoint24HasTwoPoints:0 +ControlPoint24ShortName:1 +ControlPoint24Mark1Lon:10.169522 +ControlPoint24Mark1Lat:54.428727 + +ControlPoint25UUID:6c30e118-9a8d-11e0-85be-406186cbf87c +ControlPoint25Name:49b4-F +ControlPoint25HasTwoPoints:1 +ControlPoint25ShortName:F +ControlPoint25Mark1Lon:10.209712 +ControlPoint25Mark1Lat:54.445952 +ControlPoint25Mark2Lon:10.210658 +ControlPoint25Mark2Lat:54.445241 + +ControlPoint26UUID:7634bbfe-9dac-11e0-85be-406186cbf87c +ControlPoint26Name:SB-Mark Red (WP 124) +ControlPoint26HasTwoPoints:0 +ControlPoint26ShortName:WP +ControlPoint26Mark1Lon:10.236667 +ControlPoint26Mark1Lat:55.045000 + +ControlPoint27UUID:79dab2ea-9aaf-11e0-85be-406186cbf87c +ControlPoint27Name:49erB5-4 +ControlPoint27HasTwoPoints:1 +ControlPoint27ShortName:4 +ControlPoint27Mark1Lon:10.201355 +ControlPoint27Mark1Lat:54.446073 +ControlPoint27Mark2Lon:10.197830 +ControlPoint27Mark2Lat:54.446886 + +ControlPoint28UUID:7a218886-9b64-11e0-85be-406186cbf87c +ControlPoint28Name:K Mark1 +ControlPoint28HasTwoPoints:0 +ControlPoint28ShortName:1 +ControlPoint28Mark1Lon:10.165025 +ControlPoint28Mark1Lat:54.503712 + +ControlPoint29UUID:7c639ea8-9da9-11e0-85be-406186cbf87c +ControlPoint29Name:SB Mark1 (Schleimünde Approach (r/w) ) +ControlPoint29HasTwoPoints:0 +ControlPoint29ShortName:1 +ControlPoint29Mark1Lon:10.060000 +ControlPoint29Mark1Lat:54.665000 + +ControlPoint30UUID:8145342e-992e-11e0-85be-406186cbf87c +ControlPoint30Name:Mark 2 red (Fahrwassertonne 2) +ControlPoint30HasTwoPoints:0 +ControlPoint30ShortName:2 +ControlPoint30Mark1Lon:10.283333 +ControlPoint30Mark1Lat:54.465000 + +ControlPoint31UUID:84009c94-98e3-11e0-85be-406186cbf87c +ControlPoint31Name:Test - Top Mark +ControlPoint31HasTwoPoints:0 +ControlPoint31ShortName:1 +ControlPoint31Mark1Lon:10.169028 +ControlPoint31Mark1Lat:54.428993 + +ControlPoint32UUID:86c90a6e-9a9c-11e0-85be-406186cbf87c +ControlPoint32Name:S3-4 +ControlPoint32HasTwoPoints:1 +ControlPoint32ShortName:4 +ControlPoint32Mark1Lon:10.218663 +ControlPoint32Mark1Lat:54.515525 +ControlPoint32Mark2Lon:10.218214 +ControlPoint32Mark2Lat:54.515649 + +ControlPoint33UUID:8d1c4750-9b64-11e0-85be-406186cbf87c +ControlPoint33Name:K Mark2 +ControlPoint33HasTwoPoints:0 +ControlPoint33ShortName:2 +ControlPoint33Mark1Lon:10.169415 +ControlPoint33Mark1Lat:54.428730 + +ControlPoint34UUID:99cae4d0-992e-11e0-85be-406186cbf87c +ControlPoint34Name:Mark 1 green (Fahrwassertonne 1) +ControlPoint34HasTwoPoints:0 +ControlPoint34ShortName:1 +ControlPoint34Mark1Lon:10.251667 +ControlPoint34Mark1Lat:54.475000 + +ControlPoint35UUID:9beca160-9a8f-11e0-85be-406186cbf87c +ControlPoint35Name:49erb1-1 +ControlPoint35HasTwoPoints:0 +ControlPoint35ShortName:T +ControlPoint35Mark1Lon:10.196778 +ControlPoint35Mark1Lat:54.439621 + +ControlPoint36UUID:9c4e4984-9a93-11e0-85be-406186cbf87c +ControlPoint36Name:Star2-4 +ControlPoint36HasTwoPoints:1 +ControlPoint36ShortName:4 +ControlPoint36Mark1Lon:10.230194 +ControlPoint36Mark1Lat:54.504793 +ControlPoint36Mark2Lon:10.229448 +ControlPoint36Mark2Lat:54.505033 + +ControlPoint37UUID:9caa2f3a-98e3-11e0-85be-406186cbf87c +ControlPoint37Name:Test - start +ControlPoint37HasTwoPoints:1 +ControlPoint37ShortName:S +ControlPoint37Mark1Lon:10.169440 +ControlPoint37Mark1Lat:54.428712 +ControlPoint37Mark2Lon:10.169403 +ControlPoint37Mark2Lat:54.428872 + +ControlPoint38UUID:9da911ea-9950-11e0-85be-406186cbf87c +ControlPoint38Name:H Mark4 +ControlPoint38HasTwoPoints:1 +ControlPoint38ShortName:4 +ControlPoint38Mark1Lon:10.169477 +ControlPoint38Mark1Lat:54.428658 +ControlPoint38Mark2Lon:10.169432 +ControlPoint38Mark2Lat:54.428505 + +ControlPoint39UUID:a0cbd40c-9dab-11e0-85be-406186cbf87c +ControlPoint39Name:SB-Mark Schleimünde Approach (r/w) +ControlPoint39HasTwoPoints:0 +ControlPoint39ShortName:SA +ControlPoint39Mark1Lon:10.060000 +ControlPoint39Mark1Lat:54.665000 + +ControlPoint40UUID:a68cc03e-9b64-11e0-85be-406186cbf87c +ControlPoint40Name:K Mark3 +ControlPoint40HasTwoPoints:1 +ControlPoint40ShortName:3 +ControlPoint40Mark1Lon:10.169510 +ControlPoint40Mark1Lat:54.428703 +ControlPoint40Mark2Lon:10.237422 +ControlPoint40Mark2Lat:54.480728 + +ControlPoint41UUID:a8c75c3e-9a8c-11e0-85be-406186cbf87c +ControlPoint41Name:49b4-Start +ControlPoint41HasTwoPoints:1 +ControlPoint41ShortName:S +ControlPoint41Mark1Lon:10.209545 +ControlPoint41Mark1Lat:54.446065 +ControlPoint41Mark2Lon:10.211504 +ControlPoint41Mark2Lat:54.444520 + +ControlPoint42UUID:a91da4ea-9dac-11e0-85be-406186cbf87c +ControlPoint42Name:SB-Mark Thurö Rev (Y/B) (WP 277) +ControlPoint42HasTwoPoints:0 +ControlPoint42ShortName:Th +ControlPoint42Mark1Lon:10.735000 +ControlPoint42Mark1Lat:55.020000 + +ControlPoint43UUID:b2ad1adc-9950-11e0-85be-406186cbf87c +ControlPoint43Name:H Finish +ControlPoint43HasTwoPoints:1 +ControlPoint43ShortName:F +ControlPoint43Mark1Lon:10.169398 +ControlPoint43Mark1Lat:54.428765 +ControlPoint43Mark2Lon:10.169380 +ControlPoint43Mark2Lat:54.428638 + +ControlPoint44UUID:b31eaad0-9dab-11e0-85be-406186cbf87c +ControlPoint44Name:SB-Mark Spodsbjerg-SE +ControlPoint44HasTwoPoints:0 +ControlPoint44ShortName:Sp +ControlPoint44Mark1Lon:10.845000 +ControlPoint44Mark1Lat:54.918333 + +ControlPoint45UUID:b3af1998-992e-11e0-85be-406186cbf87c +ControlPoint45Name:Mark K yellow (Kabeltonne) +ControlPoint45HasTwoPoints:0 +ControlPoint45ShortName:K +ControlPoint45Mark1Lon:10.255000 +ControlPoint45Mark1Lat:54.488333 + +ControlPoint46UUID:bfe4dc42-9b64-11e0-85be-406186cbf87c +ControlPoint46Name:K Mark4 +ControlPoint46HasTwoPoints:1 +ControlPoint46ShortName:4 +ControlPoint46Mark1Lon:10.198390 +ControlPoint46Mark1Lat:54.497398 +ControlPoint46Mark2Lon:10.198355 +ControlPoint46Mark2Lat:54.497917 + +ControlPoint47UUID:c2199936-9dac-11e0-85be-406186cbf87c +ControlPoint47Name:SB-Mark Black Yellow (WP 253) +ControlPoint47HasTwoPoints:0 +ControlPoint47ShortName:BY +ControlPoint47Mark1Lon:10.936667 +ControlPoint47Mark1Lat:55.185000 + +ControlPoint48UUID:c583e438-9950-11e0-85be-406186cbf87c +ControlPoint48Name:D Start +ControlPoint48HasTwoPoints:1 +ControlPoint48ShortName:S +ControlPoint48Mark1Lon:10.169462 +ControlPoint48Mark1Lat:54.428848 +ControlPoint48Mark2Lon:10.169363 +ControlPoint48Mark2Lat:54.428722 + +ControlPoint49UUID:cd57b5f8-992e-11e0-85be-406186cbf87c +ControlPoint49Name:Stollergrund-N (black/yellow) +ControlPoint49HasTwoPoints:0 +ControlPoint49ShortName:SN +ControlPoint49Mark1Lon:10.200000 +ControlPoint49Mark1Lat:54.536667 + +ControlPoint50UUID:d2b4c5d2-9950-11e0-85be-406186cbf87c +ControlPoint50Name:D Mark1 +ControlPoint50HasTwoPoints:0 +ControlPoint50ShortName:1 +ControlPoint50Mark1Lon:10.166928 +ControlPoint50Mark1Lat:54.345997 + +ControlPoint51UUID:de21cff2-9ab6-11e0-85be-406186cbf87c +ControlPoint51Name:49Y-R8-1a +ControlPoint51HasTwoPoints:0 +ControlPoint51ShortName:1 +ControlPoint51Mark1Lon:10.207476 +ControlPoint51Mark1Lat:54.435762 + +ControlPoint52UUID:e299f688-9dac-11e0-85be-406186cbf87c +ControlPoint52Name:SB-Gate DW 59 green and DW 56 red +ControlPoint52HasTwoPoints:0 +ControlPoint52ShortName:59 +ControlPoint52Mark1Lon:10.780000 +ControlPoint52Mark1Lat:54.736667 + +ControlPoint53UUID:e59815c6-9b64-11e0-85be-406186cbf87c +ControlPoint53Name:K Finish +ControlPoint53HasTwoPoints:1 +ControlPoint53ShortName:F +ControlPoint53Mark1Lon:10.163997 +ControlPoint53Mark1Lat:54.499787 +ControlPoint53Mark2Lon:10.163545 +ControlPoint53Mark2Lat:54.500347 + +ControlPoint54UUID:e663db6c-9a8c-11e0-85be-406186cbf87c +ControlPoint54Name:49b4-1 +ControlPoint54HasTwoPoints:0 +ControlPoint54ShortName:1 +ControlPoint54Mark1Lon:10.196146 +ControlPoint54Mark1Lat:54.441407 + +ControlPoint55UUID:f214ea50-992e-11e0-85be-406186cbf87c +ControlPoint55Name:Gate between Mittelgrund-N +ControlPoint55HasTwoPoints:0 +ControlPoint55ShortName:MN +ControlPoint55Mark1Lon:10.055000 +ControlPoint55Mark1Lat:54.513333 + +ControlPoint56UUID:f641d92e-9ab6-11e0-85be-406186cbf87c +ControlPoint56Name:49Y-R8-1b +ControlPoint56HasTwoPoints:0 +ControlPoint56ShortName:1 +ControlPoint56Mark1Lon:10.203712 +ControlPoint56Mark1Lat:54.434916 + +ControlPoint57UUID:f76bea2c-9a95-11e0-85be-406186cbf87c +ControlPoint57Name:DIM1-F +ControlPoint57HasTwoPoints:1 +ControlPoint57ShortName:F +ControlPoint57Mark1Lon:9.850912 +ControlPoint57Mark1Lat:54.474097 +ControlPoint57Mark2Lon:9.849851 +ControlPoint57Mark2Lat:54.472319 + + +SegmentFile:segments.php +Route1UUID:2a75ee72-9dde-11e0-85be-406186cbf87c +Route1Description:505 Race 4 +Route1Length:0 +Route1ControlPoint1UUID:65b2dfbc-9b64-11e0-85be-406186cbf87c +Route1ControlPoint2UUID:7a218886-9b64-11e0-85be-406186cbf87c +Route1ControlPoint3UUID:bfe4dc42-9b64-11e0-85be-406186cbf87c +Route1ControlPoint4UUID:7a218886-9b64-11e0-85be-406186cbf87c +Route1ControlPoint5UUID:bfe4dc42-9b64-11e0-85be-406186cbf87c +Route1ControlPoint6UUID:e59815c6-9b64-11e0-85be-406186cbf87c + +//-------- AUTO GENERATED PARAMETERS END ------------------ diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupRetrievalProcessor.java similarity index 53% rename from java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupFilteringRetrievalProcessor.java rename to java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupRetrievalProcessor.java index 168a93419ab..c2a1de1a759 100644 --- a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupRetrievalProcessor.java @@ -16,25 +16,20 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; -import com.sap.sailing.datamining.data.LeaderboardGroupWithContext; -import com.sap.sailing.datamining.impl.data.HasLeaderboardGroupContextImpl; -import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContextImpl; import com.sap.sailing.datamining.test.util.ConcurrencyTestsUtil; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; import com.sap.sailing.server.RacingEventService; import com.sap.sse.datamining.AdditionalResultDataBuilder; -import com.sap.sse.datamining.components.FilterCriteria; import com.sap.sse.datamining.components.Processor; -public class TestLeaderboardGroupFilteringRetrievalProcessor { +public class TestLeaderboardGroupRetrievalProcessor { private RacingEventService service; private Processor retriever; - private Collection retrievedGroups; + private Collection retrievedGroups; @Before public void initializeComponents() { @@ -42,12 +37,15 @@ public class TestLeaderboardGroupFilteringRetrievalProcessor { stub(service.getLeaderboardGroups()).toReturn(getGroupsInService()); retrievedGroups = new HashSet<>(); - Processor receiver = new Processor() { + Processor receiver = new Processor() { @Override - public void onElement(LeaderboardGroupWithContext element) { + public void processElement(LeaderboardGroup element) { retrievedGroups.add(element); } @Override + public void onFailure(Throwable failure) { + } + @Override public void finish() throws InterruptedException { } @Override @@ -59,14 +57,7 @@ public class TestLeaderboardGroupFilteringRetrievalProcessor { } }; - FilterCriteria criteria = new FilterCriteria() { - @Override - public boolean matches(LeaderboardGroupWithContext element) { - String name = element.getLeaderboardGroupName(); - return name == "LG1" || name == "LG2" || name == "LG3"; - } - }; - retriever = new LeaderboardGroupFilteringRetrievalProcessor(ConcurrencyTestsUtil.getExecutor(), Arrays.asList(receiver), criteria); + retriever = new LeaderboardGroupRetrievalProcessor(ConcurrencyTestsUtil.getExecutor(), Arrays.asList(receiver)); } private Map getGroupsInService() { @@ -82,26 +73,21 @@ public class TestLeaderboardGroupFilteringRetrievalProcessor { @Ignore @Test public void testFilteringRetrieval() throws InterruptedException { - retriever.onElement(service); + retriever.processElement(service); retriever.finish(); - Collection expectedGroups = getExpectedRetrievedGroups(); + Collection expectedGroups = getExpectedRetrievedGroups(); assertThat(retrievedGroups, is(expectedGroups)); } - private Collection getExpectedRetrievedGroups() { - Collection groups = new HashSet<>(); - groups.add(groupToGroupWithContext(new LeaderboardGroupImpl("LG1", "", false, new ArrayList()))); - groups.add(groupToGroupWithContext(new LeaderboardGroupImpl("LG2", "", false, new ArrayList()))); - groups.add(groupToGroupWithContext(new LeaderboardGroupImpl("LG3", "", false, new ArrayList()))); - groups.add(groupToGroupWithContext(new LeaderboardGroupImpl("LG4", "", false, new ArrayList()))); - groups.add(groupToGroupWithContext(new LeaderboardGroupImpl("LG5", "", false, new ArrayList()))); + private Collection getExpectedRetrievedGroups() { + Collection groups = new HashSet<>(); + groups.add(new LeaderboardGroupImpl("LG1", "", false, new ArrayList())); + groups.add(new LeaderboardGroupImpl("LG2", "", false, new ArrayList())); + groups.add(new LeaderboardGroupImpl("LG3", "", false, new ArrayList())); + groups.add(new LeaderboardGroupImpl("LG4", "", false, new ArrayList())); + groups.add(new LeaderboardGroupImpl("LG5", "", false, new ArrayList())); return groups; } - private LeaderboardGroupWithContext groupToGroupWithContext(LeaderboardGroupImpl leaderboardGroup) { - HasLeaderboardGroupContext context = new HasLeaderboardGroupContextImpl(leaderboardGroup); - return new LeaderboardGroupWithContextImpl(context); - } - } diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java new file mode 100644 index 00000000000..761ecbc6a6a --- /dev/null +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java @@ -0,0 +1,103 @@ +package com.sap.sailing.datamining.impl.functions; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.impl.data.SailingDataMiningClassesWithFunctionsService; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Moving; +import com.sap.sailing.domain.base.Nationality; +import com.sap.sailing.domain.base.Team; +import com.sap.sailing.domain.common.Speed; +import com.sap.sse.common.Named; +import com.sap.sse.datamining.factories.FunctionFactory; +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.FunctionRegistry; +import com.sap.sse.datamining.impl.functions.SimpleFunctionRegistry; + +public class TestSailingFunctionsRegistration { + + private static FunctionRegistry functionRegistry; + + @BeforeClass + public static void setUpFunctionRegistryAndProvider() { + SailingDataMiningClassesWithFunctionsService classesWithFunctionsService = new SailingDataMiningClassesWithFunctionsService(); + + functionRegistry = new SimpleFunctionRegistry(); + functionRegistry.registerAllWithInternalFunctionPolicy(classesWithFunctionsService.getInternalClassesWithMarkedMethods()); + } + + @Test + public void testImportantRegisteredDimensions() throws NoSuchMethodException, SecurityException { + Collection> expectedDimensions = createExpectedImportantRegisteredDimensions(); + Collection> providedDimensions = functionRegistry.getDimensions(); + for (Function dimension : expectedDimensions) { + assertThat("The expected dimension '" + dimension + "' wasn't provided.", + providedDimensions.contains(dimension), is(true)); + } + } + + private Collection> createExpectedImportantRegisteredDimensions() throws NoSuchMethodException, SecurityException { + Collection> expectedDimensions = new HashSet<>(); + + Method getRegattaMethod = HasTrackedRaceContext.class.getMethod("getRegatta", new Class[0]); + Function getRegatta = FunctionFactory.createMethodWrappingFunction(getRegattaMethod); + Method getNameMethod = Named.class.getMethod("getName", new Class[0]); + Function getName = FunctionFactory.createMethodWrappingFunction(getNameMethod); + expectedDimensions.add(FunctionFactory.createCompoundFunction(Arrays.asList(getRegatta, getName))); + + Method getYearMethod = HasTrackedRaceContext.class.getMethod("getYear", new Class[0]); + expectedDimensions.add(FunctionFactory.createMethodWrappingFunction(getYearMethod)); + + Method getCompetitorMethod = HasTrackedLegOfCompetitorContext.class.getMethod("getCompetitor", new Class[0]); + Function getCompetitor = FunctionFactory.createMethodWrappingFunction(getCompetitorMethod); + Method getTeamMethod = Competitor.class.getMethod("getTeam", new Class[0]); + Function getTeam = FunctionFactory.createMethodWrappingFunction(getTeamMethod); + Method getNationalityMethod = Team.class.getMethod("getNationality", new Class[0]); + Function getNationality = FunctionFactory.createMethodWrappingFunction(getNationalityMethod); + Method getAcronymMethod = Nationality.class.getMethod("getThreeLetterIOCAcronym", new Class[0]); + Function getAcronym = FunctionFactory.createMethodWrappingFunction(getAcronymMethod); + expectedDimensions.add(FunctionFactory.createCompoundFunction(Arrays.asList(getCompetitor, getTeam, getNationality, getAcronym))); + + return expectedDimensions; + } + + @Test + public void testImportantRegisteredStatistics() throws NoSuchMethodException, SecurityException { + Collection> expectedStatistics = createExpectedImportantRegisteredStatistics(); + Collection> providedStatistics = functionRegistry.getStatistics(); + for (Function statistic : expectedStatistics) { + assertThat("The expected statistic '" + statistic + "' wasn't provided.", + providedStatistics.contains(statistic), is(true)); + } + } + + private Collection> createExpectedImportantRegisteredStatistics() throws NoSuchMethodException, SecurityException { + Collection> expectedStatistics = new HashSet<>(); + + Method getDistanceTraveledMethod = HasTrackedLegOfCompetitorContext.class.getMethod("getDistanceTraveled", new Class[0]); + expectedStatistics.add(FunctionFactory.createMethodWrappingFunction(getDistanceTraveledMethod)); + + Method getGPSFixMethod = HasGPSFixContext.class.getMethod("getGPSFix", new Class[0]); + Function getGPSFix = FunctionFactory.createMethodWrappingFunction(getGPSFixMethod); + Method getSpeedMethod = Moving.class.getMethod("getSpeed", new Class[0]); + Function getSpeed = FunctionFactory.createMethodWrappingFunction(getSpeedMethod); + Method getKnotsMethod = Speed.class.getMethod("getKnots", new Class[0]); + Function getKnots = FunctionFactory.createMethodWrappingFunction(getKnotsMethod); + expectedStatistics.add(FunctionFactory.createCompoundFunction(Arrays.asList(getGPSFix, getSpeed, getKnots))); + + return expectedStatistics; + } + +} diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/TestClusters.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/TestClusters.java deleted file mode 100644 index da95ff3d53e..00000000000 --- a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/TestClusters.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.sap.sailing.datamining.test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -import com.sap.sailing.datamining.Clusters; -import com.sap.sse.datamining.data.deprecated.ClusterOfComparable; -import com.sap.sse.datamining.impl.data.deprecated.ClusterOfComparableImpl; - -public class TestClusters { - - @Test - public void testClusterOfComparable() { - ClusterOfComparable cluster = new ClusterOfComparableImpl("Test", 3, 1); - - assertTrue(cluster.isInRange(1)); - assertTrue(cluster.isInRange(2)); - assertTrue(cluster.isInRange(3)); - - assertFalse(cluster.isInRange(null)); - assertFalse(cluster.isInRange(0)); - assertFalse(cluster.isInRange(4)); - } - - @Test - public void testWindStrengthClusters() { - assertNull(Clusters.WindStrength.getClusterFor(15, Clusters.WindStrength.StandardClusters)); - assertEquals(Clusters.WindStrength.VeryLight, Clusters.WindStrength.getClusterFor(1, Clusters.WindStrength.StandardClusters)); - } - -} diff --git a/java/com.sap.sailing.datamining/META-INF/MANIFEST.MF b/java/com.sap.sailing.datamining/META-INF/MANIFEST.MF index 55ba7a730cb..0a80e84b7a5 100644 --- a/java/com.sap.sailing.datamining/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.datamining/META-INF/MANIFEST.MF @@ -14,6 +14,7 @@ Require-Bundle: com.sap.sailing.domain;bundle-version="1.0.0", com.sap.sailing.domain.common, com.sap.sailing.datamining.shared;bundle-version="1.0.0", com.sap.sse.datamining;bundle-version="1.0.0", - com.sap.sse.datamining.shared;bundle-version="1.0.0" + com.sap.sse.datamining.shared;bundle-version="1.0.0", + com.sap.sse.common Bundle-ClassPath: . -Bundle-Activator: com.sap.sailing.datamining.impl.Activator +Bundle-Activator: com.sap.sailing.datamining.Activator diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java new file mode 100644 index 00000000000..1b03fd245e6 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java @@ -0,0 +1,38 @@ +package com.sap.sailing.datamining; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +import com.sap.sailing.datamining.impl.data.SailingDataMiningClassesWithFunctionsService; +import com.sap.sse.datamining.ClassesWithFunctionsService; + +public class Activator implements BundleActivator { + + public static final String dataRetrieverGroupName = "Sailing"; + + private static BundleContext context; + + private ServiceRegistration classesWithFunctionsServiceRegistration; + + @Override + public void start(BundleContext context) throws Exception { + Activator.context = context; + registerClassesWithFunctionsService(); + } + + @Override + public void stop(BundleContext context) throws Exception { + unregisterClassesWithFunctionsService(); + } + + private void unregisterClassesWithFunctionsService() { + context.ungetService(classesWithFunctionsServiceRegistration.getReference()); + } + + private void registerClassesWithFunctionsService() { + classesWithFunctionsServiceRegistration = context.registerService(ClassesWithFunctionsService.class, + new SailingDataMiningClassesWithFunctionsService(), null); + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Clusters.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Clusters.java deleted file mode 100644 index 6d216820d7b..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Clusters.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.sap.sailing.datamining; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import com.sap.sailing.datamining.impl.WindStrengthClusterImpl; - -public class Clusters { - - private Clusters() { } - - public static class WindStrength { - - public static WindStrengthCluster VeryLight = new WindStrengthClusterImpl("Very Light", 0.0, 2.5); - public static WindStrengthCluster Light = new WindStrengthClusterImpl("Very Light", 2.5, 5.0); - public static WindStrengthCluster Medium = new WindStrengthClusterImpl("Very Light", 5.0, 7.5); - public static WindStrengthCluster Strong = new WindStrengthClusterImpl("Very Light", 7.5, 10.0); - public static WindStrengthCluster VeryStrong = new WindStrengthClusterImpl("Very Light", 10.0, 12.0); - public static Collection StandardClusters = Arrays.asList(VeryLight, Light, Medium, Strong, VeryStrong); - - private WindStrength() { } - - public static WindStrengthCluster getClusterFor(double windStrengthInBeafort, Collection clusters) { - List sortedClusters = new ArrayList(clusters); - Collections.sort(sortedClusters, new Comparator() { - @Override - public int compare(WindStrengthCluster cluster1, WindStrengthCluster cluster2) { - return cluster1.getUpperRange().compareTo(cluster2.getUpperRange()); - } - }); - - for (WindStrengthCluster cluster : sortedClusters) { - if (cluster.isInRange(windStrengthInBeafort)) { - return cluster; - } - } - return null; - } - - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/DeprecatedEnumsToFunctionDTOConverter.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/DeprecatedEnumsToFunctionDTOConverter.java new file mode 100644 index 00000000000..1791284ba3f --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/DeprecatedEnumsToFunctionDTOConverter.java @@ -0,0 +1,105 @@ +package com.sap.sailing.datamining; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasTrackedLegContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.shared.DimensionIdentifier; +import com.sap.sailing.datamining.shared.StatisticType; +import com.sap.sailing.domain.common.LegType; +import com.sap.sse.datamining.shared.dto.FunctionDTO; +import com.sap.sse.datamining.shared.impl.dto.FunctionDTOImpl; + +public class DeprecatedEnumsToFunctionDTOConverter { + + private static Map statisticTypeToFunctionDTOMap = new HashMap<>(); + private static Map dimensionIdentifierToFunctionDTOMap = new HashMap<>(); + + static { + for (StatisticType statisticType : StatisticType.values()) { + FunctionDTO functionDTO = null; + switch (statisticType) { + case Distance: + functionDTO = new FunctionDTOImpl("getDistanceTraveled", HasTrackedLegOfCompetitorContext.class.getSimpleName(), "Double", + new ArrayList(), "Distance", false); + break; + case Speed: + functionDTO = new FunctionDTOImpl("getGPSFix -> getSpeed -> getKnots", HasGPSFixContext.class.getSimpleName(), + "double", new ArrayList(), "Speed", false); + break; + } + statisticTypeToFunctionDTOMap.put(statisticType, functionDTO); + } + + for (DimensionIdentifier dimensionIdentifier : DimensionIdentifier.values()) { + FunctionDTO functionDTO = null; + switch (dimensionIdentifier) { + case BoatClassName: + functionDTO = new FunctionDTOImpl("getBoatClass -> getName", HasTrackedRaceContext.class.getSimpleName(), + String.class.getSimpleName(), new ArrayList(), "Boat Class Name", true); + break; + case CompetitorName: + functionDTO = new FunctionDTOImpl("getCompetitor -> getTeam -> getName", HasTrackedLegOfCompetitorContext.class.getSimpleName(), + String.class.getSimpleName(), new ArrayList(), "Competitor Name", true); + break; + case CourseAreaName: + functionDTO = new FunctionDTOImpl("getCourseArea -> getName", HasTrackedRaceContext.class.getSimpleName(), + String.class.getSimpleName(), new ArrayList(), "Course Area Name", true); + break; + case FleetName: + functionDTO = new FunctionDTOImpl("getFleet -> getName", HasTrackedRaceContext.class.getSimpleName(), + String.class.getSimpleName(), new ArrayList(), "Fleet Name", true); + break; + case LegNumber: + functionDTO = new FunctionDTOImpl("getLegNumber", HasTrackedLegContext.class.getSimpleName(), + "int", new ArrayList(), "Leg Number", true); + break; + case LegType: + functionDTO = new FunctionDTOImpl("getLegType", HasTrackedLegContext.class.getSimpleName(), + LegType.class.getSimpleName(), new ArrayList(), "Leg Type", true); + break; + case Nationality: + functionDTO = new FunctionDTOImpl("getCompetitor -> getTeam -> getNationality -> getThreeLetterIOCAcronym", HasTrackedLegOfCompetitorContext.class.getSimpleName(), + String.class.getSimpleName(), new ArrayList(), "Nationality", true); + break; + case RaceName: + functionDTO = new FunctionDTOImpl("getRace -> getName", HasTrackedRaceContext.class.getSimpleName(), + String.class.getSimpleName(), new ArrayList(), "Race Name", true); + break; + case RegattaName: + functionDTO = new FunctionDTOImpl("getRegatta -> getName", HasTrackedRaceContext.class.getSimpleName(), + String.class.getSimpleName(), new ArrayList(), "Regattay Name", true); + break; + case SailID: + functionDTO = new FunctionDTOImpl("getCompetitor -> getBoat -> getSailID", HasTrackedLegOfCompetitorContext.class.getSimpleName(), + String.class.getSimpleName(), new ArrayList(), "Sail ID", true); + break; + case WindStrength: + break; + case Year: + functionDTO = new FunctionDTOImpl("getYear", HasTrackedRaceContext.class.getSimpleName(), + "Integer", new ArrayList(), "Year", true); + break; + } + if (functionDTO != null) { + dimensionIdentifierToFunctionDTOMap.put(dimensionIdentifier, functionDTO); + } + } + } + + public static FunctionDTO getFunctionDTOFor(StatisticType statisticType) { + return statisticTypeToFunctionDTOMap.get(statisticType); + } + + public static FunctionDTO getFunctionDTOFor(DimensionIdentifier dimensionIdentifier) { + return dimensionIdentifierToFunctionDTOMap.get(dimensionIdentifier); + } + + private DeprecatedEnumsToFunctionDTOConverter() { + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/QueryDefinitionConverter.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/QueryDefinitionConverter.java new file mode 100644 index 00000000000..53cc44fab2a --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/QueryDefinitionConverter.java @@ -0,0 +1,35 @@ +package com.sap.sailing.datamining; + +import java.util.Map.Entry; + +import com.sap.sailing.datamining.shared.DimensionIdentifier; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; +import com.sap.sse.datamining.shared.QueryDefinition; +import com.sap.sse.datamining.shared.impl.QueryDefinitionImpl; + +public class QueryDefinitionConverter { + + public static QueryDefinition convertDeprecatedQueryDefinition(QueryDefinitionDeprecated queryDefinitionDeprecated) { + QueryDefinitionImpl queryDefinition = new QueryDefinitionImpl(queryDefinitionDeprecated.getLocaleInfoName(), + DeprecatedEnumsToFunctionDTOConverter.getFunctionDTOFor(queryDefinitionDeprecated.getStatisticType()), + queryDefinitionDeprecated.getAggregatorType()); + + for (Entry> filterSelectionEntry : queryDefinitionDeprecated.getSelection() + .entrySet()) { + queryDefinition.setFilterSelectionFor( + DeprecatedEnumsToFunctionDTOConverter.getFunctionDTOFor(filterSelectionEntry.getKey()), + filterSelectionEntry.getValue()); + } + + for (DimensionIdentifier dimensionIdentifier : queryDefinitionDeprecated.getDimensionsToGroupBy()) { + queryDefinition.appendDimensionToGroupBy(DeprecatedEnumsToFunctionDTOConverter + .getFunctionDTOFor(dimensionIdentifier)); + } + + return queryDefinition; + } + + private QueryDefinitionConverter() { + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/WindStrengthCluster.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/WindStrengthCluster.java deleted file mode 100644 index 39d701d738f..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/WindStrengthCluster.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.sap.sailing.datamining; - -import com.sap.sse.datamining.data.deprecated.ClusterOfComparable; - -public interface WindStrengthCluster extends ClusterOfComparable { - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/GPSFixWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/GPSFixWithContext.java deleted file mode 100644 index 9eff07bbd22..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/GPSFixWithContext.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.sap.sailing.datamining.data; - -import com.sap.sailing.datamining.WindStrengthCluster; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sse.datamining.shared.annotations.Dimension; - -public interface GPSFixWithContext extends GPSFixMoving { - - @Dimension(messageKey="regatta") - public String getRegattaName(); - - @Dimension(messageKey="race") - public String getRaceName(); - - @Dimension(messageKey="legNumber") - public int getLegNumber(); - - @Dimension(messageKey="course") - public String getCourseAreaName(); - - @Dimension(messageKey="fleet") - public String getFleetName(); - - @Dimension(messageKey="boatClass") - public String getBoatClassName(); - - /** - * The year of the start of the race. - */ - @Dimension(messageKey="year") - public Integer getYear(); - - /** - * The leg type of the leg, which contains this gps fix. Can be null if there's no wind in the race. - */ - @Dimension(messageKey="legType") - public LegType getLegType(); - - @Dimension(messageKey="competitor") - public String getCompetitorName(); - - @Dimension(messageKey="sailID") - public String getCompetitorSailID(); - - @Dimension(messageKey="nationality") - public String getCompetitorNationality(); - - @Dimension(messageKey="windStrength") - public WindStrengthCluster getWindStrength(); - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java index 475a425bfda..ec26746969d 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java @@ -1,5 +1,11 @@ package com.sap.sailing.datamining.data; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sse.datamining.shared.annotations.Connector; + public interface HasGPSFixContext extends HasTrackedLegOfCompetitorContext { + + @Connector + public GPSFixMoving getGPSFix(); } \ No newline at end of file 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 deleted file mode 100644 index b2451294b7f..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardContext.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sap.sailing.datamining.data; - -import com.sap.sailing.domain.leaderboard.Leaderboard; - -public interface HasLeaderboardContext extends HasLeaderboardGroupContext { - - public Leaderboard getLeaderboard(); - -} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardGroupContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardGroupContext.java deleted file mode 100644 index cbdbcd23ab8..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardGroupContext.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sap.sailing.datamining.data; - -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; - -public interface HasLeaderboardGroupContext { - - public LeaderboardGroup getLeaderboardGroup(); - -} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegContext.java index c918082a020..ba1bf249c64 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegContext.java @@ -2,11 +2,15 @@ package com.sap.sailing.datamining.data; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sse.datamining.shared.annotations.Dimension; public interface HasTrackedLegContext extends HasTrackedRaceContext { public TrackedLeg getTrackedLeg(); + + @Dimension(messageKey="LegType") public LegType getLegType(); + @Dimension(messageKey="LegNumber") public int getLegNumber(); } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegOfCompetitorContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegOfCompetitorContext.java index e1f836f5d13..7e5bca77eff 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegOfCompetitorContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegOfCompetitorContext.java @@ -1,9 +1,19 @@ package com.sap.sailing.datamining.data; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sse.datamining.shared.Unit; +import com.sap.sse.datamining.shared.annotations.Connector; +import com.sap.sse.datamining.shared.annotations.Statistic; public interface HasTrackedLegOfCompetitorContext extends HasTrackedLegContext { + + public TrackedLegOfCompetitor getTrackedLegOfCompetitor(); + @Connector public Competitor getCompetitor(); + + @Statistic(messageKey="DistanceTraveled", resultUnit=Unit.Meters, resultDecimals=0) + public Double getDistanceTraveled(); } \ No newline at end of file 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 b5301a27830..335891d3754 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 @@ -1,14 +1,38 @@ package com.sap.sailing.datamining.data; +import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.datamining.shared.annotations.Dimension; +import com.sap.sse.datamining.shared.annotations.Connector; -public interface HasTrackedRaceContext extends HasLeaderboardContext { - - public CourseArea getCourseArea(); - public Fleet getFleet(); +public interface HasTrackedRaceContext { + public TrackedRace getTrackedRace(); + + @Connector + public Event getEvent(); + + @Connector + public Regatta getRegatta(); + + @Connector + public CourseArea getCourseArea(); + + @Connector + public BoatClass getBoatClass(); + + @Connector + public Fleet getFleet(); + + @Connector + public RaceDefinition getRace(); + + @Dimension(messageKey="Year") public Integer getYear(); } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/LeaderboardGroupWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/LeaderboardGroupWithContext.java deleted file mode 100644 index 9ff5a9abeed..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/LeaderboardGroupWithContext.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.sap.sailing.datamining.data; - -import com.sap.sse.datamining.shared.annotations.Dimension; - -public interface LeaderboardGroupWithContext { - - @Dimension(messageKey="leaderboardGroup") - public String getLeaderboardGroupName(); - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/TrackedLegOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/TrackedLegOfCompetitorWithContext.java deleted file mode 100644 index 8499ce8d244..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/TrackedLegOfCompetitorWithContext.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.sap.sailing.datamining.data; - -import com.sap.sailing.datamining.WindStrengthCluster; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.LegType; -import com.sap.sse.datamining.shared.Unit; -import com.sap.sse.datamining.shared.annotations.Dimension; -import com.sap.sse.datamining.shared.annotations.SideEffectFreeValue; - -public interface TrackedLegOfCompetitorWithContext { - - @Dimension(messageKey="regatta") - public String getRegattaName(); - - @Dimension(messageKey="race") - public String getRaceName(); - - @Dimension(messageKey="legNumber") - public int getLegNumber(); - - @Dimension(messageKey="course") - public String getCourseAreaName(); - - @Dimension(messageKey="fleet") - public String getFleetName(); - - @Dimension(messageKey="boatClass") - public String getBoatClassName(); - - /** - * The year of the start of the race. - */ - @Dimension(messageKey="year") - public Integer getYear(); - - @Dimension(messageKey="legType") - public LegType getLegType(); - - @Dimension(messageKey="competitor") - public String getCompetitorName(); - - @Dimension(messageKey="sailID") - public String getCompetitorSailID(); - - @Dimension(messageKey="nationality") - public String getCompetitorNationality(); - - @Dimension(messageKey="windStrength") - public WindStrengthCluster getWindStrength(); - - @SideEffectFreeValue(messageKey="distanceTraveled", resultUnit=Unit.Meters) - public Distance getDistanceTraveled(); - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/DimensionManager.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/DimensionManager.java deleted file mode 100644 index b5fe6dbbb27..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/DimensionManager.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.sap.sailing.datamining.dimensions; - -import com.sap.sailing.datamining.shared.DimensionIdentifier; -import com.sap.sse.datamining.data.deprecated.Dimension; - -public interface DimensionManager { - - public Dimension getDimensionFor(DimensionIdentifier sharedDimension); - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/DimensionManagerProvider.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/DimensionManagerProvider.java deleted file mode 100644 index cfce45cbf80..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/DimensionManagerProvider.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.datamining.dimensions; - -import com.sap.sailing.datamining.data.GPSFixWithContext; -import com.sap.sailing.datamining.data.TrackedLegOfCompetitorWithContext; -import com.sap.sailing.datamining.shared.DataTypes; - -public final class DimensionManagerProvider { - - private DimensionManagerProvider() { } - - private static final DimensionManager GPSFixDimensionManager = new GPSFixDimensionManager(); - private static final DimensionManager TrackedLegOfCompetitorDimensionManager = new TrackedLegOfCompetitorDimensionManager(); - - @SuppressWarnings("unchecked") - public static DimensionManager getDimensionManagerFor(DataTypes dataType) { - switch (dataType) { - case GPSFix: - return (DimensionManager) GPSFixDimensionManager; - case TrackedLegOfCompetitor: - return (DimensionManager) TrackedLegOfCompetitorDimensionManager; - } - throw new IllegalArgumentException("Not yet implemented for the given data type: " - + dataType.toString()); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/GPSFixDimensionManager.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/GPSFixDimensionManager.java deleted file mode 100644 index 25fdaa879ef..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/GPSFixDimensionManager.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.sap.sailing.datamining.dimensions; - -import com.sap.sailing.datamining.data.GPSFixWithContext; -import com.sap.sailing.datamining.shared.DimensionIdentifier; -import com.sap.sailing.domain.common.LegType; -import com.sap.sse.datamining.data.deprecated.Dimension; -import com.sap.sse.datamining.impl.data.deprecated.AbstractDimension; - -public final class GPSFixDimensionManager implements DimensionManager { - - public GPSFixDimensionManager() { } - - /** - * @return The dimension for the given dimension type. Throws an exception, if the used ValueType doesn't match the ValueType of the returning dimension. - */ - @Override - public Dimension getDimensionFor(DimensionIdentifier dimension) { - switch (dimension) { - case BoatClassName: - return BoatClassNameDimension; - case CompetitorName: - return CompetitorNameDimension; - case CourseAreaName: - return CourseAreaNameDimension; - case FleetName: - return FleetNameDimension; - case LegNumber: - return LegNumberDimension; - case LegType: - return LegTypeDimension; - case Nationality: - return NationalityDimension; - case RaceName: - return RaceNameDimension; - case RegattaName: - return RegattaNameDimension; - case SailID: - return SailIDDimension; - case WindStrength: - return WindStrengthDimension; - case Year: - return YearDimension; - } - throw new IllegalArgumentException("Not yet implemented for the given dimension: " - + dimension.toString()); - } - - public final static Dimension RegattaNameDimension = new AbstractDimension("Regatta") { - @Override - public String getDimensionValueFrom(GPSFixWithContext data) { - return data.getRegattaName(); - } - }; - - public final static Dimension RaceNameDimension = new AbstractDimension("Race") { - @Override - public String getDimensionValueFrom(GPSFixWithContext data) { - return data.getRaceName(); - } - }; - - public final static Dimension LegNumberDimension = new AbstractDimension("Leg Number") { - @Override - public Integer getDimensionValueFrom(GPSFixWithContext data) { - return data.getLegNumber(); - } - }; - - public final static Dimension CourseAreaNameDimension = new AbstractDimension("Course Area") { - @Override - public String getDimensionValueFrom(GPSFixWithContext data) { - return data.getCourseAreaName(); - } - }; - - public final static Dimension FleetNameDimension = new AbstractDimension("Fleet") { - @Override - public String getDimensionValueFrom(GPSFixWithContext data) { - return data.getFleetName(); - } - }; - - public final static Dimension BoatClassNameDimension = new AbstractDimension("Boat Class") { - @Override - public String getDimensionValueFrom(GPSFixWithContext data) { - return data.getBoatClassName(); - } - }; - - public final static Dimension YearDimension = new AbstractDimension("Year") { - @Override - public Integer getDimensionValueFrom(GPSFixWithContext data) { - return data.getYear(); - } - }; - - public final static Dimension LegTypeDimension = new AbstractDimension("Leg Type") { - @Override - public LegType getDimensionValueFrom(GPSFixWithContext data) { - return data.getLegType(); - } - }; - - public final static Dimension CompetitorNameDimension = new AbstractDimension("Competitor") { - @Override - public String getDimensionValueFrom(GPSFixWithContext data) { - return data.getCompetitorName(); - } - }; - - public final static Dimension SailIDDimension = new AbstractDimension("Sail ID") { - @Override - public String getDimensionValueFrom(GPSFixWithContext data) { - return data.getCompetitorSailID(); - } - }; - - public final static Dimension NationalityDimension = new AbstractDimension("Nationality") { - @Override - public String getDimensionValueFrom(GPSFixWithContext data) { - return data.getCompetitorNationality(); - } - }; - - //TODO after new clusters - public final static Dimension WindStrengthDimension = new AbstractDimension("Wind Strength") { - @Override - public String getDimensionValueFrom(GPSFixWithContext data) { - return data.getWindStrength().getName(); - } - }; - -} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/TrackedLegOfCompetitorDimensionManager.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/TrackedLegOfCompetitorDimensionManager.java deleted file mode 100644 index e389758cb9a..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/dimensions/TrackedLegOfCompetitorDimensionManager.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.sap.sailing.datamining.dimensions; - -import com.sap.sailing.datamining.data.TrackedLegOfCompetitorWithContext; -import com.sap.sailing.datamining.shared.DimensionIdentifier; -import com.sap.sailing.domain.common.LegType; -import com.sap.sse.datamining.data.deprecated.Dimension; -import com.sap.sse.datamining.impl.data.deprecated.AbstractDimension; - -public final class TrackedLegOfCompetitorDimensionManager implements DimensionManager { - - public TrackedLegOfCompetitorDimensionManager() { } - - /** - * @return The dimension for the given dimension type. Throws an exception, if the used ValueType doesn't match the ValueType of the returning dimension. - */ - public Dimension getDimensionFor(DimensionIdentifier dimension) { - switch (dimension) { - case BoatClassName: - return BoatClassNameDimension; - case CompetitorName: - return CompetitorNameDimension; - case CourseAreaName: - return CourseAreaNameDimension; - case FleetName: - return FleetNameDimension; - case LegNumber: - return LegNumberDimension; - case LegType: - return LegTypeDimension; - case Nationality: - return NationalityDimension; - case RaceName: - return RaceNameDimension; - case RegattaName: - return RegattaNameDimension; - case SailID: - return SailIDDimension; - case WindStrength: - return WindStrengthDimension; - case Year: - return YearDimension; - } - throw new IllegalArgumentException("Not yet implemented for the given dimension: " - + dimension.toString()); - } - - public final static Dimension RegattaNameDimension = new AbstractDimension("Regatta") { - @Override - public String getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getRegattaName(); - } - }; - - public final static Dimension RaceNameDimension = new AbstractDimension("Race") { - @Override - public String getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getRaceName(); - } - }; - - public final static Dimension LegNumberDimension = new AbstractDimension("Leg Number") { - @Override - public Integer getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getLegNumber(); - } - }; - - public final static Dimension CourseAreaNameDimension = new AbstractDimension("Course Area") { - @Override - public String getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getCourseAreaName(); - } - }; - - public final static Dimension FleetNameDimension = new AbstractDimension("Fleet") { - @Override - public String getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getFleetName(); - } - }; - - public final static Dimension BoatClassNameDimension = new AbstractDimension("Boat Class") { - @Override - public String getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getBoatClassName(); - } - }; - - public final static Dimension YearDimension = new AbstractDimension("Year") { - @Override - public Integer getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getYear(); - } - }; - - public final static Dimension LegTypeDimension = new AbstractDimension("Leg Type") { - @Override - public LegType getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getLegType(); - } - }; - - public final static Dimension CompetitorNameDimension = new AbstractDimension("Competitor") { - @Override - public String getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getCompetitorName(); - } - }; - - public final static Dimension SailIDDimension = new AbstractDimension("Sail ID") { - @Override - public String getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getCompetitorSailID(); - } - }; - - public final static Dimension NationalityDimension = new AbstractDimension("Nationality") { - @Override - public String getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getCompetitorNationality(); - } - }; - - //TODO after new clusters - public final static Dimension WindStrengthDimension = new AbstractDimension("Wind Strength") { - @Override - public String getDimensionValueFrom(TrackedLegOfCompetitorWithContext data) { - return data.getWindStrength().getName(); - } - }; - -} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/DataMiningFactory.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/DataMiningFactory.java index 595fb46bfda..1a769ed142b 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/DataMiningFactory.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/DataMiningFactory.java @@ -1,57 +1,67 @@ package com.sap.sailing.datamining.factories; -import java.util.Locale; +import java.util.ArrayList; +import java.util.List; -import com.sap.sailing.datamining.shared.QueryDefinition; -import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.datamining.QueryDefinitionConverter; +import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sse.datamining.Query; -import com.sap.sse.datamining.components.ParallelAggregator; -import com.sap.sse.datamining.components.ParallelDataRetriever; -import com.sap.sse.datamining.components.ParallelExtractor; -import com.sap.sse.datamining.components.ParallelFilter; -import com.sap.sse.datamining.components.ParallelGrouper; -import com.sap.sse.datamining.factories.AggregatorFactory; -import com.sap.sse.datamining.i18n.DataMiningStringMessages; -import com.sap.sse.datamining.impl.DataMiningActivator; -import com.sap.sse.datamining.impl.deprecated.QueryImpl; -import com.sap.sse.datamining.workers.FiltrationWorker; -import com.sap.sse.datamining.workers.WorkerBuilder; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.factories.ProcessorFactory; +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.FunctionProvider; +import com.sap.sse.datamining.impl.ProcessorQuery; +import com.sap.sse.datamining.impl.components.GroupedDataEntry; +import com.sap.sse.datamining.shared.QueryDefinition; +import com.sap.sse.datamining.shared.dto.FunctionDTO; public final class DataMiningFactory { - + private DataMiningFactory() { } - - public static Query createQuery( - QueryDefinition queryDefinition, RacingEventService racingService) { - Locale locale = DataMiningStringMessages.Util.getLocaleFrom(queryDefinition.getLocaleInfoName()); - - ParallelDataRetriever retriever = DataRetrieverFactory.createDataRetriever( - queryDefinition.getDataType(), racingService, DataMiningActivator.getExecutor()); - - ParallelFilter filter = createFilter(queryDefinition); - - ParallelGrouper grouper = GrouperFactory.createGrouper(queryDefinition, - DataMiningActivator.getExecutor()); - ParallelExtractor extractor = ExtractorFactory.createExtractor( - DataMiningActivator.getStringMessages(), locale, queryDefinition.getStatisticType(), - DataMiningActivator.getExecutor()); - ParallelAggregator aggregator = AggregatorFactory.createAggregator( - DataMiningActivator.getStringMessages(), locale, queryDefinition.getStatisticType().getValueType(), - queryDefinition.getAggregatorType(), DataMiningActivator.getExecutor()); - - return new QueryImpl(DataMiningActivator.getStringMessages(), locale, - retriever, filter, grouper, extractor, aggregator); + + // TODO Delete, after the deprecated components have been removed + public static Query createQuery(QueryDefinitionDeprecated queryDefinition, DataSourceType dataSource, FunctionProvider functionProvider) { + return createQuery(QueryDefinitionConverter.convertDeprecatedQueryDefinition(queryDefinition), dataSource, functionProvider); } - private static ParallelFilter createFilter(QueryDefinition queryDefinition) { - if (queryDefinition.getSelection().isEmpty()) { - return FilterFactory.createNonFilteringFilter(); - } + // TODO Make public, after the deprecated components have been removed + private static Query createQuery(QueryDefinition queryDefinition, DataSourceType dataSource, FunctionProvider functionProvider) { + ProcessorQuery query = ProcessorFactory.createProcessorQuery(dataSource, queryDefinition.getLocaleInfoName()); + Processor> aggregationProcessor = ProcessorFactory.createAggregationProcessor(query, queryDefinition.getAggregatorType()); + + @SuppressWarnings("unchecked") // TODO Clean, after the deprecated components have been removed + Function extractionFunction = (Function) functionProvider.getFunctionForDTO(queryDefinition.getExtractionFunction()); + Processor> extractionProcessor = ProcessorFactory.createExtractionProcessor(aggregationProcessor, extractionFunction); + + List> dimensionsToGroupBy = getDimensionsToGroupBy(queryDefinition, functionProvider); + Processor groupingProcessor = ProcessorFactory.createGroupingProcessor(extractionProcessor, dimensionsToGroupBy); + + SailingDataRetrievalLevels dataRetrievalLevel = calculateDataRetrievalLevel(extractionFunction); + Processor firstRetrievalProcessor = SailingDataRetrieverFactory.createRetrievalProcessorChain(dataRetrievalLevel, groupingProcessor, queryDefinition.getFilterSelection(), functionProvider); + + query.setFirstProcessor(firstRetrievalProcessor); + return query; + } - WorkerBuilder> workerBuilder = FilterFactory.createDimensionFilterBuilder( - queryDefinition.getDataType(), queryDefinition.getSelection()); - return FilterFactory.createParallelFilter(workerBuilder, DataMiningActivator.getExecutor()); + private static List> getDimensionsToGroupBy(QueryDefinition queryDefinition, FunctionProvider functionProvider) { + List> dimensionsToGroupBy = new ArrayList<>(); + for (FunctionDTO functionDTO : queryDefinition.getDimensionsToGroupBy()) { + dimensionsToGroupBy.add(functionProvider.getFunctionForDTO(functionDTO)); + } + return dimensionsToGroupBy; + } + + private static SailingDataRetrievalLevels calculateDataRetrievalLevel(Function extractionFunction) { + if (extractionFunction.getDeclaringType().equals(HasGPSFixContext.class)) { + return SailingDataRetrievalLevels.GPSFix; + } + if (extractionFunction.getDeclaringType().equals(HasTrackedLegOfCompetitorContext.class)) { + return SailingDataRetrievalLevels.TrackedLegOfCompetitor; + } + return SailingDataRetrievalLevels.GPSFix; } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/DataRetrieverFactory.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/DataRetrieverFactory.java deleted file mode 100644 index 1635da5fb2d..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/DataRetrieverFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.sap.sailing.datamining.factories; - -import java.util.concurrent.ThreadPoolExecutor; - -import com.sap.sailing.datamining.impl.components.GroupDividingParallelDataRetriever; -import com.sap.sailing.datamining.impl.workers.builders.DataRetrieverWorkerBuilder; -import com.sap.sailing.datamining.shared.DataTypes; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.server.RacingEventService; -import com.sap.sse.datamining.components.ParallelDataRetriever; -import com.sap.sse.datamining.workers.DataRetrievalWorker; -import com.sap.sse.datamining.workers.WorkerBuilder; - -public final class DataRetrieverFactory { - - private DataRetrieverFactory() { - } - - /** - * Creates a retriever for the given data type. Throws an exception, if the used DataType doesn't match - * the DataType of the returning retriever. - */ - public static ParallelDataRetriever createDataRetriever(DataTypes dataType, RacingEventService racingService, ThreadPoolExecutor executor) { - WorkerBuilder> workerBuilder = new DataRetrieverWorkerBuilder(dataType); - return new GroupDividingParallelDataRetriever(racingService, workerBuilder, executor); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/ExtractorFactory.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/ExtractorFactory.java deleted file mode 100644 index 3716ad2b970..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/ExtractorFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.sap.sailing.datamining.factories; - -import java.util.Locale; -import java.util.concurrent.ThreadPoolExecutor; - -import com.sap.sailing.datamining.impl.workers.builders.ExtractionWorkerBuilder; -import com.sap.sailing.datamining.shared.StatisticType; -import com.sap.sse.datamining.components.ParallelExtractor; -import com.sap.sse.datamining.i18n.DataMiningStringMessages; -import com.sap.sse.datamining.impl.components.deprecated.GroupDividingParallelExtractor; -import com.sap.sse.datamining.shared.Message; -import com.sap.sse.datamining.shared.Unit; -import com.sap.sse.datamining.workers.ExtractionWorker; -import com.sap.sse.datamining.workers.WorkerBuilder; - -public final class ExtractorFactory { - - private ExtractorFactory() { } - - public static ParallelExtractor createExtractor(DataMiningStringMessages stringMessages, Locale locale, - StatisticType statisticType, ThreadPoolExecutor executor) { - WorkerBuilder> workerBuilder = new ExtractionWorkerBuilder(statisticType); - String signifier = statisticType.getUnit() == Unit.None ? stringMessages.get(locale, statisticType.getSignifierMessage()) : - stringMessages.get(locale, Message.SignifierInUnit, statisticType.getSignifierMessage(), statisticType.getUnitMessage()); - return new GroupDividingParallelExtractor(signifier, statisticType.getUnit(), statisticType.getValueDecimals(), - workerBuilder, executor); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/FilterFactory.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/FilterFactory.java deleted file mode 100644 index af13bfdbb25..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/FilterFactory.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.sap.sailing.datamining.factories; - -import java.util.Collection; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ThreadPoolExecutor; - -import com.sap.sailing.datamining.dimensions.DimensionManager; -import com.sap.sailing.datamining.dimensions.DimensionManagerProvider; -import com.sap.sailing.datamining.shared.DataTypes; -import com.sap.sailing.datamining.shared.DimensionIdentifier; -import com.sap.sse.datamining.components.FilterCriteria; -import com.sap.sse.datamining.components.ParallelFilter; -import com.sap.sse.datamining.data.deprecated.Dimension; -import com.sap.sse.datamining.impl.components.deprecated.NonFilteringFilter; -import com.sap.sse.datamining.impl.components.deprecated.PartitioningParallelFilter; -import com.sap.sse.datamining.impl.criterias.AndCompoundFilterCriteria; -import com.sap.sse.datamining.impl.criterias.CompoundFilterCriteria; -import com.sap.sse.datamining.impl.criterias.deprecated.DimensionValuesFilterCriteria; -import com.sap.sse.datamining.impl.workers.builders.FilterByCriteriaBuilder; -import com.sap.sse.datamining.workers.FiltrationWorker; -import com.sap.sse.datamining.workers.WorkerBuilder; - -public final class FilterFactory { - - private FilterFactory() { } - - public static ParallelFilter createParallelFilter(WorkerBuilder> workerBuilder, ThreadPoolExecutor executor) { - return new PartitioningParallelFilter(workerBuilder, executor); - } - - public static WorkerBuilder> createDimensionFilterBuilder(DataTypes dataType, Map> selection) { - FilterCriteria criteria = createAndCompoundDimensionFilterCritera(dataType, selection); - WorkerBuilder> builder = new FilterByCriteriaBuilder(criteria); - return builder; - } - - /** - * @return A filter that filters nothing. So the returning collection is the same as the given one. - */ - public static ParallelFilter createNonFilteringFilter() { - return new NonFilteringFilter(); - } - - private static FilterCriteria createAndCompoundDimensionFilterCritera(DataTypes dataType, Map> selection) { - DimensionManager dimensionManager = DimensionManagerProvider.getDimensionManagerFor(dataType); - CompoundFilterCriteria compoundCriteria = new AndCompoundFilterCriteria(); - - for (Entry> entry : selection.entrySet()) { - Dimension dimension = dimensionManager.getDimensionFor(entry.getKey()); - if (dimension != null) { - FilterCriteria criteria = createDimensionFilterCriteria(dimension, entry.getValue()); - compoundCriteria.addCriteria(criteria); - } - } - return compoundCriteria; - } - - @SuppressWarnings("unchecked") - private static FilterCriteria createDimensionFilterCriteria(Dimension dimension, Iterable values) { - return new DimensionValuesFilterCriteria(dimension, (Collection) values); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/GrouperFactory.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/GrouperFactory.java deleted file mode 100644 index f2b28b311e7..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/GrouperFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.sap.sailing.datamining.factories; - -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.concurrent.ThreadPoolExecutor; - -import com.sap.sailing.datamining.dimensions.DimensionManager; -import com.sap.sailing.datamining.dimensions.DimensionManagerProvider; -import com.sap.sailing.datamining.shared.DataTypes; -import com.sap.sailing.datamining.shared.DimensionIdentifier; -import com.sap.sailing.datamining.shared.QueryDefinition; -import com.sap.sse.datamining.components.ParallelGrouper; -import com.sap.sse.datamining.data.deprecated.Dimension; -import com.sap.sse.datamining.impl.components.deprecated.PartitioningParallelGrouper; -import com.sap.sse.datamining.impl.workers.builders.MultiDimensionalGroupingWorkerBuilder; -import com.sap.sse.datamining.workers.GroupingWorker; -import com.sap.sse.datamining.workers.WorkerBuilder; - -public final class GrouperFactory { - - private GrouperFactory() { } - - public static ParallelGrouper createGrouper(QueryDefinition queryDefinition, ThreadPoolExecutor executor) { - WorkerBuilder> workerBuilder = createGroupingWorkerBuilder(queryDefinition); - return new PartitioningParallelGrouper(workerBuilder, executor); - } - - private static WorkerBuilder> createGroupingWorkerBuilder(QueryDefinition queryDefinition) { - switch (queryDefinition.getGrouperType()) { - case Dimensions: - return createMultiDimensionalGroupingWorkerBuilder(queryDefinition.getDataType(), queryDefinition.getDimensionsToGroupBy()); - } - throw new IllegalArgumentException("Not yet implemented for the given grouper type: " - + queryDefinition.getGrouperType().toString()); - } - - @SuppressWarnings("unchecked") - private static WorkerBuilder> createMultiDimensionalGroupingWorkerBuilder(DataTypes dataType, List dimensionsToGroupBy) { - DimensionManager dimensionManager = DimensionManagerProvider.getDimensionManagerFor(dataType); - Collection> dimensions = new LinkedHashSet>(); - for (DimensionIdentifier sharedDimension : dimensionsToGroupBy) { - Dimension dimension = (Dimension) dimensionManager.getDimensionFor(sharedDimension); - dimensions.add(dimension); - } - return new MultiDimensionalGroupingWorkerBuilder(dimensions); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/SailingDataRetrievalLevels.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/SailingDataRetrievalLevels.java new file mode 100644 index 00000000000..22a1aa66106 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/SailingDataRetrievalLevels.java @@ -0,0 +1,7 @@ +package com.sap.sailing.datamining.factories; + +public enum SailingDataRetrievalLevels { + + LeaderboardGroup, RegattaLeaderboard, TrackedRace, TrackedLeg, TrackedLegOfCompetitor, GPSFix + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/SailingDataRetrieverFactory.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/SailingDataRetrieverFactory.java new file mode 100644 index 00000000000..e3c599f1a96 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/factories/SailingDataRetrieverFactory.java @@ -0,0 +1,105 @@ +package com.sap.sailing.datamining.factories; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ThreadPoolExecutor; + +import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasTrackedLegContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.RegattaLeaderboardFilteringRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedLegFilteringRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorFilteringRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedRaceFilteringRetrievalProcessor; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sse.datamining.components.FilterCriteria; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.FunctionProvider; +import com.sap.sse.datamining.impl.DataMiningActivator; +import com.sap.sse.datamining.impl.criterias.AndCompoundFilterCriteria; +import com.sap.sse.datamining.impl.criterias.CompoundFilterCriteria; +import com.sap.sse.datamining.impl.criterias.NonFilteringFilterCriteria; +import com.sap.sse.datamining.impl.criterias.NullaryFunctionValuesFilterCriteria; +import com.sap.sse.datamining.shared.dto.FunctionDTO; + +public final class SailingDataRetrieverFactory { + + private SailingDataRetrieverFactory() { + } + + /** + * Creates the processor chain for the data retrieval and returns the first processor of the chain. + * @param groupingProcessor The result receiver after the retrieval + * @param filterSelection The filter functions and values for the filtration + * @param functionProvider + * @return The first processor of the retrieval chain. + */ + @SuppressWarnings("unchecked") + public static Processor createRetrievalProcessorChain(SailingDataRetrievalLevels dataRetrievalLevel, Processor groupingProcessor, Map> filterSelection, FunctionProvider functionProvider) { + Processor resultReceiver = groupingProcessor; + for (int i = dataRetrievalLevel.ordinal(); i >= 0; i--) { + resultReceiver = createDataRetrieverFor(SailingDataRetrievalLevels.values()[i], resultReceiver, filterSelection, functionProvider); + } + return (Processor) resultReceiver; + } + + @SuppressWarnings("unchecked") + /* The way this method is used should guarantee, that the result receiver matches the new processor + */ + private static Processor createDataRetrieverFor(SailingDataRetrievalLevels dataRetrievalLevel, + Processor resultReceiver, Map> filterSelection, FunctionProvider functionProvider) { + ThreadPoolExecutor executor = DataMiningActivator.getExecutor(); + + switch (dataRetrievalLevel) { + case GPSFix: + Processor gpsFixSpecificResultReceiver = (Processor) resultReceiver; + return new GPSFixRetrievalProcessor(executor, Arrays.asList(gpsFixSpecificResultReceiver)); + case LeaderboardGroup: + Processor leaderboardGroupSpecificResultReceiver = (Processor) resultReceiver; + return new LeaderboardGroupRetrievalProcessor(executor, Arrays.asList(leaderboardGroupSpecificResultReceiver)); + case RegattaLeaderboard: + Processor regattaLeaderboardSpecificResultReceiver = (Processor) resultReceiver; + return new RegattaLeaderboardFilteringRetrievalProcessor(executor, Arrays.asList(regattaLeaderboardSpecificResultReceiver), getFilterCriteriaForBaseDataType(RegattaLeaderboard.class, filterSelection, functionProvider)); + case TrackedLeg: + Processor trackedLegSpecificResultReceiver = (Processor) resultReceiver; + return new TrackedLegFilteringRetrievalProcessor(executor, Arrays.asList(trackedLegSpecificResultReceiver), getFilterCriteriaForBaseDataType(HasTrackedLegContext.class, filterSelection, functionProvider)); + case TrackedLegOfCompetitor: + Processor trackedLegOfCompetitorSpecificResultReceiver = (Processor) resultReceiver; + return new TrackedLegOfCompetitorFilteringRetrievalProcessor(executor, Arrays.asList(trackedLegOfCompetitorSpecificResultReceiver), getFilterCriteriaForBaseDataType(HasTrackedLegOfCompetitorContext.class, filterSelection, functionProvider)); + case TrackedRace: + Processor trackedRaceSpecificResultReceiver = (Processor) resultReceiver; + return new TrackedRaceFilteringRetrievalProcessor(executor, Arrays.asList(trackedRaceSpecificResultReceiver), getFilterCriteriaForBaseDataType(HasTrackedRaceContext.class, filterSelection, functionProvider)); + } + throw new IllegalArgumentException("No data retriever implemented for the given data retrieval level '" + + dataRetrievalLevel + "'"); + } + + private static FilterCriteria getFilterCriteriaForBaseDataType(Class baseDataType, + Map> filterSelection, FunctionProvider functionProvider) { + CompoundFilterCriteria criteria = null; + for (Entry> filterSelectionEntry : filterSelection.entrySet()) { + Function function = functionProvider.getFunctionForDTO(filterSelectionEntry.getKey()); + if (baseDataType.equals(function.getDeclaringType())) { + if (criteria == null) { + criteria = new AndCompoundFilterCriteria<>(); + } + + Collection filterValues = new ArrayList<>(); + for (Object filterValue : filterSelectionEntry.getValue()) { + filterValues.add(filterValue); + } + criteria.addCriteria(new NullaryFunctionValuesFilterCriteria(function, filterValues)); + } + } + return criteria != null ? criteria : new NonFilteringFilterCriteria(); + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/Activator.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/Activator.java deleted file mode 100644 index 4fd17a70345..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/Activator.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.sap.sailing.datamining.impl; - -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; - -import com.sap.sailing.datamining.impl.data.DataMiningClassesWithFunctionsService; -import com.sap.sse.datamining.functions.ClassesWithFunctionsService; - -public class Activator implements BundleActivator { - - private static BundleContext context; - - private ServiceRegistration classesWithFunctionsServiceReference; - - @Override - public void start(BundleContext context) throws Exception { - Activator.context = context; - registerClassesWithFunctionsService(); - } - - @Override - public void stop(BundleContext context) throws Exception { - unregisterClassesWithFunctionsService(); - } - - private void unregisterClassesWithFunctionsService() { - context.ungetService(classesWithFunctionsServiceReference.getReference()); - } - - private void registerClassesWithFunctionsService() { - classesWithFunctionsServiceReference = context.registerService(ClassesWithFunctionsService.class, - new DataMiningClassesWithFunctionsService(), null); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/WindStrengthClusterImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/WindStrengthClusterImpl.java deleted file mode 100644 index 602cc87830c..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/WindStrengthClusterImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.sap.sailing.datamining.impl; - - -import com.sap.sailing.datamining.WindStrengthCluster; -import com.sap.sse.datamining.impl.data.deprecated.ClusterOfComparableImpl; - -public class WindStrengthClusterImpl extends ClusterOfComparableImpl implements WindStrengthCluster { - - public WindStrengthClusterImpl(String name, Double lowerRangeInBeaufort, Double upperRangeInBeaufort) { - super(name, upperRangeInBeaufort, lowerRangeInBeaufort); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/AbstractLeaderboardGroupDataRetrievalWorker.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/AbstractLeaderboardGroupDataRetrievalWorker.java deleted file mode 100644 index 8a5abc27ad4..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/AbstractLeaderboardGroupDataRetrievalWorker.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.sap.sailing.datamining.impl.components; - -import java.util.ArrayList; -import java.util.Collection; - -import com.sap.sailing.datamining.data.HasTrackedLegContext; -import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; -import com.sap.sailing.datamining.impl.data.HasTrackedLegContextImpl; -import com.sap.sailing.datamining.impl.data.HasTrackedLegOfCompetitorContextImpl; -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.Leg; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sse.datamining.impl.workers.retrievers.AbstractRetrievalWorker; -import com.sap.sse.datamining.workers.DataRetrievalWorker; - -public abstract class AbstractLeaderboardGroupDataRetrievalWorker - extends AbstractRetrievalWorker> - implements DataRetrievalWorker { - - private LeaderboardGroup group; - - @Override - public void setSource(LeaderboardGroup source) { - this.group = source; - } - - protected LeaderboardGroup getGroup() { - return group; - } - - private static Collection> retrieveDataTillTrackedLeg(LeaderboardGroup group) { - Collection> data = new ArrayList>(); - for (Leaderboard leaderboard : group.getLeaderboards()) { - CourseArea courseArea = leaderboard.getDefaultCourseArea(); - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { - for (Fleet fleet : raceColumn.getFleets()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null) { - int legNumber = 1; - for (Leg leg : trackedRace.getRace().getCourse().getLegs()) { - TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg); - if (trackedLeg != null) { - HasTrackedLegContext dataContext = new HasTrackedLegContextImpl(group, leaderboard, - courseArea, fleet, trackedRace, trackedLeg, legNumber); - data.add(new Pair(trackedLeg, dataContext)); - legNumber++; - } - } - } - } - } - } - return data; - } - - protected static Collection> retrieveDataTillTrackedLegOfCompetitor( - LeaderboardGroup group) { - Collection> data = new ArrayList>(); - Collection> baseData = retrieveDataTillTrackedLeg(group); - for (Pair baseDataEntry : baseData) { - TrackedLeg trackedLeg = baseDataEntry.getA(); - HasTrackedLegContext trackedLegContext = baseDataEntry.getB(); - for (Competitor competitor : trackedLegContext.getTrackedRace().getRace().getCompetitors()) { - TrackedLegOfCompetitor trackedLegOfCompetitor = trackedLeg.getTrackedLeg(competitor); - HasTrackedLegOfCompetitorContext dataContext = new HasTrackedLegOfCompetitorContextImpl(trackedLegContext, - competitor); - data.add(new Pair(trackedLegOfCompetitor, - dataContext)); - } - } - return data; - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GPSFixRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GPSFixRetrievalProcessor.java new file mode 100644 index 00000000000..7eb964a2b69 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GPSFixRetrievalProcessor.java @@ -0,0 +1,47 @@ +package com.sap.sailing.datamining.impl.components; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ExecutorService; + +import com.sap.sailing.datamining.Activator; +import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.datamining.impl.data.GPSFixWithContext; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; +import com.sap.sse.datamining.shared.annotations.DataRetriever; + +@DataRetriever(dataType=HasGPSFixContext.class, + groupName=Activator.dataRetrieverGroupName, + level=5) +public class GPSFixRetrievalProcessor extends AbstractSimpleRetrievalProcessor { + + public GPSFixRetrievalProcessor(ExecutorService executor, Collection> resultReceivers) { + super(executor, resultReceivers); + } + + @Override + protected Iterable retrieveData(HasTrackedLegOfCompetitorContext element) { + Collection gpsFixesWithContext = new ArrayList<>(); + GPSFixTrack competitorTrack = element.getTrackedRace().getTrack(element.getCompetitor()); + competitorTrack.lockForRead(); + try { + TrackedLegOfCompetitor trackedLegOfCompetitor = element.getTrackedLegOfCompetitor(); + if (trackedLegOfCompetitor.getStartTime() != null && trackedLegOfCompetitor.getFinishTime() != null) { + for (GPSFixMoving gpsFix : competitorTrack.getFixes(trackedLegOfCompetitor.getStartTime(), true, trackedLegOfCompetitor.getFinishTime(), true)) { + HasGPSFixContext gpsFixWithContext = new GPSFixWithContext(element, gpsFix); + gpsFixesWithContext.add(gpsFixWithContext); + } + } + } finally { + competitorTrack.unlockAfterRead(); + } + return gpsFixesWithContext; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GroupDividingParallelDataRetriever.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GroupDividingParallelDataRetriever.java deleted file mode 100644 index 807a874be6a..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/GroupDividingParallelDataRetriever.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.sap.sailing.datamining.impl.components; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ThreadPoolExecutor; - -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.server.RacingEventService; -import com.sap.sse.datamining.components.ParallelDataRetriever; -import com.sap.sse.datamining.impl.components.deprecated.AbstractParallelComponent; -import com.sap.sse.datamining.workers.DataRetrievalWorker; -import com.sap.sse.datamining.workers.WorkerBuilder; - -/** - * Retrieves elements of type DataType from all {@link LeaderboardGroup}s known to the - * {@link RacingEventSErvice} passed to this object's constructor. See also - * {@link RacingEventService#getLeaderboardGroups()}. - * - * @param - */ -public class GroupDividingParallelDataRetriever extends AbstractParallelComponent> - implements ParallelDataRetriever { - - private RacingEventService racingService; - private WorkerBuilder> workerBuilder; - - public GroupDividingParallelDataRetriever(RacingEventService racingService, WorkerBuilder> workerBuilder, ThreadPoolExecutor executor) { - super(executor); - this.racingService = racingService; - this.workerBuilder = workerBuilder; - } - - @Override - protected void setUpWorkersFor(Void v) { - for (LeaderboardGroup group : racingService.getLeaderboardGroups().values()) { - DataRetrievalWorker worker = workerBuilder.build(); - worker.setReceiver(this); - worker.setSource(group); - addWorker(worker); - } - } - - @Override - protected Collection finalizeData() { - Collection data = new ArrayList(); - for (Collection results : getResults()) { - data.addAll(results); - } - return Collections.unmodifiableCollection(new CopyOnWriteArrayList(data)); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardGroupFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardGroupFilteringRetrievalProcessor.java deleted file mode 100644 index 0e804362392..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardGroupFilteringRetrievalProcessor.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.sap.sailing.datamining.impl.components; - -import java.util.Collection; -import java.util.concurrent.ExecutorService; - -import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; -import com.sap.sailing.datamining.data.LeaderboardGroupWithContext; -import com.sap.sailing.datamining.impl.data.HasLeaderboardGroupContextImpl; -import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContextImpl; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.server.RacingEventService; -import com.sap.sse.datamining.components.FilterCriteria; -import com.sap.sse.datamining.components.Processor; -import com.sap.sse.datamining.impl.components.AbstractFilteringRetrievalProcessor; - -public class LeaderboardGroupFilteringRetrievalProcessor extends - AbstractFilteringRetrievalProcessor { - - public LeaderboardGroupFilteringRetrievalProcessor(ExecutorService executor, - Collection> resultReceivers, FilterCriteria criteria) { - super(executor, resultReceivers, criteria); - } - - @Override - protected Iterable retrieveData(RacingEventService element) { - return element.getLeaderboardGroups().values(); - } - - @Override - protected LeaderboardGroupWithContext contextifyElement(LeaderboardGroup partialElement) { - HasLeaderboardGroupContext context = new HasLeaderboardGroupContextImpl(partialElement); - return new LeaderboardGroupWithContextImpl(context); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardGroupRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardGroupRetrievalProcessor.java new file mode 100644 index 00000000000..986b50f5ed6 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/LeaderboardGroupRetrievalProcessor.java @@ -0,0 +1,29 @@ +package com.sap.sailing.datamining.impl.components; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; + +import com.sap.sailing.datamining.Activator; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.server.RacingEventService; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; +import com.sap.sse.datamining.shared.annotations.DataRetriever; + +@DataRetriever(dataType=LeaderboardGroup.class, + groupName=Activator.dataRetrieverGroupName, + level=0) +public class LeaderboardGroupRetrievalProcessor extends + AbstractSimpleRetrievalProcessor { + + public LeaderboardGroupRetrievalProcessor(ExecutorService executor, + Collection> resultReceivers) { + super(executor, resultReceivers); + } + + @Override + protected Iterable retrieveData(RacingEventService element) { + return element.getLeaderboardGroups().values(); + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardFilteringRetrievalProcessor.java new file mode 100644 index 00000000000..2e00a32ec48 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardFilteringRetrievalProcessor.java @@ -0,0 +1,38 @@ +package com.sap.sailing.datamining.impl.components; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ExecutorService; + +import com.sap.sailing.datamining.Activator; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sse.datamining.components.FilterCriteria; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.AbstractSimpleFilteringRetrievalProcessor; +import com.sap.sse.datamining.shared.annotations.DataRetriever; + +@DataRetriever(dataType=RegattaLeaderboard.class, + groupName=Activator.dataRetrieverGroupName, + level=1) +public class RegattaLeaderboardFilteringRetrievalProcessor extends + AbstractSimpleFilteringRetrievalProcessor { + + public RegattaLeaderboardFilteringRetrievalProcessor(ExecutorService executor, + Collection> resultReceivers, FilterCriteria criteria) { + super(executor, resultReceivers, criteria); + } + + @Override + protected Iterable retrieveData(LeaderboardGroup element) { + Collection regattaLeaderboards = new ArrayList<>(); + for (Leaderboard leaderboard : element.getLeaderboards()) { + if (leaderboard instanceof RegattaLeaderboard) { + regattaLeaderboards.add((RegattaLeaderboard) leaderboard); + } + } + return regattaLeaderboards; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegFilteringRetrievalProcessor.java new file mode 100644 index 00000000000..b4fdf0bbb37 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegFilteringRetrievalProcessor.java @@ -0,0 +1,40 @@ +package com.sap.sailing.datamining.impl.components; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ExecutorService; + +import com.sap.sailing.datamining.Activator; +import com.sap.sailing.datamining.data.HasTrackedLegContext; +import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.impl.data.TrackedLegWithContext; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sse.datamining.components.FilterCriteria; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.AbstractSimpleFilteringRetrievalProcessor; +import com.sap.sse.datamining.shared.annotations.DataRetriever; + +@DataRetriever(dataType=HasTrackedLegContext.class, + groupName=Activator.dataRetrieverGroupName, + level=3) +public class TrackedLegFilteringRetrievalProcessor extends + AbstractSimpleFilteringRetrievalProcessor { + + public TrackedLegFilteringRetrievalProcessor(ExecutorService executor, + Collection> resultReceivers, FilterCriteria criteria) { + super(executor, resultReceivers, criteria); + } + + @Override + protected Iterable retrieveData(HasTrackedRaceContext element) { + Collection trackedLegsWithContext = new ArrayList<>(); + int legNumber = 1; + for (TrackedLeg trackedLeg : element.getTrackedRace().getTrackedLegs()) { + HasTrackedLegContext trackedLegWithContext = new TrackedLegWithContext(element, trackedLeg, legNumber); + trackedLegsWithContext.add(trackedLegWithContext); + legNumber++; + } + return trackedLegsWithContext; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorFilteringRetrievalProcessor.java new file mode 100644 index 00000000000..2fa95994889 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorFilteringRetrievalProcessor.java @@ -0,0 +1,39 @@ +package com.sap.sailing.datamining.impl.components; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ExecutorService; + +import com.sap.sailing.datamining.Activator; +import com.sap.sailing.datamining.data.HasTrackedLegContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.datamining.impl.data.TrackedLegOfCompetitorWithContext; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sse.datamining.components.FilterCriteria; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.AbstractSimpleFilteringRetrievalProcessor; +import com.sap.sse.datamining.shared.annotations.DataRetriever; + +@DataRetriever(dataType=HasTrackedLegOfCompetitorContext.class, + groupName=Activator.dataRetrieverGroupName, + level=4) +public class TrackedLegOfCompetitorFilteringRetrievalProcessor extends + AbstractSimpleFilteringRetrievalProcessor { + + public TrackedLegOfCompetitorFilteringRetrievalProcessor(ExecutorService executor, + Collection> resultReceivers, + FilterCriteria criteria) { + super(executor, resultReceivers, criteria); + } + + @Override + protected Iterable retrieveData(HasTrackedLegContext element) { + Collection trackedLegOfCompetitorsWithContext = new ArrayList<>(); + for (Competitor competitor : element.getRace().getCompetitors()) { + HasTrackedLegOfCompetitorContext trackedLegOfCompetitorWithContext = new TrackedLegOfCompetitorWithContext(element, element.getTrackedLeg().getTrackedLeg(competitor)); + trackedLegOfCompetitorsWithContext.add(trackedLegOfCompetitorWithContext); + } + return trackedLegOfCompetitorsWithContext; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceFilteringRetrievalProcessor.java new file mode 100644 index 00000000000..c0b9ab05ac1 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceFilteringRetrievalProcessor.java @@ -0,0 +1,48 @@ +package com.sap.sailing.datamining.impl.components; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ExecutorService; + +import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.impl.data.TrackedRaceWithContext; +import com.sap.sailing.domain.base.Event; +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.leaderboard.Leaderboard; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.datamining.components.FilterCriteria; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.AbstractSimpleFilteringRetrievalProcessor; +import com.sap.sse.datamining.shared.annotations.DataRetriever; + +@DataRetriever(dataType=HasTrackedRaceContext.class, + groupName="Sailing", + level=2) +public class TrackedRaceFilteringRetrievalProcessor extends + AbstractSimpleFilteringRetrievalProcessor { + + public TrackedRaceFilteringRetrievalProcessor(ExecutorService executor, + Collection> resultReceivers, FilterCriteria criteria) { + super(executor, resultReceivers, criteria); + } + + @Override + protected Iterable retrieveData(Leaderboard element) { + Collection trackedRacesWithContext = new ArrayList<>(); + for (RaceColumn raceColumn : element.getRaceColumns()) { + for (Fleet fleet : raceColumn.getFleets()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + Regatta regatta = trackedRace.getTrackedRegatta().getRegatta(); + Event event = regatta.getEvent(); + HasTrackedRaceContext trackedRaceWithContext = new TrackedRaceWithContext(event, regatta, fleet, trackedRace); + trackedRacesWithContext.add(trackedRaceWithContext); + } + } + } + return trackedRacesWithContext; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/DataMiningClassesWithFunctionsService.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/DataMiningClassesWithFunctionsService.java deleted file mode 100644 index 29c67938a58..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/DataMiningClassesWithFunctionsService.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.sap.sailing.datamining.impl.data; - -import java.util.HashSet; -import java.util.Set; - -import com.sap.sailing.datamining.data.GPSFixWithContext; -import com.sap.sailing.datamining.data.TrackedLegOfCompetitorWithContext; -import com.sap.sse.datamining.functions.ClassesWithFunctionsService; - -public class DataMiningClassesWithFunctionsService implements ClassesWithFunctionsService { - - @Override - public Set> getInternalClassesWithMarkedMethods() { - Set> internalClasses = new HashSet<>(); - internalClasses.add(GPSFixWithContext.class); - internalClasses.add(TrackedLegOfCompetitorWithContext.class); - return internalClasses; - } - - @Override - public Set> getExternalLibraryClasses() { - return new HashSet<>(); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java new file mode 100644 index 00000000000..2b51b4bbd3b --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java @@ -0,0 +1,33 @@ +package com.sap.sailing.datamining.impl.data; + +import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.TrackedRace; + +public class GPSFixWithContext extends TrackedLegOfCompetitorWithContext implements HasGPSFixContext { + + private final GPSFixMoving gpsFix; + + public GPSFixWithContext(HasTrackedLegOfCompetitorContext legContext, GPSFixMoving gpsFix) { + this(legContext.getEvent(), legContext.getRegatta(), legContext.getFleet(), legContext.getTrackedRace(), + legContext.getTrackedLeg(), legContext.getLegNumber(), legContext.getTrackedLegOfCompetitor(), gpsFix); + } + + public GPSFixWithContext(Event event, Regatta regatta, Fleet fleet, TrackedRace trackedRace, + TrackedLeg trackedLeg, int legNumber, TrackedLegOfCompetitor trackedLegOfCompetitor, GPSFixMoving gpsFix) { + super(event, regatta, fleet, trackedRace, trackedLeg, legNumber, trackedLegOfCompetitor); + this.gpsFix = gpsFix; + } + + @Override + public GPSFixMoving getGPSFix() { + return gpsFix; + } + +} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasGPSFixContextImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasGPSFixContextImpl.java deleted file mode 100644 index a26e907ecf9..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasGPSFixContextImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.datamining.impl.data; - -import com.sap.sailing.datamining.data.HasGPSFixContext; -import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; -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.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedRace; - -public class HasGPSFixContextImpl extends HasTrackedLegOfCompetitorContextImpl implements HasGPSFixContext { - - public HasGPSFixContextImpl(LeaderboardGroup leaderboardGroup, Leaderboard leaderboard, CourseArea courseArea, - Fleet fleet, TrackedRace trackedRace, TrackedLeg trackedLeg, int legNumber, Competitor competitor) { - super(leaderboardGroup, leaderboard, courseArea, fleet, trackedRace, trackedLeg, legNumber, competitor); - } - - public HasGPSFixContextImpl(HasTrackedLegOfCompetitorContext baseContext) { - this(baseContext.getLeaderboardGroup(), baseContext.getLeaderboard(), baseContext.getCourseArea(), baseContext.getFleet(), - baseContext.getTrackedRace(), baseContext.getTrackedLeg(), baseContext.getLegNumber(), - baseContext.getCompetitor()); - } - -} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasLeaderboardContextImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasLeaderboardContextImpl.java deleted file mode 100644 index f74221d0401..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasLeaderboardContextImpl.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.sap.sailing.datamining.impl.data; - -import com.sap.sailing.datamining.data.HasLeaderboardContext; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; - -public class HasLeaderboardContextImpl extends HasLeaderboardGroupContextImpl implements HasLeaderboardContext { - - private final Leaderboard leaderboard; - - public HasLeaderboardContextImpl(LeaderboardGroup leaderboardGroup, Leaderboard leaderboard) { - super(leaderboardGroup); - this.leaderboard = leaderboard; - } - - @Override - public Leaderboard getLeaderboard() { - return leaderboard; - } - -} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasLeaderboardGroupContextImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasLeaderboardGroupContextImpl.java deleted file mode 100644 index 355e29daa31..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasLeaderboardGroupContextImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.sap.sailing.datamining.impl.data; - -import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; - -public class HasLeaderboardGroupContextImpl implements HasLeaderboardGroupContext { - - private final LeaderboardGroup leaderboardGroup; - - public HasLeaderboardGroupContextImpl(LeaderboardGroup leaderboardGroup) { - this.leaderboardGroup = leaderboardGroup; - } - - @Override - public LeaderboardGroup getLeaderboardGroup() { - return leaderboardGroup; - } - -} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasTrackedLegOfCompetitorContextImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasTrackedLegOfCompetitorContextImpl.java deleted file mode 100644 index ec494b241b8..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasTrackedLegOfCompetitorContextImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.sap.sailing.datamining.impl.data; - -import com.sap.sailing.datamining.data.HasTrackedLegContext; -import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; -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.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedRace; - -public class HasTrackedLegOfCompetitorContextImpl extends HasTrackedLegContextImpl implements HasTrackedLegOfCompetitorContext { - - private final Competitor competitor; - - public HasTrackedLegOfCompetitorContextImpl(LeaderboardGroup leaderboardGroup, Leaderboard leaderboard, - CourseArea courseArea, Fleet fleet, TrackedRace trackedRace, TrackedLeg trackedLeg, int legNumber, Competitor competitor) { - super(leaderboardGroup, leaderboard, courseArea, fleet, trackedRace, trackedLeg, legNumber); - this.competitor = competitor; - } - - public HasTrackedLegOfCompetitorContextImpl(HasTrackedLegContext trackedLegContext, Competitor competitor) { - this(trackedLegContext.getLeaderboardGroup(), trackedLegContext.getLeaderboard(), trackedLegContext.getCourseArea(), - trackedLegContext.getFleet(), trackedLegContext.getTrackedRace(), trackedLegContext.getTrackedLeg(), trackedLegContext.getLegNumber(), - competitor); - } - - @Override - public Competitor getCompetitor() { - return competitor; - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardGroupWithContextImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardGroupWithContextImpl.java deleted file mode 100644 index 4ec855711ea..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardGroupWithContextImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.sap.sailing.datamining.impl.data; - -import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; -import com.sap.sailing.datamining.data.LeaderboardGroupWithContext; - -public class LeaderboardGroupWithContextImpl implements LeaderboardGroupWithContext { - - private HasLeaderboardGroupContext context; - - public LeaderboardGroupWithContextImpl(HasLeaderboardGroupContext context) { - this.context = context; - } - - @Override - public String getLeaderboardGroupName() { - return context.getLeaderboardGroup().getName(); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/SailingDataMiningClassesWithFunctionsService.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/SailingDataMiningClassesWithFunctionsService.java new file mode 100644 index 00000000000..167ebf9e3fb --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/SailingDataMiningClassesWithFunctionsService.java @@ -0,0 +1,30 @@ +package com.sap.sailing.datamining.impl.data; + +import java.util.HashSet; +import java.util.Set; + +import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sse.datamining.ClassesWithFunctionsService; + +public class SailingDataMiningClassesWithFunctionsService implements ClassesWithFunctionsService { + + @Override + public Set> getInternalClassesWithMarkedMethods() { + Set> internalClasses = new HashSet<>(); + /* + * This also contains the marked methods of + * HasTrackedRaceContext, + * HasTrackedLegContext and + * HasTrackedLegOfCompetitorContext, + * because these are the super types of HasGPSFixContext. + */ + internalClasses.add(HasGPSFixContext.class); + return internalClasses; + } + + @Override + public Set> getExternalLibraryClasses() { + return new HashSet<>(); + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegOfCompetitorWithContext.java new file mode 100644 index 00000000000..e956e796fd7 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegOfCompetitorWithContext.java @@ -0,0 +1,48 @@ +package com.sap.sailing.datamining.impl.data; + +import com.sap.sailing.datamining.data.HasTrackedLegContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.TrackedRace; + +public class TrackedLegOfCompetitorWithContext extends TrackedLegWithContext implements HasTrackedLegOfCompetitorContext { + + private final TrackedLegOfCompetitor trackedLegOfCompetitor; + private final Competitor competitor; + + public TrackedLegOfCompetitorWithContext(HasTrackedLegContext trackedLegContext, TrackedLegOfCompetitor trackedLegOfCompetitor) { + this(trackedLegContext.getEvent(), trackedLegContext.getRegatta(), trackedLegContext.getFleet(), + trackedLegContext.getTrackedRace(), trackedLegContext.getTrackedLeg(), + trackedLegContext.getLegNumber(), trackedLegOfCompetitor); + } + + public TrackedLegOfCompetitorWithContext(Event event, Regatta regatta, Fleet fleet, TrackedRace trackedRace, + TrackedLeg trackedLeg, int legNumber, TrackedLegOfCompetitor trackedLegOfCompetitor) { + super(event, regatta, fleet, trackedRace, trackedLeg, legNumber); + this.trackedLegOfCompetitor = trackedLegOfCompetitor; + this.competitor = trackedLegOfCompetitor.getCompetitor(); + } + + @Override + public TrackedLegOfCompetitor getTrackedLegOfCompetitor() { + return trackedLegOfCompetitor; + } + + @Override + public Competitor getCompetitor() { + return competitor; + } + + @Override + public Double getDistanceTraveled() { + TimePoint timePoint = getTrackedRace().getEndOfTracking(); + return getTrackedLegOfCompetitor().getDistanceTraveled(timePoint).getMeters(); + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasTrackedLegContextImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegWithContext.java similarity index 67% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasTrackedLegContextImpl.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegWithContext.java index fc9d85ee03d..64d1e38e685 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasTrackedLegContextImpl.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegWithContext.java @@ -2,34 +2,31 @@ package com.sap.sailing.datamining.impl.data; import com.sap.sailing.datamining.data.HasTrackedLegContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; -import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; -public class HasTrackedLegContextImpl extends HasTrackedRaceContextImpl implements HasTrackedLegContext { +public class TrackedLegWithContext extends TrackedRaceWithContext implements HasTrackedLegContext { private final TrackedLeg trackedLeg; private final int legNumber; private LegType legType; private boolean legTypeHasBeenInitialized; - public HasTrackedLegContextImpl(HasTrackedRaceContext trackedRaceContext, TrackedLeg trackedLeg, int legNumber) { - this(trackedRaceContext.getLeaderboardGroup(), trackedRaceContext.getLeaderboard(), trackedRaceContext - .getCourseArea(), trackedRaceContext.getFleet(), trackedRaceContext.getTrackedRace(), trackedLeg, - legNumber); + public TrackedLegWithContext(HasTrackedRaceContext trackedRaceContext, TrackedLeg trackedLeg, int legNumber) { + this(trackedRaceContext.getEvent(), trackedRaceContext.getRegatta(), trackedRaceContext.getFleet(), + trackedRaceContext.getTrackedRace(), trackedLeg, legNumber); } - public HasTrackedLegContextImpl(LeaderboardGroup leaderboardGroup, Leaderboard leaderboard, CourseArea courseArea, Fleet fleet, - TrackedRace trackedRace, TrackedLeg trackedLeg, int legNumber) { - super(leaderboardGroup, leaderboard, courseArea, fleet, trackedRace); + public TrackedLegWithContext(Event event, Regatta regatta, Fleet fleet, TrackedRace trackedRace, TrackedLeg trackedLeg, int legNumber) { + super(event, regatta, fleet, trackedRace); this.trackedLeg = trackedLeg; this.legNumber = legNumber; } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasTrackedRaceContextImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java similarity index 60% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasTrackedRaceContextImpl.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java index 1ee5f98c319..4e863bb6089 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/HasTrackedRaceContextImpl.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java @@ -2,49 +2,65 @@ package com.sap.sailing.datamining.impl.data; 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.CourseArea; +import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.tracking.TrackedRace; -public class HasTrackedRaceContextImpl extends HasLeaderboardContextImpl implements HasTrackedRaceContext { +public class TrackedRaceWithContext implements HasTrackedRaceContext { - private final CourseArea courseArea; + private final Event event; + private final Regatta regatta; private final Fleet fleet; private final TrackedRace trackedRace; private Integer year; private boolean yearHasBeenInitialized; - - public HasTrackedRaceContextImpl(HasLeaderboardContext leaderboardContext, CourseArea courseArea, Fleet fleet, - TrackedRace trackedRace) { - this(leaderboardContext.getLeaderboardGroup(), leaderboardContext.getLeaderboard(), courseArea, fleet, trackedRace); - } - public HasTrackedRaceContextImpl(LeaderboardGroup leaderboardGroup, Leaderboard leaderboard, CourseArea courseArea, - Fleet fleet, TrackedRace trackedRace) { - super(leaderboardGroup, leaderboard); - this.courseArea = courseArea; + public TrackedRaceWithContext(Event event, Regatta regatta, Fleet fleet, TrackedRace trackedRace) { + this.event = event; + this.regatta = regatta; this.fleet = fleet; this.trackedRace = trackedRace; } - + + @Override + public Event getEvent() { + return event; + } + + @Override + public Regatta getRegatta() { + return regatta; + } + @Override public CourseArea getCourseArea() { - return courseArea; + return getRegatta().getDefaultCourseArea(); + } + + @Override + public BoatClass getBoatClass() { + return getRegatta().getBoatClass(); + } + + @Override + public TrackedRace getTrackedRace() { + return trackedRace; } @Override public Fleet getFleet() { return fleet; } - + @Override - public TrackedRace getTrackedRace() { - return trackedRace; + public RaceDefinition getRace() { + return trackedRace.getRace(); } @Override diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/gps_fix/GPSFixLeaderboardGroupDataRetrievalWorker.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/gps_fix/GPSFixLeaderboardGroupDataRetrievalWorker.java deleted file mode 100644 index 34a35a734a0..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/gps_fix/GPSFixLeaderboardGroupDataRetrievalWorker.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.sap.sailing.datamining.impl.gps_fix; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import com.sap.sailing.datamining.data.HasGPSFixContext; -import com.sap.sailing.datamining.data.GPSFixWithContext; -import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; -import com.sap.sailing.datamining.impl.components.AbstractLeaderboardGroupDataRetrievalWorker; -import com.sap.sailing.datamining.impl.data.HasGPSFixContextImpl; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; - -public class GPSFixLeaderboardGroupDataRetrievalWorker extends AbstractLeaderboardGroupDataRetrievalWorker { - - @Override - public Collection doWork() { - Collection data = new ArrayList(); - Collection> baseData = retrieveDataTillTrackedLegOfCompetitor(getGroup()); - for (Pair baseDataEntry : baseData) { - HasGPSFixContext context = new HasGPSFixContextImpl(baseDataEntry.getB()); - data.addAll(retrieveDataFor(context)); - } - return data; - } - - private Collection retrieveDataFor(HasGPSFixContext context) { - List data = new ArrayList(); - TrackedLegOfCompetitor trackedLegOfCompetitor = context.getTrackedLeg().getTrackedLeg(context.getCompetitor()); - GPSFixTrack competitorTrack = context.getTrackedRace().getTrack(context.getCompetitor()); - competitorTrack.lockForRead(); - try { - if (trackedLegOfCompetitor.getStartTime() != null && trackedLegOfCompetitor.getFinishTime() != null) { - for (GPSFixMoving gpsFix : competitorTrack.getFixes(trackedLegOfCompetitor.getStartTime(), true, trackedLegOfCompetitor.getFinishTime(), true)) { - data.add(new GPSFixWithContextImpl(gpsFix, context)); - } - } - } finally { - competitorTrack.unlockAfterRead(); - } - return data; - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/gps_fix/GPSFixWithContextImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/gps_fix/GPSFixWithContextImpl.java deleted file mode 100644 index bd56ee9d4c1..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/gps_fix/GPSFixWithContextImpl.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.sap.sailing.datamining.impl.gps_fix; - -import com.sap.sailing.datamining.Clusters.WindStrength; -import com.sap.sailing.datamining.WindStrengthCluster; -import com.sap.sailing.datamining.data.HasGPSFixContext; -import com.sap.sailing.datamining.data.GPSFixWithContext; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; - -public class GPSFixWithContextImpl extends GPSFixMovingImpl implements GPSFixWithContext { - private static final long serialVersionUID = -5551381302809417831L; - - private HasGPSFixContext context; - private Wind wind; - - private boolean windHasBeenInitialized; - - public GPSFixWithContextImpl(GPSFixMoving gpsFix, HasGPSFixContext context) { - super(copyPosition(gpsFix), copyTimePoint(gpsFix), copySpeed(gpsFix)); - this.context = context; - - windHasBeenInitialized= false; - } - - private static SpeedWithBearing copySpeed(GPSFixMoving gpsFix) { - return new KnotSpeedWithBearingImpl(gpsFix.getSpeed().getKnots(), gpsFix.getSpeed().getBearing()); - } - - private static TimePoint copyTimePoint(GPSFixMoving gpsFix) { - return new MillisecondsTimePoint(gpsFix.getTimePoint().asMillis()); - } - - private static Position copyPosition(GPSFixMoving gpsFix) { - return new DegreePosition(gpsFix.getPosition().getLatDeg(), gpsFix.getPosition().getLngDeg()); - } - - @Override - public String getRegattaName() { - return context.getTrackedRace().getTrackedRegatta().getRegatta().getName(); - } - - @Override - public String getRaceName() { - return context.getTrackedRace().getRace().getName(); - } - - @Override - public int getLegNumber() { - return context.getLegNumber(); - } - - @Override - public String getCourseAreaName() { - return context.getCourseArea().getName(); - } - - @Override - public String getFleetName() { - return context.getFleet().getName(); - } - - @Override - public String getBoatClassName() { - return context.getCompetitor().getBoat().getBoatClass().getName(); - } - - @Override - public Integer getYear() { - return context.getYear(); - } - - @Override - public LegType getLegType() { - return context.getLegType(); - } - - @Override - public String getCompetitorName() { - return context.getCompetitor().getName(); - } - - @Override - public String getCompetitorSailID() { - return context.getCompetitor().getBoat().getSailID(); - } - - @Override - public String getCompetitorNationality() { - return context.getCompetitor().getTeam().getNationality().getThreeLetterIOCAcronym(); - } - - @Override - public WindStrengthCluster getWindStrength() { - if (getWind() == null) { - return null; - } - - return WindStrength.getClusterFor(getWind().getBeaufort(), WindStrength.StandardClusters); - } - - public Wind getWind() { - if (!windHasBeenInitialized) { - wind = context.getTrackedRace().getWind(getPosition(), getTimePoint()); - windHasBeenInitialized = true; - } - - return wind; - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/tracked_leg_of_competitor/DistanceTraveledExtractionWorker.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/tracked_leg_of_competitor/DistanceTraveledExtractionWorker.java deleted file mode 100644 index ea79b20f949..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/tracked_leg_of_competitor/DistanceTraveledExtractionWorker.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.sap.sailing.datamining.impl.tracked_leg_of_competitor; - -import com.sap.sailing.datamining.data.TrackedLegOfCompetitorWithContext; -import com.sap.sailing.domain.common.Distance; -import com.sap.sse.datamining.impl.workers.extractors.AbstractExtractionWorker; - -public class DistanceTraveledExtractionWorker extends AbstractExtractionWorker { - - @Override - public Double extract(TrackedLegOfCompetitorWithContext dataEntry) { - Distance distanceTraveled = dataEntry.getDistanceTraveled(); - return distanceTraveled != null ? distanceTraveled.getMeters() : 0; - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/tracked_leg_of_competitor/TrackedLegOfCompetitorLeaderboardGroupDataRetrievalWorker.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/tracked_leg_of_competitor/TrackedLegOfCompetitorLeaderboardGroupDataRetrievalWorker.java deleted file mode 100644 index 6a700a65945..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/tracked_leg_of_competitor/TrackedLegOfCompetitorLeaderboardGroupDataRetrievalWorker.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.sap.sailing.datamining.impl.tracked_leg_of_competitor; - -import java.util.ArrayList; -import java.util.Collection; - -import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; -import com.sap.sailing.datamining.data.TrackedLegOfCompetitorWithContext; -import com.sap.sailing.datamining.impl.components.AbstractLeaderboardGroupDataRetrievalWorker; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; - -public class TrackedLegOfCompetitorLeaderboardGroupDataRetrievalWorker extends AbstractLeaderboardGroupDataRetrievalWorker { - - @Override - public Collection doWork() { - Collection data = new ArrayList(); - Collection> baseData = retrieveDataTillTrackedLegOfCompetitor(getGroup()); - for (Pair baseDataEntry : baseData) { - data.add(new TrackedLegOfCompetitorWithContextImpl(baseDataEntry.getA(), baseDataEntry.getB())); - } - return data; - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/tracked_leg_of_competitor/TrackedLegOfCompetitorWithContextImpl.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/tracked_leg_of_competitor/TrackedLegOfCompetitorWithContextImpl.java deleted file mode 100644 index f211553eab9..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/tracked_leg_of_competitor/TrackedLegOfCompetitorWithContextImpl.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.sap.sailing.datamining.impl.tracked_leg_of_competitor; - -import com.sap.sailing.datamining.WindStrengthCluster; -import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; -import com.sap.sailing.datamining.data.TrackedLegOfCompetitorWithContext; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; - -public class TrackedLegOfCompetitorWithContextImpl implements TrackedLegOfCompetitorWithContext { - - private TrackedLegOfCompetitor trackedLegOfCompetitor; - private HasTrackedLegOfCompetitorContext context; - - public TrackedLegOfCompetitorWithContextImpl(TrackedLegOfCompetitor trackedLegOfCompetitor, HasTrackedLegOfCompetitorContext context) { - this.trackedLegOfCompetitor = trackedLegOfCompetitor; - this.context = context; - } - - @Override - public String getRegattaName() { - return context.getTrackedRace().getTrackedRegatta().getRegatta().getName(); - } - - @Override - public String getRaceName() { - return context.getTrackedRace().getRace().getName(); - } - - @Override - public int getLegNumber() { - return context.getLegNumber(); - } - - @Override - public String getCourseAreaName() { - return context.getCourseArea().getName(); - } - - @Override - public String getFleetName() { - return context.getFleet().getName(); - } - - @Override - public String getBoatClassName() { - return context.getCompetitor().getBoat().getBoatClass().getName(); - } - - @Override - public Integer getYear() { - return context.getYear(); - } - - @Override - public LegType getLegType() { - return context.getLegType(); - } - - @Override - public String getCompetitorName() { - return context.getCompetitor().getName(); - } - - @Override - public String getCompetitorSailID() { - return context.getCompetitor().getBoat().getSailID(); - } - - @Override - public String getCompetitorNationality() { - return context.getCompetitor().getTeam().getNationality().getThreeLetterIOCAcronym(); - } - - @Override - public WindStrengthCluster getWindStrength() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Distance getDistanceTraveled() { - TimePoint finishTime = trackedLegOfCompetitor.getFinishTime(); - return finishTime != null ? trackedLegOfCompetitor.getDistanceTraveled(finishTime) : null; - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/workers/SpeedInKnotsExtractionWorker.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/workers/SpeedInKnotsExtractionWorker.java deleted file mode 100644 index cf130331d30..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/workers/SpeedInKnotsExtractionWorker.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.sap.sailing.datamining.impl.workers; - -import com.sap.sailing.domain.base.Moving; -import com.sap.sse.datamining.impl.workers.extractors.AbstractExtractionWorker; - -public class SpeedInKnotsExtractionWorker extends AbstractExtractionWorker { - - @Override - public Double extract(Moving dataEntry) { - return dataEntry.getSpeed().getKnots(); - } - -} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/workers/builders/DataRetrieverWorkerBuilder.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/workers/builders/DataRetrieverWorkerBuilder.java deleted file mode 100644 index 7c34311185c..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/workers/builders/DataRetrieverWorkerBuilder.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.sap.sailing.datamining.impl.workers.builders; - -import com.sap.sailing.datamining.impl.gps_fix.GPSFixLeaderboardGroupDataRetrievalWorker; -import com.sap.sailing.datamining.impl.tracked_leg_of_competitor.TrackedLegOfCompetitorLeaderboardGroupDataRetrievalWorker; -import com.sap.sailing.datamining.shared.DataTypes; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sse.datamining.workers.DataRetrievalWorker; -import com.sap.sse.datamining.workers.WorkerBuilder; - -public class DataRetrieverWorkerBuilder implements WorkerBuilder> { - - private final DataTypes dataType; - - public DataRetrieverWorkerBuilder(DataTypes dataType) { - this.dataType = dataType; - } - - @Override - @SuppressWarnings("unchecked") - public DataRetrievalWorker build() { - switch (dataType) { - case GPSFix: - return (DataRetrievalWorker) new GPSFixLeaderboardGroupDataRetrievalWorker(); - case TrackedLegOfCompetitor: - return (DataRetrievalWorker) new TrackedLegOfCompetitorLeaderboardGroupDataRetrievalWorker(); - } - throw new IllegalArgumentException("Not yet implemented for the given data type: " + dataType.toString()); - } - -} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/workers/builders/ExtractionWorkerBuilder.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/workers/builders/ExtractionWorkerBuilder.java deleted file mode 100644 index f589cf1ec69..00000000000 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/workers/builders/ExtractionWorkerBuilder.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.sap.sailing.datamining.impl.workers.builders; - -import com.sap.sailing.datamining.impl.tracked_leg_of_competitor.DistanceTraveledExtractionWorker; -import com.sap.sailing.datamining.impl.workers.SpeedInKnotsExtractionWorker; -import com.sap.sailing.datamining.shared.StatisticType; -import com.sap.sse.datamining.workers.ExtractionWorker; -import com.sap.sse.datamining.workers.WorkerBuilder; - -public class ExtractionWorkerBuilder implements WorkerBuilder> { - - private StatisticType statisticType; - - public ExtractionWorkerBuilder(StatisticType statisticType) { - this.statisticType = statisticType; - } - - @Override - @SuppressWarnings("unchecked") - public ExtractionWorker build() { - switch (statisticType) { - case Speed: - return (ExtractionWorker) new SpeedInKnotsExtractionWorker(); - case Distance: - return (ExtractionWorker) new DistanceTraveledExtractionWorker(); - } - throw new IllegalArgumentException("Not yet implemented for the given statistic type: " - + statisticType.toString()); - } - -} diff --git a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF index cf73d6c518b..ed8ea9b07e3 100755 --- a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF @@ -14,5 +14,10 @@ Export-Package: com.sap.sailing.domain.common, com.sap.sailing.domain.common.quadtree, com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test", com.sap.sailing.domain.common.racelog, + com.sap.sailing.domain.common.racelog.tracking, com.sap.sailing.domain.common.racelog.utils, + com.sap.sailing.domain.common.scalablevalue, + com.sap.sailing.domain.common.scalablevalue.impl, com.sap.sailing.domain.common.trackfiles +Require-Bundle: com.sap.sse.datamining.shared, + com.sap.sse.common diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/SailingDomain.gwt.xml b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/SailingDomain.gwt.xml index 67cbee6e8e0..366a8944444 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/SailingDomain.gwt.xml +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/SailingDomain.gwt.xml @@ -6,4 +6,7 @@ + + + diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractPosition.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractPosition.java index fafdf75c2be..84c0bc7344b 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractPosition.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractPosition.java @@ -57,7 +57,13 @@ public class AbstractPosition implements Position { @Override public Distance getDistance(Position p) { - return new CentralAngleDistance(getCentralAngleRad(p)); + final Distance result; + if (p == this || this.equals(p)) { + result = Distance.NULL; + } else { + result = new CentralAngleDistance(getCentralAngleRad(p)); + } + return result; } @Override diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Color.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Color.java index bcdead1f5bc..7ce135d2001 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Color.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Color.java @@ -3,7 +3,6 @@ package com.sap.sailing.domain.common; import java.io.Serializable; import com.sap.sailing.domain.common.impl.RGBColor; -import com.sap.sailing.domain.common.impl.Util; public interface Color extends Serializable { final Color WHITE = new RGBColor(255, 255, 255); @@ -32,9 +31,9 @@ public interface Color extends Serializable { final Color BLUE = new RGBColor(0, 0, 255); - Util.Triple getAsRGB(); + com.sap.sse.common.Util.Triple getAsRGB(); - Util.Triple getAsHSV(); + com.sap.sse.common.Util.Triple getAsHSV(); String getAsHtml(); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java index e9c6215c65a..f0ea050a3b4 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java @@ -1,7 +1,10 @@ package com.sap.sailing.domain.common; +import static com.sap.sailing.domain.common.SortingOrder.ASCENDING; +import static com.sap.sailing.domain.common.SortingOrder.DESCENDING; +import static com.sap.sailing.domain.common.SortingOrder.NONE; + import java.io.Serializable; -import static com.sap.sailing.domain.common.SortingOrder.*; /** * Identifies details that can be requested from the racing service. Optionally, the details can specify a precision @@ -13,13 +16,14 @@ import static com.sap.sailing.domain.common.SortingOrder.*; */ public enum DetailType implements Serializable { DISTANCE_TRAVELED(0, ASCENDING), AVERAGE_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), RACE_RANK(0, ASCENDING), REGATTA_RANK(0, ASCENDING), OVERALL_RANK(0, ASCENDING), - RANK_GAIN(0, DESCENDING), NUMBER_OF_MANEUVERS(0, ASCENDING), CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), + RANK_GAIN(0, ASCENDING), NUMBER_OF_MANEUVERS(0, ASCENDING), CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS(1, ASCENDING), VELOCITY_MADE_GOOD_IN_KNOTS(2, DESCENDING), GAP_TO_LEADER_IN_SECONDS(0, ASCENDING), GAP_CHANGE_SINCE_LEG_START_IN_SECONDS(0, ASCENDING), SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED(0, ASCENDING), WINDWARD_DISTANCE_TO_GO_IN_METERS(0, ASCENDING), - AVERAGE_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING), RACE_DISTANCE_TRAVELED(0, ASCENDING), + AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING), AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING), RACE_DISTANCE_TRAVELED(0, ASCENDING), RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), RACE_GAP_TO_LEADER_IN_SECONDS(0, ASCENDING), - RACE_DISTANCE_TO_LEADER_IN_METERS(0, ASCENDING), RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING), + RACE_DISTANCE_TO_LEADER_IN_METERS(0, ASCENDING), RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING), + RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING), WINDWARD_DISTANCE_TO_OVERALL_LEADER(0, ASCENDING), START_TACK(0, ASCENDING), DISTANCE_TO_START_AT_RACE_START(1, ASCENDING), SPEED_OVER_GROUND_AT_RACE_START(1, DESCENDING), SPEED_OVER_GROUND_WHEN_PASSING_START(1, DESCENDING), DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS(1, ASCENDING), diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DoublePair.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DoublePair.java new file mode 100755 index 00000000000..dff2cf90181 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DoublePair.java @@ -0,0 +1,20 @@ +package com.sap.sailing.domain.common; + +public class DoublePair { + private final double a; + private final double b; + + public DoublePair(double a, double b) { + super(); + this.a = a; + this.b = b; + } + + public double getA() { + return a; + } + + public double getB() { + return b; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Duration.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Duration.java index f5f6363792c..00ab5b896b0 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Duration.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Duration.java @@ -12,6 +12,7 @@ import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl; */ public interface Duration extends Serializable, Comparable { + static final Duration NULL = new MillisecondsDurationImpl(0); static final Duration ONE_SECOND = new MillisecondsDurationImpl(1000); static final Duration ONE_MINUTE = ONE_SECOND.times(60); static final Duration ONE_HOUR = ONE_MINUTE.times(60); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Named.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Named.java deleted file mode 100755 index 780b9b83cbd..00000000000 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Named.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.sap.sailing.domain.common; - -import java.io.Serializable; - -public interface Named extends Serializable { - String getName(); -} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java index 94f2cb097de..76433b896a4 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java @@ -12,13 +12,13 @@ package com.sap.sailing.domain.common; */ public enum PassingInstruction { - None, - Port, - Starboard, - Gate, - Line, - Offset, - FixedBearing; + None(1, 2), + Port(1), + Starboard(1), + Gate(2), + Line(2), + Offset(2), + FixedBearing(1); public static PassingInstruction[] relevantValues() { PassingInstruction[] uiValues = new PassingInstruction[PassingInstruction.values().length - 1]; @@ -40,4 +40,15 @@ public enum PassingInstruction { return null; } + /** + * Denotes for which types of {@code ControlPoints} (defined by the number of marks they are made up of) + * this passing instruction is applicable.

+ * E.g. {@code [1,2]} denotes that the passing instruction is applicable for {@code ControlPoints} with either one + * or two marks. + */ + public final int[] applicability; + + private PassingInstruction(int... applicability) { + this.applicability = applicability; + } } \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Renamable.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Renamable.java index 88de4fb5506..15641f5eebf 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Renamable.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Renamable.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.common; +import com.sap.sse.common.Named; + public interface Renamable extends Named { void setName(String newName); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoreCorrectionProvider.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoreCorrectionProvider.java index e7e333e0155..4e3b966c2ae 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoreCorrectionProvider.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoreCorrectionProvider.java @@ -3,7 +3,9 @@ package com.sap.sailing.domain.common; import java.util.Map; import java.util.Set; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Named; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; /** * Describes sources of official regatta results which may be imported and applied to ScoreCorrection @@ -19,7 +21,7 @@ public interface ScoreCorrectionProvider extends Named { * score corrections taken at different times. Later score corrections are expected to be cumulative, meaning they * also contain all previous corrections. */ - public Map>> getHasResultsForBoatClassFromDateByEventName() throws Exception; + public Map>> getHasResultsForBoatClassFromDateByEventName() throws Exception; /** * @param eventName diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java index b49b524d5cb..0102c273dda 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.common; public enum ScoringSchemeType { - LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, - HIGH_POINT_FIRST_GETS_TEN, HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO + LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN, + HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Speed.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Speed.java index 192bb359998..a8944893e70 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Speed.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Speed.java @@ -2,6 +2,9 @@ package com.sap.sailing.domain.common; import java.io.Serializable; +import com.sap.sse.datamining.shared.Unit; +import com.sap.sse.datamining.shared.annotations.Statistic; + /** * A speed, convertable in various units of measure. Can be negative. * @@ -48,6 +51,7 @@ public interface Speed extends Comparable, Serializable { } }; + @Statistic(messageKey="InKnots", resultDecimals=2, resultUnit=Unit.Knots) double getKnots(); double getMetersPerSecond(); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TimeRange.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TimeRange.java new file mode 100644 index 00000000000..600474436a5 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TimeRange.java @@ -0,0 +1,67 @@ +package com.sap.sailing.domain.common; + +import java.io.Serializable; + +public interface TimeRange extends Comparable, Serializable { + /** + * @return {@code null}, if the timepoint is at the beginning of time + */ + TimePoint from(); + + /** + * @return {@code null}, if the timepoint is at the beginning of time + */ + TimePoint to(); + + /** + * Also returns true if (either or both) the from or to timepoints are equal. + * E.g. 10-100 lies within 10-00, and 10-50 lies within 10-100. + * @param other + * @return + */ + boolean liesWithin(TimeRange other); + + /** + * Reverse operation of {@link #liesWithin(TimeRange)}. + * x.includes(y) = y.liesWithin(x) + * @param other + * @return + */ + boolean includes(TimeRange other); + + boolean includes(TimePoint timePoint); + + boolean intersects(TimeRange other); + + boolean startsBefore(TimeRange other); + + boolean endsAfter(TimeRange other); + + /** + * Is the time range open ended towards older timepoints? + */ + boolean openBeginning(); + + /** + * Is the time range open ended towards newer timepoints? + */ + boolean openEnd(); + + /** + * Get time-difference between the timepoint and this timerange. + * It will be 0 if the timepoint lies within the timerange, otherwise the time-difference + * to either the start or end of the timerange, depending on which is closer. The + * time difference is a duration that is guaranteed to be positive. + */ + Duration timeDifference(TimePoint timePoint); + + /** + * Merges the two ranges, only possible if {@code other} {@link #intersects()} this range. + */ + TimeRange union(TimeRange other); + + /** + * Intersection of the two ranges, only possible if {@code other} {@link #intersects()} this range. + */ + TimeRange intersection(TimeRange other); +} \ No newline at end of file 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 d60d94ac02b..8d6775de7a9 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 @@ -13,7 +13,7 @@ import java.util.UUID; import com.sap.sailing.domain.common.LeaderboardType; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public abstract class AbstractLeaderboardDTO implements Serializable { private static final long serialVersionUID = -205106531931903527L; @@ -30,7 +30,7 @@ public abstract class AbstractLeaderboardDTO implements Serializable { public UUID defaultCourseAreaId; public String defaultCourseAreaName; public ScoringSchemeType scoringScheme; - public LeaderboardType type; + public LeaderboardType type; private Long delayToLiveInMillisForLatestRace; @@ -277,12 +277,12 @@ public abstract class AbstractLeaderboardDTO implements Serializable { return false; } - public List> getLiveRaces(long serverTimePointAsMillis) { - List> result = new ArrayList>(); + 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 Pair(race, fleet)); + result.add(new Util.Pair(race, fleet)); } } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java index 7061682af37..93bece9fbff 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java @@ -23,7 +23,7 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial private String idAsString; private BoatClassDTO boatClass; - CompetitorDTOImpl() {} + public CompetitorDTOImpl() {} public CompetitorDTOImpl(String name, Color color, String twoLetterIsoCountryCode, String threeLetterIocCountryCode, String countryName, String sailID, String idAsString, BoatClassDTO boatClass) { diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/IncrementalLeaderboardDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/IncrementalLeaderboardDTO.java index 47f9e831eea..0badc8d88e9 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/IncrementalLeaderboardDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/IncrementalLeaderboardDTO.java @@ -12,8 +12,7 @@ import java.util.Map; import java.util.Set; import com.sap.sailing.domain.common.Cloner; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; /** * Reduces the transmission size of a {@link LeaderboardDTO} by comparing this object to a previous version of @@ -177,14 +176,14 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme /** * @param previousVersion needed because after serialization/deserialization, the transient {@link #previousLeaderboard} will be null */ - public Set> getAllUnchangedCompetitorsAndKeys(LeaderboardDTO previousVersion) { - Set> result = new HashSet>(); + public Set> getAllUnchangedCompetitorsAndKeys(LeaderboardDTO previousVersion) { + Set> result = new HashSet>(); for (Map.Entry raceColumnNameAndBitSet : unchanged.entrySet()) { final long[] bitset = raceColumnNameAndBitSet.getValue(); if (bitset == null) { // this means that all entries for the column are unchanged for all competitors for (CompetitorDTO competitor : previousVersion.competitors) { - result.add(new Pair(competitor, raceColumnNameAndBitSet.getKey())); + result.add(new com.sap.sse.common.Util.Pair(competitor, raceColumnNameAndBitSet.getKey())); } } else { int competitorNumber = 0; @@ -192,7 +191,7 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme long bitValue = 1; for (int bit=0; bit(previousVersion.competitors.get(competitorNumber), raceColumnNameAndBitSet.getKey())); + result.add(new com.sap.sse.common.Util.Pair(previousVersion.competitors.get(competitorNumber), raceColumnNameAndBitSet.getKey())); } bitValue <<= 1; competitorNumber++; @@ -259,7 +258,7 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme * @author Axel Uhl (d043530) * */ - static class UnchangedLegDetails extends UnchangedWithCompetitorsInBitSet> { + static class UnchangedLegDetails extends UnchangedWithCompetitorsInBitSet> { private static final long serialVersionUID = 8726138955651801210L; UnchangedLegDetails() {} // for (de)serialization @@ -277,7 +276,7 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme * by null, meaning the race column / leg details are unchnaged for all competitors. */ public void compact() { - for (Pair key : getAllUnchangedKeys()) { + for (com.sap.sse.common.Util.Pair key : getAllUnchangedKeys()) { if (key.getB() != null) { tryToCompact(key); } @@ -290,11 +289,11 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme * or for which all entries in the race column are unchanged. */ @Override - protected int getNumberOfSetBits(Pair key) { + protected int getNumberOfSetBits(com.sap.sse.common.Util.Pair key) { int result; if (key.getB() != null) { long[] bitset = getUnchangedBitset(key); - long[] nullBitset = getUnchangedBitset(new Pair(key.getA(), null)); + long[] nullBitset = getUnchangedBitset(new com.sap.sse.common.Util.Pair(key.getA(), null)); long[] orBitset = or(bitset, nullBitset); result = getNumberOfBitsSet(orBitset); } else { @@ -413,16 +412,16 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme } final Set rowsUnchangedForCompetitors = new HashSet(); if (rowsUnchanged != null) { - for (Pair rowUnchanged : rowsUnchanged.getAllUnchangedCompetitorsAndKeys(previousVersion)) { + for (com.sap.sse.common.Util.Pair rowUnchanged : rowsUnchanged.getAllUnchangedCompetitorsAndKeys(previousVersion)) { rowsUnchangedForCompetitors.add(rowUnchanged.getA()); rows.put(rowUnchanged.getA(), previousVersion.rows.get(rowUnchanged.getA())); } } - final Set> allUnchangedLeaderboardEntriesAsCompetitorsAndColumnNames = - unchangedLeaderboardEntries == null ? new HashSet>() : + final Set> allUnchangedLeaderboardEntriesAsCompetitorsAndColumnNames = + unchangedLeaderboardEntries == null ? new HashSet>() : unchangedLeaderboardEntries.getAllUnchangedCompetitorsAndKeys(previousVersion); if (unchangedLeaderboardEntries != null) { - for (Pair competitorAndColumnName : allUnchangedLeaderboardEntriesAsCompetitorsAndColumnNames) { + for (com.sap.sse.common.Util.Pair competitorAndColumnName : allUnchangedLeaderboardEntriesAsCompetitorsAndColumnNames) { CompetitorDTO previousCompetitor = competitorAndColumnName.getA(); if (!rowsUnchangedForCompetitors.contains(previousCompetitor)) { // only care if not the entire row was marked unchanged LeaderboardEntryDTO previousEntry = previousVersion.rows.get(previousCompetitor).fieldsByRaceColumnName.get( @@ -435,14 +434,14 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme } } if (legDetailsUnchanged != null) { - for (Pair> competitorInPreviosAndColumnNameAndLegDetailsIndex : legDetailsUnchanged + for (com.sap.sse.common.Util.Pair> competitorInPreviosAndColumnNameAndLegDetailsIndex : legDetailsUnchanged .getAllUnchangedCompetitorsAndKeys(previousVersion)) { CompetitorDTO previousCompetitor = competitorInPreviosAndColumnNameAndLegDetailsIndex.getA(); final String raceColumnName = competitorInPreviosAndColumnNameAndLegDetailsIndex.getB().getA(); // if it's already clear that the entire LeaderboardEntryDTO was unchanged, the entry in legDetailsUnchanged was // merely a hint for column compression within legDetailsUnchanged and doesn't need to be handled here if (!allUnchangedLeaderboardEntriesAsCompetitorsAndColumnNames - .contains(new Pair(previousCompetitor, raceColumnName))) { + .contains(new com.sap.sse.common.Util.Pair(previousCompetitor, raceColumnName))) { final LeaderboardRowDTO leaderboardRowDTO = rows.get(previousCompetitor); LeaderboardEntryDTO leaderboardEntry = leaderboardRowDTO == null ? null : leaderboardRowDTO.fieldsByRaceColumnName.get(raceColumnName); @@ -606,7 +605,7 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme // also mark all leg details for the competitor for this column as unchanged; this will ensure // that column compression can work for leg details getLegDetailsUnchanged(previousVersion).unchanged(competitorAndRow.getKey(), - new Pair(raceColumnNameAndLeaderboardEntry.getKey(), null)); + new com.sap.sse.common.Util.Pair(raceColumnNameAndLeaderboardEntry.getKey(), null)); } else { LeaderboardEntryDTO newLeaderboardEntryDTO = new LeaderboardEntryDTO(); cloner.clone(raceColumnNameAndLeaderboardEntry.getValue(), newLeaderboardEntryDTO); @@ -627,7 +626,7 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme && previousVersion.competitors.contains(competitorAndRow.getKey())) { getLegDetailsUnchanged(previousVersion).unchanged( competitorAndRow.getKey(), - new Pair(raceColumnNameAndLeaderboardEntry.getKey(), + new com.sap.sse.common.Util.Pair(raceColumnNameAndLeaderboardEntry.getKey(), legDetailsIndex)); newLeaderboardEntryDTO.legDetails.set(legDetailsIndex, null); } @@ -641,7 +640,7 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme // however, mark the legDetails as unchanged in legDetailsUnchanged so as to allow for // all-column compression getLegDetailsUnchanged(previousVersion).unchanged(competitorAndRow.getKey(), - new Pair(raceColumnNameAndLeaderboardEntry.getKey(), null)); + new com.sap.sse.common.Util.Pair(raceColumnNameAndLeaderboardEntry.getKey(), null)); } } } @@ -693,7 +692,7 @@ public class IncrementalLeaderboardDTO extends LeaderboardDTO implements Increme if (row != null && row.fieldsByRaceColumnName != null) { for (Map.Entry e : row.fieldsByRaceColumnName.entrySet()) { getUnchangedLeaderboardEntries(previousVersion).unchanged(competitor, e.getKey()); - getLegDetailsUnchanged(previousVersion).unchanged(competitor, new Pair(e.getKey(), /* all legs unchanged */ null)); + getLegDetailsUnchanged(previousVersion).unchanged(competitor, new com.sap.sse.common.Util.Pair(e.getKey(), /* all legs unchanged */ null)); } } } 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 a051f344539..1de3beb1e7f 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 @@ -49,18 +49,26 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab */ private boolean higherScoresIsBetter; + /** + * Tells whether this leaderboard has data about overall details. This corresponds with the addOverallDetails request parameter + * for a leaderboard DTO computation. + */ + private boolean hasOverallDetails; + LeaderboardDTO() {} // for serialization /** * @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, boolean higherScoreIsBetter, UUIDGenerator uuidGenerator) { + public LeaderboardDTO(Date timePointOfLastCorrectionsValidity, String comment, boolean higherScoreIsBetter, UUIDGenerator uuidGenerator, boolean hasOverallDetails) { initCollections(); id = uuidGenerator.generateRandomUUID(); this.timePointOfLastCorrectionsValidity = timePointOfLastCorrectionsValidity; this.comment = comment; this.higherScoresIsBetter = higherScoreIsBetter; + this.hasOverallDetails = hasOverallDetails; } private void initCollections() { @@ -165,4 +173,12 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab public void setComment(String comment) { this.comment = comment; } + + /** + * Tells whether this leaderboard has data about overall details. This corresponds with the addOverallDetails request parameter + * for a leaderboard DTO computation. + */ + public boolean hasOverallDetails() { + return hasOverallDetails; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java index b5531ace848..54710cd4c22 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java @@ -44,7 +44,9 @@ public class LeaderboardEntryDTO implements Serializable { public Double windwardDistanceToOverallLeaderInMeters; - public Double averageCrossTrackErrorInMeters; + public Double averageAbsoluteCrossTrackErrorInMeters; + + public Double averageSignedCrossTrackErrorInMeters; public Double distanceToStartLineFiveSecondsBeforeStartInMeters; @@ -99,7 +101,9 @@ public class LeaderboardEntryDTO implements Serializable { final int prime = 31; int result = 1; result = prime * result - + ((averageCrossTrackErrorInMeters == null) ? 0 : averageCrossTrackErrorInMeters.hashCode()); + + ((averageAbsoluteCrossTrackErrorInMeters == null) ? 0 : averageAbsoluteCrossTrackErrorInMeters.hashCode()); + result = prime * result + + ((averageSignedCrossTrackErrorInMeters == null) ? 0 : averageSignedCrossTrackErrorInMeters.hashCode()); result = prime * result + (discarded ? 1231 : 1237); result = prime * result @@ -141,10 +145,15 @@ public class LeaderboardEntryDTO implements Serializable { if (getClass() != obj.getClass()) return false; LeaderboardEntryDTO other = (LeaderboardEntryDTO) obj; - if (averageCrossTrackErrorInMeters == null) { - if (other.averageCrossTrackErrorInMeters != null) + if (averageAbsoluteCrossTrackErrorInMeters == null) { + if (other.averageAbsoluteCrossTrackErrorInMeters != null) return false; - } else if (!averageCrossTrackErrorInMeters.equals(other.averageCrossTrackErrorInMeters)) + } else if (!averageAbsoluteCrossTrackErrorInMeters.equals(other.averageAbsoluteCrossTrackErrorInMeters)) + return false; + if (averageSignedCrossTrackErrorInMeters == null) { + if (other.averageSignedCrossTrackErrorInMeters != null) + return false; + } else if (!averageSignedCrossTrackErrorInMeters.equals(other.averageSignedCrossTrackErrorInMeters)) return false; if (discarded != other.discarded) return false; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LegEntryDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LegEntryDTO.java index e80fd7b9a0b..5e647835271 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LegEntryDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LegEntryDTO.java @@ -46,13 +46,16 @@ public class LegEntryDTO implements Serializable { public boolean finished; public Map numberOfManeuvers; public Map averageManeuverLossInMeters; - public Double averageCrossTrackErrorInMeters; + public Double averageAbsoluteCrossTrackErrorInMeters; + public Double averageSignedCrossTrackErrorInMeters; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result - + ((averageCrossTrackErrorInMeters == null) ? 0 : averageCrossTrackErrorInMeters.hashCode()); + + ((averageAbsoluteCrossTrackErrorInMeters == null) ? 0 : averageAbsoluteCrossTrackErrorInMeters.hashCode()); + result = prime * result + + ((averageSignedCrossTrackErrorInMeters == null) ? 0 : averageSignedCrossTrackErrorInMeters.hashCode()); result = prime * result + ((averageManeuverLossInMeters == null) ? 0 : averageManeuverLossInMeters.hashCode()); result = prime * result + ((averageSpeedOverGroundInKnots == null) ? 0 : averageSpeedOverGroundInKnots.hashCode()); @@ -87,10 +90,15 @@ public class LegEntryDTO implements Serializable { if (getClass() != obj.getClass()) return false; LegEntryDTO other = (LegEntryDTO) obj; - if (averageCrossTrackErrorInMeters == null) { - if (other.averageCrossTrackErrorInMeters != null) + if (averageAbsoluteCrossTrackErrorInMeters == null) { + if (other.averageAbsoluteCrossTrackErrorInMeters != null) return false; - } else if (!averageCrossTrackErrorInMeters.equals(other.averageCrossTrackErrorInMeters)) + } else if (!averageAbsoluteCrossTrackErrorInMeters.equals(other.averageAbsoluteCrossTrackErrorInMeters)) + return false; + if (averageSignedCrossTrackErrorInMeters == null) { + if (other.averageSignedCrossTrackErrorInMeters != null) + return false; + } else if (!averageSignedCrossTrackErrorInMeters.equals(other.averageSignedCrossTrackErrorInMeters)) return false; if (averageManeuverLossInMeters == null) { if (other.averageManeuverLossInMeters != null) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/NamedDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/NamedDTO.java index d577f7275d6..58fc643f6c0 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/NamedDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/NamedDTO.java @@ -2,7 +2,9 @@ package com.sap.sailing.domain.common.dto; import java.io.Serializable; -public class NamedDTO implements Serializable { +import com.sap.sse.common.Named; + +public class NamedDTO implements Named, Serializable { private static final long serialVersionUID = -6620231501480297257L; private String name; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java index 140aa1a1db0..f9d9874b71e 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.common.dto; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sse.common.Util; /** * Like the superclass, but competitors are fetched from the {@link LeaderboardDTO#getSuppressedCompetitors()} list diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTO.java index f1a6e33525f..c399d6dbdf6 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTO.java @@ -22,6 +22,8 @@ public class RaceColumnDTO extends NamedDTO implements Serializable { private Boolean isValidInTotalScore; private Double explicitFactor; private double effectiveFactor; + + private Map raceLogTrackingInfos = new HashMap(); RaceColumnDTO() {} // for GWT serialization @@ -283,4 +285,12 @@ public class RaceColumnDTO extends NamedDTO implements Serializable { public double getEffectiveFactor() { return effectiveFactor; } + + public RaceLogTrackingInfoDTO getRaceLogTrackingInfo(FleetDTO fleet) { + return raceLogTrackingInfos.get(fleet); + } + + public void setRaceLogTrackingInfo(FleetDTO fleet, RaceLogTrackingInfoDTO info) { + raceLogTrackingInfos.put(fleet, info); + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceLogTrackingInfoDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceLogTrackingInfoDTO.java new file mode 100644 index 00000000000..5a424d1412b --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceLogTrackingInfoDTO.java @@ -0,0 +1,21 @@ +package com.sap.sailing.domain.common.dto; + +import java.io.Serializable; + +import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; + +public class RaceLogTrackingInfoDTO implements Serializable { + private static final long serialVersionUID = -6861382173074620168L; + public boolean raceLogTrackerExists; + public boolean competitorRegistrationsExists; + public RaceLogTrackingState raceLogTrackingState; + + protected RaceLogTrackingInfoDTO() {} + + public RaceLogTrackingInfoDTO(boolean raceLogTrackerExists, boolean competitorRegistrationsExists, + RaceLogTrackingState raceLogTrackingState) { + this.raceLogTrackerExists = raceLogTrackerExists; + this.competitorRegistrationsExists = competitorRegistrationsExists; + this.raceLogTrackingState = raceLogTrackingState; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractColor.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractColor.java index ae5b6b0009c..adb3ee4d4fe 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractColor.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractColor.java @@ -1,14 +1,14 @@ package com.sap.sailing.domain.common.impl; import com.sap.sailing.domain.common.Color; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; public abstract class AbstractColor implements Color { private static final long serialVersionUID = 7758884012281863458L; @Override public String getAsHtml() { - Triple asRGB = getAsRGB(); + Util.Triple asRGB = getAsRGB(); return "#" + toBrowserHexValue(asRGB.getA()) + toBrowserHexValue(asRGB.getB()) + toBrowserHexValue(asRGB.getC()); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractTimePoint.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractTimePoint.java index a2177caedcc..a2be3e6e4d0 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractTimePoint.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractTimePoint.java @@ -1,112 +1,113 @@ -package com.sap.sailing.domain.common.impl; - -import java.util.Comparator; -import java.util.Date; - -import com.sap.sailing.domain.common.Duration; -import com.sap.sailing.domain.common.TimePoint; - -public abstract class AbstractTimePoint implements TimePoint { - private static final long serialVersionUID = 8825508619301420378L; - - public static Comparator TIMEPOINT_COMPARATOR = new SerializableComparator() { - private static final long serialVersionUID = 7644726881387366025L; - - @Override - public int compare(TimePoint o1, TimePoint o2) { - long milliDiff = o1.asMillis() - o2.asMillis(); - return milliDiff<0 ? -1 : milliDiff == 0 ? 0 : 1; - } - }; - - @Override - public int compareTo(TimePoint o) { - return TIMEPOINT_COMPARATOR.compare(this, o); - } - - @Override - public Date asDate() { - Date result = getDateFromCache(); - if (result == null) { - result = new Date(asMillis()); - cacheDate(result); - } - return result; - } - - @Override - public TimePoint plus(Duration duration) { - final TimePoint result; - if (duration != null) { - result = plus(duration.asMillis()); - } else { - result = this; - } - return result; - } - - @Override - public TimePoint minus(Duration duration) { - final TimePoint result; - if (duration != null) { - result = minus(duration.asMillis()); - } else { - result = this; - } - return result; - } - - @Override - public Duration until(TimePoint later) { - return new MillisecondsDurationImpl(later.asMillis()-asMillis()); - } - - @Override - public TimePoint plus(long milliseconds) { - return new MillisecondsTimePoint(asMillis()+milliseconds); - } - - @Override - public TimePoint minus(long milliseconds) { - return plus(-milliseconds); - } - - @Override - public boolean after(TimePoint other) { - return this.compareTo(other) > 0; - } - - @Override - public boolean before(TimePoint other) { - return this.compareTo(other) < 0; - } - - @Override - public int hashCode() { - return (int) (asMillis() & Integer.MAX_VALUE); - } - - @Override - public boolean equals(Object obj) { - return compareTo((TimePoint) obj) == 0; - } - - @Override - public String toString() { - return asDate().toString(); - } - - /** - * A subclass may implement a cache for the {@link Date} representation by overriding this method as well - * as {@link #cacheDate(Date)}. - */ - protected Date getDateFromCache() { - return null; - } - - /** - * A subclass may implement a cache for the {@link Date} representation by overriding this method as well - * as {@link #getDate()}. - */ - protected void cacheDate(Date date) {} -} +package com.sap.sailing.domain.common.impl; + +import java.util.Comparator; +import java.util.Date; + +import com.sap.sailing.domain.common.Duration; +import com.sap.sailing.domain.common.TimePoint; + +public abstract class AbstractTimePoint implements TimePoint { + private static final long serialVersionUID = 8825508619301420378L; + + public static Comparator TIMEPOINT_COMPARATOR = new SerializableComparator() { + private static final long serialVersionUID = 7644726881387366025L; + + @Override + public int compare(TimePoint o1, TimePoint o2) { + long o1Millis = o1.asMillis(); + long o2Millis = o2.asMillis(); + return o1Millis 0; + } + + @Override + public boolean before(TimePoint other) { + return this.compareTo(other) < 0; + } + + @Override + public int hashCode() { + return (int) (asMillis() & Integer.MAX_VALUE); + } + + @Override + public boolean equals(Object obj) { + return compareTo((TimePoint) obj) == 0; + } + + @Override + public String toString() { + return asDate().toString(); + } + + /** + * A subclass may implement a cache for the {@link Date} representation by overriding this method as well + * as {@link #cacheDate(Date)}. + */ + protected Date getDateFromCache() { + return null; + } + + /** + * A subclass may implement a cache for the {@link Date} representation by overriding this method as well + * as {@link #getDate()}. + */ + protected void cacheDate(Date date) {} +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ColorMapImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ColorMapImpl.java index 0f307f87777..e64431c5488 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ColorMapImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ColorMapImpl.java @@ -6,7 +6,7 @@ import java.util.List; import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.ColorMap; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; /** @@ -64,7 +64,7 @@ public class ColorMapImpl implements ColorMap { public boolean addBlockedColor(Color color) { boolean result = false; if(color != null) { - Triple asHSV = color.getAsHSV(); + Util.Triple asHSV = color.getAsHSV(); result = blockedColors.add(new HSVColor(asHSV.getA(), asHSV.getB(), asHSV.getC())); } return result; @@ -73,7 +73,7 @@ public class ColorMapImpl implements ColorMap { public boolean removeBlockedColor(Color color) { boolean result = false; if(color != null) { - Triple asHSV = color.getAsHSV(); + Util.Triple asHSV = color.getAsHSV(); result = blockedColors.remove(new HSVColor(asHSV.getA(), asHSV.getB(), asHSV.getC())); } return result; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/HSVColor.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/HSVColor.java index 519b08a64b8..5d6e45ceb0c 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/HSVColor.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/HSVColor.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.common.impl; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; /** * A color defined in the HSV color schema (hue, saturation, brightness) Hue is a degree between 0.0 and 360.0 @@ -35,7 +35,7 @@ public class HSVColor extends AbstractColor { } @Override - public Triple getAsRGB() { + public Util.Triple getAsRGB() { float r, b, g; if (saturation == 0) { @@ -80,14 +80,14 @@ public class HSVColor extends AbstractColor { b = q; } } - Triple RGBColor = new Triple(Math.round(r * 255), + Util.Triple RGBColor = new Util.Triple(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)); return RGBColor; } @Override - public Triple getAsHSV() { - return new Triple(hue, saturation, brightness); + public Util.Triple getAsHSV() { + return new Util.Triple(hue, saturation, brightness); } @Override diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NamedImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NamedImpl.java index 3e815cffa95..ad46af369a6 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NamedImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NamedImpl.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.common.impl; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; public class NamedImpl implements Named { private static final long serialVersionUID = -4815125282671451300L; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java index 33a2d247546..32713ccc178 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java @@ -13,7 +13,7 @@ import java.util.Comparator; public class NaturalComparator implements Comparator { boolean caseSensitive; - public NaturalComparator(boolean caseSensitive){ + public NaturalComparator(boolean caseSensitive) { this.caseSensitive = caseSensitive; } @@ -26,9 +26,9 @@ public class NaturalComparator implements Comparator { */ @Override public int compare(String a, String b) { - if(!caseSensitive){ + if (!caseSensitive){ a = a.toLowerCase(); - b = a.toLowerCase(); + b = b.toLowerCase(); } int result = 0; int aIndex = 0; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/QRCodeUtils.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/QRCodeUtils.java index 551aab07d1e..2c48304a04b 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/QRCodeUtils.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/QRCodeUtils.java @@ -2,7 +2,7 @@ package com.sap.sailing.domain.common.impl; import java.net.URLEncoder; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; /** * This class is used by our backend, in GWT-client code and by the Android app. Therefore we cannot use classes like @@ -18,7 +18,7 @@ public class QRCodeUtils { return apkUrl + "#" + fragmentKey + "=" + encodedIdentifier; } - public static Pair splitQRContent(String qrCodeContent) { + public static Util.Pair splitQRContent(String qrCodeContent) { int fragmentIndex = qrCodeContent.lastIndexOf('#'); if (fragmentIndex == -1 || fragmentIndex == 0 || qrCodeContent.length() == fragmentIndex + 1) { throw new IllegalArgumentException("There is no server or identifier."); @@ -31,7 +31,7 @@ public class QRCodeUtils { deviceIdentifier = deviceIdentifier.replaceAll("%20", " "); String apkUrl = qrCodeContent.substring(0, fragmentIndex); - return new Pair(deviceIdentifier, apkUrl); + return new Util.Pair(deviceIdentifier, apkUrl); } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/RGBColor.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/RGBColor.java index 3faa62a768f..818f18cc729 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/RGBColor.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/RGBColor.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.common.impl; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; /** * A color defined in the RGB color schema @@ -66,11 +66,11 @@ public class RGBColor extends AbstractColor { } @Override - public Triple getAsRGB() { - return new Triple(red, green, blue); + public Util.Triple getAsRGB() { + return new Util.Triple(red, green, blue); } - public Triple getAsHSV() { + public Util.Triple getAsHSV() { float hue, saturation, brightness; int cmax = (red > green) ? red : green; if (blue > cmax) { @@ -107,7 +107,7 @@ public class RGBColor extends AbstractColor { } } - Triple HSVColor = new Triple(hue, saturation, brightness); + Util.Triple HSVColor = new Util.Triple(hue, saturation, brightness); return HSVColor; } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/TimeRangeImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/TimeRangeImpl.java new file mode 100644 index 00000000000..943f337b459 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/TimeRangeImpl.java @@ -0,0 +1,143 @@ +package com.sap.sailing.domain.common.impl; + +import com.sap.sailing.domain.common.Duration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimeRange; +import com.sap.sse.common.Util; + +public class TimeRangeImpl extends Util.Pair implements TimeRange { + public static final TimePoint BeginningOfTime = new MillisecondsTimePoint(Long.MIN_VALUE); + public static final TimePoint EndOfTime = new MillisecondsTimePoint(Long.MAX_VALUE); + private static final long serialVersionUID = 8710198176227507300L; + + public static TimeRange create(long fromMillis, long toMillis) { + return new TimeRangeImpl(new MillisecondsTimePoint(fromMillis), new MillisecondsTimePoint(toMillis)); + } + + public TimeRangeImpl(TimePoint from, TimePoint to) { + super(from == null ? BeginningOfTime : from, to == null ? EndOfTime : to); + if (from().after(to())) { + throw new IllegalArgumentException("from " + from() + " must lie before to " + to() + " in a TimeRange"); + } + } + + @Override + public int compareTo(TimeRange other) { + final int result; + if (other.from().equals(from()) && other.to().equals(to())) { + result = 0; + } else { + result = startsBefore(other) ? -1 : 1; + } + return result; + } + + @Override + public TimePoint from() { + return getA(); + } + + @Override + public TimePoint to() { + return getB(); + } + + @Override + public boolean liesWithin(TimeRange other) { + return from().compareTo(other.from()) >= 0 && to().compareTo(other.to()) <= 0; + } + + @Override + public boolean includes(TimeRange other) { + return other.liesWithin(this); + } + + @Override + public boolean intersects(TimeRange other) { + return includes(other.from()) || includes(other.to()) || liesWithin(other); + } + + @Override + public boolean includes(TimePoint timePoint) { + return from().compareTo(timePoint) <= 0 && to().compareTo(timePoint) >= 0; + } + + @Override + public boolean startsBefore(TimeRange other) { + return from().before(other.from()); + } + + @Override + public boolean endsAfter(TimeRange other) { + return to().after(other.to()); + } + + @Override + public Duration timeDifference(TimePoint timePoint) { + final Duration result; + if (includes(timePoint)) { + result = Duration.NULL; + } else if (timePoint.before(from())) { + result = timePoint.until(from()); + } else { + result = to().until(timePoint); + } + return result; + } + + @Override + public TimeRange union(TimeRange other) { + final TimeRange result; + if (!intersects(other)) { + result = null; + } else { + TimePoint newFrom = startsBefore(other) ? from() : other.from(); + TimePoint newTo = endsAfter(other) ? to() : other.to(); + result = new TimeRangeImpl(newFrom, newTo); + } + return result; + } + + @Override + public boolean openBeginning() { + return from().equals(BeginningOfTime); + } + + @Override + public boolean openEnd() { + return to().equals(EndOfTime); + } + + @Override + public TimeRange intersection(TimeRange other) { + final TimeRange result; + if (!intersects(other)) { + result = null; + } else { + TimePoint newFrom = startsBefore(other) ? other.from() : from(); + TimePoint newTo = endsAfter(other) ? other.to() : to(); + result = new TimeRangeImpl(newFrom, newTo); + } + return result; + } + + @Override + public boolean equals(Object obj) { + final boolean result; + if (this == obj) { + result = true; + } else { + if (obj instanceof TimeRange) { + TimeRange other = (TimeRange) obj; + return from().equals(other.from()) && to().equals(other.to()); + } + result = super.equals(obj); + } + return result; + } + + @Override + public String toString() { + return from() + "-" + to(); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/WindSourceWithAdditionalID.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/WindSourceWithAdditionalID.java index acf438f171d..db560f16a0c 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/WindSourceWithAdditionalID.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/WindSourceWithAdditionalID.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.common.impl; +import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; /** diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java index c0ba3da7a37..d0debae5ad7 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java @@ -1,18 +1,29 @@ package com.sap.sailing.domain.common.racelog; /** + * The status of a race in a race log * @author D053502 * */ public enum RaceLogRaceStatus { - UNKNOWN, UNSCHEDULED, SCHEDULED, STARTPHASE, RUNNING, FINISHING, FINISHED; + UNKNOWN(0), UNSCHEDULED(1), SCHEDULED(2), STARTPHASE(3), RUNNING(4), FINISHING(5), FINISHED(6); + /** the order number represents the natural order of the states */ + private int orderNumber; + + RaceLogRaceStatus(int orderNumber) { + this.orderNumber = orderNumber; + } + public static boolean isActive(RaceLogRaceStatus status) { if (status == null) { return false; } - return status.equals(SCHEDULED) || status.equals(STARTPHASE) || status.equals(RUNNING) - || status.equals(FINISHING); + return status.equals(SCHEDULED) || status.equals(STARTPHASE) || status.equals(RUNNING) || status.equals(FINISHING); + } + + public int getOrderNumber() { + return orderNumber; } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NoCorrespondingServiceRegisteredException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NoCorrespondingServiceRegisteredException.java new file mode 100644 index 00000000000..daad2f61a18 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NoCorrespondingServiceRegisteredException.java @@ -0,0 +1,24 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +public class NoCorrespondingServiceRegisteredException extends RuntimeException { + public String type; + public String serviceInterface; + private static final long serialVersionUID = -358955216089477585L; + + protected NoCorrespondingServiceRegisteredException() {} + + public NoCorrespondingServiceRegisteredException(String message, String type, String serviceInterface) { + super(message); + this.type = type; + this.serviceInterface = serviceInterface; + } + + public String getDeviceType() { + return type; + } + + @Override + public String toString() { + return super.toString() + " (Service: " + serviceInterface + ", Type: " + type + ")"; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NotDenotableForRaceLogTrackingException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NotDenotableForRaceLogTrackingException.java new file mode 100644 index 00000000000..296f4e29659 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NotDenotableForRaceLogTrackingException.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +public class NotDenotableForRaceLogTrackingException extends Exception { + + private static final long serialVersionUID = 6721038282862173471L; + + public NotDenotableForRaceLogTrackingException() { + super(); + } + + public NotDenotableForRaceLogTrackingException(String msg) { + super(msg); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NotDenotedForRaceLogTrackingException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NotDenotedForRaceLogTrackingException.java new file mode 100644 index 00000000000..badfccb5523 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NotDenotedForRaceLogTrackingException.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +public class NotDenotedForRaceLogTrackingException extends Exception { + + private static final long serialVersionUID = 6721038282862173471L; + + public NotDenotedForRaceLogTrackingException() { + super(); + } + + public NotDenotedForRaceLogTrackingException(String msg) { + super(msg); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NotRevokableException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NotRevokableException.java new file mode 100644 index 00000000000..f30eda4a13b --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/NotRevokableException.java @@ -0,0 +1,13 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +public class NotRevokableException extends Exception { + private static final long serialVersionUID = 6825451915706047105L; + + public NotRevokableException() { + super(); + } + + public NotRevokableException(String msg) { + super(msg); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/RaceLogRaceTrackerExistsException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/RaceLogRaceTrackerExistsException.java new file mode 100644 index 00000000000..6f1bb16b2db --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/RaceLogRaceTrackerExistsException.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +public class RaceLogRaceTrackerExistsException extends RuntimeException { + + private static final long serialVersionUID = 6721038282862173471L; + + public RaceLogRaceTrackerExistsException() { + super(); + } + + public RaceLogRaceTrackerExistsException(String raceLogName) { + super("Tracker for racelog " + raceLogName + " already exists"); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/RaceLogTrackingState.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/RaceLogTrackingState.java new file mode 100644 index 00000000000..9f18093d622 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/RaceLogTrackingState.java @@ -0,0 +1,30 @@ +package com.sap.sailing.domain.common.racelog.tracking; + + +/** + * The state of the race tracked via the racelog. This can be determined by inspecting the corresponding events + * in the racelog with the {@link RaceLogTrackingStateAnalyzer}. + * @author Fredrik Teschke + * + */ +public enum RaceLogTrackingState { + //can't determine whether tracking has ended from race log, therefore omitting TRACKING_ENDED state for now + /** + * no {@link DenoteForTrackingEvent} is present + */ + NOT_A_RACELOG_TRACKED_RACE, + + /** + * a {@link DenoteForTrackingEvent} is present, but no {@link StartTrackingEvent} yet. + */ + AWAITING_RACE_DEFINITION, + + /** + * a {@link StartTrackingEvent} is present + */ + TRACKING; + + public boolean isForTracking() { + return this != NOT_A_RACELOG_TRACKED_RACE; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/RaceNotCreatedException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/RaceNotCreatedException.java new file mode 100644 index 00000000000..b3b7507efc4 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/RaceNotCreatedException.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +public class RaceNotCreatedException extends RuntimeException { + + private static final long serialVersionUID = 6721038282862173471L; + + public RaceNotCreatedException() { + super(); + } + + public RaceNotCreatedException(String msg) { + super(msg); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/SingleTypeBasedServiceFinderImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/SingleTypeBasedServiceFinderImpl.java new file mode 100644 index 00000000000..1ea432216c9 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/SingleTypeBasedServiceFinderImpl.java @@ -0,0 +1,37 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +/** + * Only can provide the service for a single {@code type}, e.g. on smartphones with no OSGi context. + * Optionally, a {@link #setFallbackService(Object) fallback} can be set. + * @author Fredrik Teschke + */ +public class SingleTypeBasedServiceFinderImpl implements +TypeBasedServiceFinder { + private final ServiceType service; + private final String type; + private ServiceType fallback; + + public SingleTypeBasedServiceFinderImpl(ServiceType service, ServiceType fallback, String type) { + this.service = service; + this.type = type; + this.fallback = fallback; + } + + public SingleTypeBasedServiceFinderImpl(ServiceType service, String type) { + this(service, null, type); + } + + @Override + public ServiceType findService(String type) + throws NoCorrespondingServiceRegisteredException { + if (type.equals(this.type)) return service; + if (fallback != null) return fallback; + throw new NoCorrespondingServiceRegisteredException("Only one service registered", type, service.getClass().getSimpleName()); + } + + @Override + public void setFallbackService(ServiceType fallback) { + this.fallback = fallback; + } + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TransformationException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TransformationException.java new file mode 100644 index 00000000000..0a63c6d8b5e --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TransformationException.java @@ -0,0 +1,30 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +import java.io.IOException; + +public class TransformationException extends IOException { + private static final long serialVersionUID = -7795777645371428971L; + + public TransformationException() { + } + + public TransformationException(Class from, Class to, String at, Throwable e) { + super("Couldn't transform "+from.getName()+" into "+to.getName()+" at "+at, e); + } + + public TransformationException(Class from, Class to, String at) { + this(from, to, at, null); + } + + public TransformationException(String message, Throwable e) { + super(message, e); + } + + public TransformationException(Throwable e) { + super(e); + } + + public TransformationException(String message) { + super(message); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TransformationHandler.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TransformationHandler.java new file mode 100644 index 00000000000..ed5cb641218 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TransformationHandler.java @@ -0,0 +1,13 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +/** + * Handle transformation between two types. Most likely use-case is serialization, + * e.g. for JSON documents or database persistence. + * + * @author Fredrik Teschke + * + */ +public interface TransformationHandler { + T1 transformBack(T2 object) throws TransformationException; + T2 transformForth(T1 object) throws TransformationException; +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TypeBasedServiceFinder.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TypeBasedServiceFinder.java new file mode 100644 index 00000000000..33f8ac77c84 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TypeBasedServiceFinder.java @@ -0,0 +1,29 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +/** + * @author Fredrik Teschke + * + * @param Specifies desired the service interface + */ +public interface TypeBasedServiceFinder { + public static final String TYPE = "type"; + + /** + * Find a service that implements the interface for the specified {@code type} + * of object (e.g. type of a {@link GPSFix}). + * @param type A {@link String} that specifies for which type the service should be registered + * @throws NoCorrespondingServiceRegisteredException Thrown, if no service is registered for this type. + */ + ServiceType findService(String type) throws NoCorrespondingServiceRegisteredException; + + /** + * Sets a fallback service (optional), that is used if no service is registered for a {@code type}. + * This should be a generic service, that can deal with any {@code type}, and provides a + * "better-than-nothing" implementation, which will be returned instead of throwing a + * {@link NoCorrespondingServiceRegisteredException}. + *

+ * This method does not need to be implemented, but can remain a stub, if no fallback behaviour + * is desired. + */ + void setFallbackService(ServiceType fallback); +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TypeBasedServiceFinderFactory.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TypeBasedServiceFinderFactory.java new file mode 100644 index 00000000000..2b03570cf5a --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TypeBasedServiceFinderFactory.java @@ -0,0 +1,18 @@ +package com.sap.sailing.domain.common.racelog.tracking; + + +/** + * A factory for creating {@link TypeBasedServiceFinder}s, for a specific type of service. + * The desired service type is specified by the {@link Class} object passed to the + * {@link #createServiceFinder(Class)} method. + * + * The created {@link TypeBasedServiceFinder} can then be used to find the desired + * service for a specific type. E.g., there may be a persistence service interface for + * {@link GPSFix}es, and there are several different service registered, depending on the type + * of {@link GPSFix}. The service finder then can find the appropriate service for each fix. + * @author Fredrik Teschke + * + */ +public interface TypeBasedServiceFinderFactory { + TypeBasedServiceFinder createServiceFinder(Class clazz); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/AbstractScalarValue.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/AbstractScalarValue.java similarity index 83% rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/AbstractScalarValue.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/AbstractScalarValue.java index 8866576e8aa..c14cc01883c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/AbstractScalarValue.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/AbstractScalarValue.java @@ -1,4 +1,5 @@ -package com.sap.sailing.domain.confidence; +package com.sap.sailing.domain.common.scalablevalue; + /** * A {@link ScalableValue} where the ValueType and the AveragesTo type are both the same because diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/IsScalable.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/IsScalable.java similarity index 73% rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/IsScalable.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/IsScalable.java index cf5d21aa3bd..4789e8a2a3d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/IsScalable.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/IsScalable.java @@ -1,4 +1,4 @@ -package com.sap.sailing.domain.confidence; +package com.sap.sailing.domain.common.scalablevalue; import java.io.Serializable; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ScalableValue.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/ScalableValue.java similarity index 81% rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ScalableValue.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/ScalableValue.java index a65e5696786..61ae9bbf7ca 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ScalableValue.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/ScalableValue.java @@ -1,4 +1,4 @@ -package com.sap.sailing.domain.confidence; +package com.sap.sailing.domain.common.scalablevalue; public interface ScalableValue { ScalableValue multiply(double factor); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableBearing.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableBearing.java similarity index 91% rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableBearing.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableBearing.java index 5fb345832fc..bc9985de895 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableBearing.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableBearing.java @@ -1,9 +1,9 @@ -package com.sap.sailing.domain.base.impl; +package com.sap.sailing.domain.common.scalablevalue.impl; -import com.sap.sailing.domain.base.BearingWithConfidence.DoublePair; import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.DoublePair; import com.sap.sailing.domain.common.impl.RadianBearingImpl; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; public class ScalableBearing implements ScalableValue { private final double sin; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalablePosition.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalablePosition.java similarity index 91% rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalablePosition.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalablePosition.java index 17d41414d81..fe37ec0f32e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalablePosition.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalablePosition.java @@ -1,8 +1,8 @@ -package com.sap.sailing.domain.base.impl; +package com.sap.sailing.domain.common.scalablevalue.impl; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.impl.RadianPosition; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; public class ScalablePosition implements ScalableValue { private final double x, y, z; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableSpeed.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableSpeed.java similarity index 87% rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableSpeed.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableSpeed.java index 9093d34f8f8..6165e0acf56 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableSpeed.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableSpeed.java @@ -1,8 +1,8 @@ -package com.sap.sailing.domain.base.impl; +package com.sap.sailing.domain.common.scalablevalue.impl; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; public class ScalableSpeed implements ScalableValue { private final double knots; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableSpeedWithBearing.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableSpeedWithBearing.java similarity index 80% rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableSpeedWithBearing.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableSpeedWithBearing.java index 03694b57c8b..719d111965f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableSpeedWithBearing.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableSpeedWithBearing.java @@ -1,4 +1,4 @@ -package com.sap.sailing.domain.base.impl; +package com.sap.sailing.domain.common.scalablevalue.impl; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Speed; @@ -6,8 +6,8 @@ import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.RadianBearingImpl; -import com.sap.sailing.domain.common.impl.Util.Triple; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; +import com.sap.sse.common.Util; /** * Separately scales speed and bearing. Instead of considering speed and bearing a single vector that can be scaled, the @@ -19,7 +19,7 @@ import com.sap.sailing.domain.confidence.ScalableValue; * @author Axel Uhl (d043530) * */ -public class ScalableSpeedWithBearing implements ScalableValue, SpeedWithBearing> { +public class ScalableSpeedWithBearing implements ScalableValue, SpeedWithBearing> { private final Speed speed; private final double sin; private final double cos; @@ -42,7 +42,7 @@ public class ScalableSpeedWithBearing implements ScalableValue, SpeedWithBearing> t) { + public ScalableSpeedWithBearing add(ScalableValue, SpeedWithBearing> t) { Speed newSpeed = new KnotSpeedImpl(speed.getKnots() + t.getValue().getA().getKnots()); return new ScalableSpeedWithBearing(newSpeed, sin+t.getValue().getB(), cos+t.getValue().getC()); } @@ -61,8 +61,8 @@ public class ScalableSpeedWithBearing implements ScalableValue getValue() { - return new Triple(speed, sin, cos); + public Util.Triple getValue() { + return new Util.Triple(speed, sin, cos); } } \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/trackfiles/TrackFilesDataSource.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/trackfiles/TrackFilesDataSource.java index 44c5be215a6..173e168cb52 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/trackfiles/TrackFilesDataSource.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/trackfiles/TrackFilesDataSource.java @@ -1,5 +1,5 @@ package com.sap.sailing.domain.common.trackfiles; public enum TrackFilesDataSource { - METADATA, COMPETITORS, BUOYS, WIND, MANEUVERS + METADATA, COMPETITORS, BUOYS, WIND, MANEUVERS } diff --git a/java/com.sap.sailing.domain.deckmanadapter.test/.classpath b/java/com.sap.sailing.domain.deckmanadapter.test/.classpath new file mode 100755 index 00000000000..a14ade4efd3 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter.test/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/java/com.sap.sailing.domain.deckmanadapter.test/.project b/java/com.sap.sailing.domain.deckmanadapter.test/.project new file mode 100755 index 00000000000..fd1b6107082 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter.test/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.domain.deckmanadapter.test + + + + + + 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.gwt.ui.test/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.deckmanadapter.test/.settings/org.eclipse.jdt.core.prefs similarity index 54% rename from java/com.sap.sailing.gwt.ui.test/.settings/org.eclipse.jdt.core.prefs rename to java/com.sap.sailing.domain.deckmanadapter.test/.settings/org.eclipse.jdt.core.prefs index 48319e0614a..f42de363afa 100755 --- a/java/com.sap.sailing.gwt.ui.test/.settings/org.eclipse.jdt.core.prefs +++ b/java/com.sap.sailing.domain.deckmanadapter.test/.settings/org.eclipse.jdt.core.prefs @@ -1,8 +1,7 @@ -#Wed Nov 02 13:57:42 CET 2011 -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.6 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/java/de.csenk.gwt.ws/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.deckmanadapter.test/.settings/org.eclipse.pde.core.prefs similarity index 100% rename from java/de.csenk.gwt.ws/.settings/org.eclipse.pde.core.prefs rename to java/com.sap.sailing.domain.deckmanadapter.test/.settings/org.eclipse.pde.core.prefs diff --git a/java/com.sap.sailing.domain.deckmanadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.deckmanadapter.test/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..58ddcc43fd4 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter.test/META-INF/MANIFEST.MF @@ -0,0 +1,9 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Test +Bundle-SymbolicName: com.sap.sailing.domain.deckmanadapter.test +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Fragment-Host: com.sap.sailing.domain.deckmanadapter +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Require-Bundle: org.junit4 diff --git a/java/com.sap.sailing.domain.deckmanadapter.test/build.properties b/java/com.sap.sailing.domain.deckmanadapter.test/build.properties new file mode 100755 index 00000000000..f11210d3f2c --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter.test/build.properties @@ -0,0 +1,5 @@ +source.. = src/,\ + resources/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.domain.deckmanadapter.test/pom.xml b/java/com.sap.sailing.domain.deckmanadapter.test/pom.xml new file mode 100644 index 00000000000..47ba702860e --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter.test/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.domain.deckmanadapter.test + eclipse-test-plugin + diff --git a/java/com.sap.sailing.domain.deckmanadapter.test/resources/simpletest.csv b/java/com.sap.sailing.domain.deckmanadapter.test/resources/simpletest.csv new file mode 100644 index 00000000000..82bd4446375 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter.test/resources/simpletest.csv @@ -0,0 +1,4 @@ +date,race_name,leg_name,leg_score,boat_speed,heel,leeway,rudder,heading,course,depth,tws,twa,twd,aws,awa,latitude,longitude,sog,cog,abstwa,absawa,absheel,absleeway,absrudder,foresail,staysail,mainsail,forestay,rake,vmg,status,starboard_port,rudder2,keelang,absrudder2,abskeelang +2013-10-11 07:16:33 UTC,2013_10_11_aa_Training01,,,12.65,-4.4,-0.5,20.1,305.0,304.5,,13.8,152.0,97.0,6.6,86.0,45.657005,13.70788,12.48,305.0,152.0,86.0,-4.4,-0.5,20.1,A2F,,M offshore D,2.4,,-11.1692870496654,DOWNWIND,starboard,41.9,65.0,41.9,65.0 +2013-10-11 07:16:34 UTC,2013_10_11_aa_Training01,,,12.59,-6.8,-0.4,20.1,305.0,304.6,,13.8,150.0,97.0,6.9,85.0,45.6570633333333,13.7077783333333,12.22,298.0,150.0,85.0,-6.8,-0.4,20.1,A2F,,M offshore D,2.8,,-10.9032598336461,DOWNWIND,starboard,41.9,65.0,41.9,65.0 +2013-10-11 07:16:35 UTC,2013_10_11_aa_Training01,,,12.54,-6.0,-0.5,20.1,307.0,306.5,,13.8,148.0,97.0,7.3,83.0,45.6570783333333,13.7077666666667,11.91,301.0,148.0,83.0,-6.0,-0.5,20.1,A2F,,M offshore D,2.2,,-10.6345231258016,DOWNWIND,starboard,41.9,65.0,41.9,65.0 diff --git a/java/com.sap.sailing.domain.deckmanadapter.test/src/com/sap/sailing/domain/deckmanadapter/test/TestDeckmanLogfileParsing.java b/java/com.sap.sailing.domain.deckmanadapter.test/src/com/sap/sailing/domain/deckmanadapter/test/TestDeckmanLogfileParsing.java new file mode 100755 index 00000000000..03df2067fac --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter.test/src/com/sap/sailing/domain/deckmanadapter/test/TestDeckmanLogfileParsing.java @@ -0,0 +1,56 @@ +package com.sap.sailing.domain.deckmanadapter.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Iterator; + +import org.junit.Test; + +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.deckmanadapter.DeckmanAdapter; +import com.sap.sailing.domain.deckmanadapter.DeckmanAdapterFactory; +import com.sap.sailing.domain.deckmanadapter.Record; +import com.sap.sailing.domain.deckmanadapter.impl.FieldType; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.Wind; + +public class TestDeckmanLogfileParsing { + private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + + @Test + public void testSimpleLogFileParsing() throws IOException, ParseException { + Reader r = new InputStreamReader(getClass().getClassLoader().getResourceAsStream("simpletest.csv")); + DeckmanAdapter adapter = DeckmanAdapterFactory.INSTANCE.createDeckmanAdapter(); + Iterator recordIter = adapter.parseLogFile(r); + // date,race_name,leg_name,leg_score,boat_speed,heel,leeway,rudder,heading,course,depth,tws,twa,twd,aws,awa,latitude,longitude,sog,cog,abstwa,absawa,absheel,absleeway,absrudder,foresail,staysail,mainsail,forestay,rake,vmg,status,starboard_port,rudder2,keelang,absrudder2,abskeelang + // 2013-10-11 07:16:33 UTC,2013_10_11_aa_Training01,,,12.65,-4.4,-0.5,20.1,305.0,304.5,,13.8,152.0,97.0,6.6,86.0,45.657005,13.70788,12.48,305.0,152.0,86.0,-4.4,-0.5,20.1,A2F,,M offshore D,2.4,,-11.1692870496654,DOWNWIND,starboard,41.9,65.0,41.9,65.0 + Record r1 = recordIter.next(); + TimePoint r1TimePoint = r1.getTimePoint(); + Position r1Position = r1.getPosition(); + GPSFixMoving r1Fix = r1.getGpsFix(); + Wind r1Wind = r1.getWind(); + assertEquals(dateFormat.parse("2013-10-11 07:16:33 UTC"), r1TimePoint.asDate()); + assertEquals(r1Fix.getPosition(), r1Position); + // SOG / COG: 12.48,305.0 + assertEquals(12.48, r1Fix.getSpeed().getKnots(), 0.000001); + assertEquals(305.0, r1Fix.getSpeed().getBearing().getDegrees(), 0.000001); + assertEquals(45.657005, r1Position.getLatDeg(), 0.0000001); + assertEquals(13.70788, r1Position.getLngDeg(), 0.0000001); + assertEquals(13.8, r1Wind.getKnots(), 0.000001); + assertEquals(97.0, r1Wind.getBearing().getDegrees(), 0.000001); + assertEquals(12.65, Double.valueOf(r1.getField(FieldType.boat_speed)), 0.000001); + + recordIter.next(); + recordIter.next(); + assertFalse(recordIter.hasNext()); + + } +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/.classpath b/java/com.sap.sailing.domain.deckmanadapter/.classpath new file mode 100755 index 00000000000..098194ca4b7 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/de.csenk.gwt.ws/.project b/java/com.sap.sailing.domain.deckmanadapter/.project similarity index 92% rename from java/de.csenk.gwt.ws/.project rename to java/com.sap.sailing.domain.deckmanadapter/.project index b1a9930e81a..93bbb09883e 100755 --- a/java/de.csenk.gwt.ws/.project +++ b/java/com.sap.sailing.domain.deckmanadapter/.project @@ -1,6 +1,6 @@ - de.csenk.gwt.ws + com.sap.sailing.domain.deckmanadapter diff --git a/java/com.sap.sailing.domain.deckmanadapter/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.deckmanadapter/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..f42de363afa --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/java/com.sap.sailing.domain.deckmanadapter/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.deckmanadapter/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.domain.deckmanadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.deckmanadapter/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..4429c780a59 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/META-INF/MANIFEST.MF @@ -0,0 +1,13 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Deckmanadapter +Bundle-SymbolicName: com.sap.sailing.domain.deckmanadapter +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Require-Bundle: com.sap.sailing.domain, + com.sap.sailing.domain.common, + com.sap.sailing.domain.shared.android, + com.sap.sailing.server.trackfiles +Import-Package: org.osgi.framework +Bundle-Activator: com.sap.sailing.domain.deckmanadapter.impl.Activator diff --git a/java/de.csenk.gwt.ws/build.properties b/java/com.sap.sailing.domain.deckmanadapter/build.properties similarity index 100% rename from java/de.csenk.gwt.ws/build.properties rename to java/com.sap.sailing.domain.deckmanadapter/build.properties diff --git a/java/de.csenk.gwt.ws/pom.xml b/java/com.sap.sailing.domain.deckmanadapter/pom.xml old mode 100755 new mode 100644 similarity index 52% rename from java/de.csenk.gwt.ws/pom.xml rename to java/com.sap.sailing.domain.deckmanadapter/pom.xml index 8775b6d3cc7..e84b15f0224 --- a/java/de.csenk.gwt.ws/pom.xml +++ b/java/com.sap.sailing.domain.deckmanadapter/pom.xml @@ -7,25 +7,6 @@ com.sap.sailing 1.0.0-SNAPSHOT - de.csenk.gwt.ws + com.sap.sailing.domain.deckmanadapter eclipse-plugin - - - - - org.eclipse.tycho - tycho-source-plugin - ${tycho-version} - - - plugin-source - generate-sources - - plugin-source - - - - - - diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/DeckmanAdapter.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/DeckmanAdapter.java new file mode 100755 index 00000000000..75b68f4b68e --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/DeckmanAdapter.java @@ -0,0 +1,8 @@ +package com.sap.sailing.domain.deckmanadapter; + +import java.io.IOException; +import java.io.Reader; + +public interface DeckmanAdapter { + LogFile parseLogFile(Reader r) throws IOException; +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/DeckmanAdapterFactory.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/DeckmanAdapterFactory.java new file mode 100755 index 00000000000..036aeb1c74e --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/DeckmanAdapterFactory.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.deckmanadapter; + +import com.sap.sailing.domain.deckmanadapter.impl.DeckmanAdapterFactoryImpl; + +public interface DeckmanAdapterFactory { + DeckmanAdapterFactory INSTANCE = new DeckmanAdapterFactoryImpl(); + + DeckmanAdapter createDeckmanAdapter(); +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/LogFile.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/LogFile.java new file mode 100755 index 00000000000..83892d1ac60 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/LogFile.java @@ -0,0 +1,6 @@ +package com.sap.sailing.domain.deckmanadapter; + +import java.util.Iterator; + +public interface LogFile extends Iterator { +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/Record.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/Record.java new file mode 100755 index 00000000000..52192bf8272 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/Record.java @@ -0,0 +1,78 @@ +package com.sap.sailing.domain.deckmanadapter; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Map; + +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.TimePoint; +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.MillisecondsTimePoint; +import com.sap.sailing.domain.deckmanadapter.impl.FieldType; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.Positioned; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.tracking.impl.WindImpl; + +public class Record implements Timed, Positioned { + private static final long serialVersionUID = -7939775022865795801L; + + private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + + private final TimePoint timePoint; + + private final Position position; + + private final GPSFixMoving gpsFix; + + private final Wind wind; + + private Map fieldsAsString; + + public Record(Map fieldsAsString) throws ParseException { + this.fieldsAsString = fieldsAsString; + timePoint = new MillisecondsTimePoint(dateFormat.parse(this.fieldsAsString.get(FieldType.date))); + position = new DegreePosition(Double.valueOf(this.fieldsAsString.get(FieldType.latitude)), + Double.valueOf(this.fieldsAsString.get(FieldType.longitude))); + final SpeedWithBearing speed = new KnotSpeedWithBearingImpl(Double.valueOf(this.fieldsAsString.get(FieldType.sog)), + new DegreeBearingImpl(Double.valueOf(this.fieldsAsString.get(FieldType.cog)))); + wind = new WindImpl(position, timePoint, new KnotSpeedWithBearingImpl(Double.valueOf(this.fieldsAsString + .get(FieldType.tws)), new DegreeBearingImpl(Double.valueOf(this.fieldsAsString.get(FieldType.twd))))); + gpsFix = new GPSFixMovingImpl(position, timePoint, speed); + } + + public String getField(FieldType fieldType) { + return fieldsAsString.get(fieldType); + } + + public Map getFieldsAsString() { + return fieldsAsString; + } + + public void setFieldsAsString(Map fieldsAsString) { + this.fieldsAsString = fieldsAsString; + } + + public TimePoint getTimePoint() { + return timePoint; + } + + public GPSFixMoving getGpsFix() { + return gpsFix; + } + + public Wind getWind() { + return wind; + } + + @Override + public Position getPosition() { + return position; + } +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/Activator.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/Activator.java new file mode 100755 index 00000000000..b32cb3c4670 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/Activator.java @@ -0,0 +1,19 @@ +package com.sap.sailing.domain.deckmanadapter.impl; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +import com.sap.sailing.server.trackfiles.common.GPSFixImporterRegistration; + +public class Activator implements BundleActivator { + + @Override + public void start(BundleContext context) throws Exception { + GPSFixImporterRegistration.register(new DeckmanGPSFixImporter(), context); + } + + @Override + public void stop(BundleContext context) throws Exception { + } + +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/DeckmanAdapterFactoryImpl.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/DeckmanAdapterFactoryImpl.java new file mode 100755 index 00000000000..52481f6aa1c --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/DeckmanAdapterFactoryImpl.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.deckmanadapter.impl; + +import com.sap.sailing.domain.deckmanadapter.DeckmanAdapter; +import com.sap.sailing.domain.deckmanadapter.DeckmanAdapterFactory; + +public class DeckmanAdapterFactoryImpl implements DeckmanAdapterFactory { + @Override + public DeckmanAdapter createDeckmanAdapter() { + return new DeckmanAdapterImpl(); + } +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/DeckmanAdapterImpl.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/DeckmanAdapterImpl.java new file mode 100755 index 00000000000..6db2e1b6a55 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/DeckmanAdapterImpl.java @@ -0,0 +1,15 @@ +package com.sap.sailing.domain.deckmanadapter.impl; + +import java.io.IOException; +import java.io.Reader; + +import com.sap.sailing.domain.deckmanadapter.DeckmanAdapter; +import com.sap.sailing.domain.deckmanadapter.LogFile; + +public class DeckmanAdapterImpl implements DeckmanAdapter { + @Override + public LogFile parseLogFile(Reader r) throws IOException { + return new LogFileImpl(r); + } + +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/DeckmanGPSFixImporter.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/DeckmanGPSFixImporter.java new file mode 100755 index 00000000000..24efb9d116b --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/DeckmanGPSFixImporter.java @@ -0,0 +1,54 @@ +package com.sap.sailing.domain.deckmanadapter.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; + +import com.sap.sailing.domain.deckmanadapter.DeckmanAdapter; +import com.sap.sailing.domain.deckmanadapter.Record; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; +import com.sap.sailing.domain.trackimport.FormatNotSupportedException; +import com.sap.sailing.domain.trackimport.GPSFixImporter; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; + +public class DeckmanGPSFixImporter implements GPSFixImporter { + + @Override + public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, String sourceName) + throws FormatNotSupportedException, IOException { + DeckmanAdapter deckmanAdapter = DeckmanAdapterFactoryImpl.INSTANCE.createDeckmanAdapter(); + TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(sourceName, getType() + "@" + new Date()); + try { + for (Iterator i = deckmanAdapter.parseLogFile(new InputStreamReader(inputStream)); i.hasNext();) { + Record record = i.next(); + callback.addFix(new GPSFixMovingImpl(record.getPosition(), record.getTimePoint(), record.getGpsFix().getSpeed()), + device); + } + } catch (RuntimeException e) { + if (e.getCause() != null) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + throw new FormatNotSupportedException("Error reading Deckman CSV file", e.getCause()); + } + } else { + throw new FormatNotSupportedException("Error reading Deckman CSV file", e); + } + } + } + + @Override + public Iterable getSupportedFileExtensions() { + return Arrays.asList(new String[] { "csv" }); + } + + @Override + public String getType() { + return "Deckman"; + } + +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/FieldType.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/FieldType.java new file mode 100755 index 00000000000..23494492437 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/FieldType.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.deckmanadapter.impl; + +public enum FieldType { + date, race_name, leg_name, leg_score, boat_speed, heel, leeway, rudder, heading, course, depth, + tws, twa, twd, aws, awa, latitude, longitude, sog, cog, abstwa, absawa, absheel, absleeway, absrudder, + foresail, staysail, mainsail, forestay, rake, vmg, status, starboard_port, rudder2, keelang, absrudder2, abskeelang; +} diff --git a/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/LogFileImpl.java b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/LogFileImpl.java new file mode 100755 index 00000000000..f581411e892 --- /dev/null +++ b/java/com.sap.sailing.domain.deckmanadapter/src/com/sap/sailing/domain/deckmanadapter/impl/LogFileImpl.java @@ -0,0 +1,66 @@ +package com.sap.sailing.domain.deckmanadapter.impl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; + +import com.sap.sailing.domain.deckmanadapter.LogFile; +import com.sap.sailing.domain.deckmanadapter.Record; + +/** + * A log file as produced by Deckman, in CSV format. The first line is expected to contain the column headers. + * Column headers not found in {@link FieldType} are ignored with a warning being logged. + * + * @author Axel Uhl (D043530) + * + */ +public class LogFileImpl implements LogFile { + private final FieldType[] fieldTypes; + private final BufferedReader bufferedReader; + private String nextLine; + + /** + * Reads the header from the reader to initialize {@link #fieldTypes}. Afterwards, using the {@link #iterator}, + * the records can be read one by one. + */ + public LogFileImpl(Reader r) throws IOException { + bufferedReader = new BufferedReader(r); + String header = bufferedReader.readLine(); + String[] fieldNames = header.split(","); + fieldTypes = new FieldType[fieldNames.length]; + for (int i=0; i fieldsAsStrings = new HashMap<>(); + final String[] fieldValues = nextLine.split(","); + try { + nextLine = bufferedReader.readLine(); + for (int i = 0; i < fieldValues.length; i++) { + fieldsAsStrings.put(fieldTypes[i], fieldValues[i]); + } + final Record result = new Record(fieldsAsStrings); + return result; + } catch (ParseException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove() is not supported on a Deckman log file"); + } + +} diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.igtimiadapter.test/META-INF/MANIFEST.MF index 6e8f31e5226..b2234b849b6 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.igtimiadapter.test/META-INF/MANIFEST.MF @@ -8,4 +8,5 @@ Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Require-Bundle: org.junit4;bundle-version="4.8.2", com.sap.sailing.domain.common, - com.sap.sailing.mongodb + com.sap.sailing.mongodb, + com.sap.sse.common diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactoryTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactoryTest.java index 1e2680d5498..bcac9cd455e 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactoryTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactoryTest.java @@ -12,11 +12,11 @@ import org.junit.Before; import org.junit.Test; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.datatypes.AntHrm; import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong; import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualitySatCount; +import com.sap.sse.common.Util; public class FixFactoryTest { private static final String demoJson = "{\n"+ diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WebSocketServerListTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WebSocketServerListTest.java index a24e0ae2782..d4402812e86 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WebSocketServerListTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WebSocketServerListTest.java @@ -10,10 +10,10 @@ import org.apache.http.client.ClientProtocolException; import org.json.simple.parser.ParseException; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory; import com.sap.sailing.mongodb.MongoDBConfiguration; import com.sap.sailing.mongodb.MongoDBService; +import com.sap.sse.common.Util; public class WebSocketServerListTest { @Test diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/persistence/IgtimiPersistenceTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/persistence/IgtimiPersistenceTest.java index 9f3028f3b87..a5879cdfd94 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/persistence/IgtimiPersistenceTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/persistence/IgtimiPersistenceTest.java @@ -6,8 +6,8 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.mongodb.MongoDBConfiguration; +import com.sap.sse.common.Util; public class IgtimiPersistenceTest { private MongoObjectFactory mongoObjectFactory; diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/BasicIgtimiAdapterTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/BasicIgtimiAdapterTest.java index 9145260be98..5d32adaf664 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/BasicIgtimiAdapterTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/BasicIgtimiAdapterTest.java @@ -21,7 +21,6 @@ import org.junit.Test; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.DataAccessWindow; import com.sap.sailing.domain.igtimiadapter.Group; import com.sap.sailing.domain.igtimiadapter.Permission; @@ -32,6 +31,7 @@ import com.sap.sailing.domain.igtimiadapter.datatypes.AWA; import com.sap.sailing.domain.igtimiadapter.datatypes.AWS; import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; import com.sap.sailing.domain.igtimiadapter.datatypes.Type; +import com.sap.sse.common.Util; public class BasicIgtimiAdapterTest extends AbstractTestWithIgtimiConnection { @Test diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/IgtimiFixTrackTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/IgtimiFixTrackTest.java index 7a7de2cdddf..d1853169bd9 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/IgtimiFixTrackTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/IgtimiFixTrackTest.java @@ -17,7 +17,6 @@ import org.junit.Test; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.DataAccessWindow; import com.sap.sailing.domain.igtimiadapter.Permission; import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; @@ -26,6 +25,7 @@ import com.sap.sailing.domain.tracking.DynamicTrack; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.Wind; +import com.sap.sse.common.Util; /** * Igtimi fixes come as isolated single fixes from separate sensors, and even if they are attached to the same device, diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/LatestDatumTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/LatestDatumTest.java index 2935082cc59..7db846f2106 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/LatestDatumTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/LatestDatumTest.java @@ -9,10 +9,10 @@ import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; import com.sap.sailing.domain.igtimiadapter.datatypes.Type; import com.sap.sailing.domain.igtimiadapter.impl.FixFactory; +import com.sap.sse.common.Util; public class LatestDatumTest { diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketTest.java index 39f2072e8fb..e981bee655d 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketTest.java @@ -30,7 +30,6 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.Account; import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver; import com.sap.sailing.domain.igtimiadapter.Client; @@ -43,6 +42,7 @@ import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl; import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory; import com.sap.sailing.mongodb.MongoDBConfiguration; import com.sap.sailing.mongodb.MongoDBService; +import com.sap.sse.common.Util; public class WebSocketTest { private static final Logger logger = Logger.getLogger(WebSocketTest.class.getName()); 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 8852c2829b7..9ac2f5a7cac 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF @@ -15,7 +15,8 @@ Require-Bundle: org.json.simple;bundle-version="1.1.0", com.sap.sailing.domain.igtimiadapter.persistence, com.sap.sailing.domain.shared.android, com.sap.sailing.domain, - com.sap.sailing.declination + com.sap.sailing.declination, + com.sap.sse.common 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", diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java index b2eafbd95a5..9309f0cc486 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java @@ -15,10 +15,10 @@ import org.json.simple.JSONObject; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.Sensor; import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; import com.sap.sailing.domain.igtimiadapter.datatypes.Type; +import com.sap.sse.common.Util; public class FixFactory { private static final Logger logger = Logger.getLogger(FixFactory.class.getName()); diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionImpl.java index 9389b4c4366..4b374a6f3f7 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionImpl.java @@ -20,7 +20,6 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.Account; import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver; import com.sap.sailing.domain.igtimiadapter.DataAccessWindow; @@ -41,6 +40,7 @@ import com.sap.sailing.domain.tracking.DynamicTrack; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.impl.DynamicTrackImpl; +import com.sap.sse.common.Util; public class IgtimiConnectionImpl implements IgtimiConnection { private static final Logger logger = Logger.getLogger(IgtimiConnectionImpl.class.getName()); diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiWindTracker.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiWindTracker.java index dd66828da22..fadf81628c1 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiWindTracker.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiWindTracker.java @@ -11,7 +11,6 @@ import java.util.logging.Logger; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.igtimiadapter.Account; import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; @@ -20,12 +19,13 @@ import com.sap.sailing.domain.igtimiadapter.shared.IgtimiWindReceiver; import com.sap.sailing.domain.tracking.AbstractWindTracker; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.WindTracker; +import com.sap.sse.common.Util; public class IgtimiWindTracker extends AbstractWindTracker implements WindTracker { private static final Logger logger = Logger.getLogger(IgtimiWindTracker.class.getName()); private static final int TIME_INTERVAL_TO_TRACK_BEFORE_RACE_START_MILLIS = 10*60*1000; // 10 minutes private static final long TIME_INTERVAL_TO_TRACK_AFTER_END_OF_RACE_MILLIS = 60*60*1000; // 60 minutes - private final Map, Account>> liveConnectionsAndDeviceSerialNumber; + private final Map, Account>> liveConnectionsAndDeviceSerialNumber; private final IgtimiWindTrackerFactory windTrackerFactory; private boolean stopping; @@ -50,7 +50,7 @@ public class IgtimiWindTracker extends AbstractWindTracker implements WindTracke IgtimiWindReceiver windReceiver = new IgtimiWindReceiver(devicesWeShouldListenTo); liveConnection.addListener(windReceiver); windReceiver.addListener(new WindListenerSendingToTrackedRace(Collections.singleton(getTrackedRace()), windTrackerFactory)); - liveConnectionsAndDeviceSerialNumber.put(liveConnection, new Pair, Account>(devicesWeShouldListenTo, account)); + liveConnectionsAndDeviceSerialNumber.put(liveConnection, new Util.Pair, Account>(devicesWeShouldListenTo, account)); } } } catch (Exception e) { @@ -114,7 +114,7 @@ public class IgtimiWindTracker extends AbstractWindTracker implements WindTracke logger.info("Stopping Igtimi live connection "+ldc); ldc.stop(); } catch (Exception e) { - final Pair, Account> deviceSerialNumberAndAccount = liveConnectionsAndDeviceSerialNumber.get(ldc); + final Util.Pair, Account> deviceSerialNumberAndAccount = liveConnectionsAndDeviceSerialNumber.get(ldc); logger.log(Level.INFO, "Exception trying to stop Igtimi live connection for wind receiver for race " + getTrackedRace().getRace() + " and device " + deviceSerialNumberAndAccount.getA() diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java index 0ef67dd0e93..7942e074189 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java @@ -13,18 +13,16 @@ import java.util.logging.Logger; import com.sap.sailing.declination.Declination; import com.sap.sailing.declination.DeclinationService; import com.sap.sailing.domain.base.Timed; -import com.sap.sailing.domain.base.impl.ScalableBearing; -import com.sap.sailing.domain.base.impl.ScalablePosition; -import com.sap.sailing.domain.base.impl.ScalableSpeed; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalableSpeed; import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver; import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiverAdapter; @@ -45,6 +43,7 @@ import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindListener; import com.sap.sailing.domain.tracking.impl.DynamicTrackImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sse.common.Util; /** * Receives Igtimi {@link Fix}es and tries to generate a {@link Wind} object from each {@link AWS} fix. For this to @@ -180,14 +179,14 @@ public class IgtimiWindReceiver implements BulkFixReceiver { private Wind getWind(final TimePoint timePoint, String deviceSerialNumber) throws ClassNotFoundException, IOException, ParseException { final Wind result; - Pair awaPair = getSurroundingFixes(getAwaTrack(deviceSerialNumber), timePoint); + com.sap.sse.common.Util.Pair awaPair = getSurroundingFixes(getAwaTrack(deviceSerialNumber), timePoint); Bearing awa = getAWA(timePoint, awaPair); - Pair awsPair = getSurroundingFixes(getAwsTrack(deviceSerialNumber), timePoint); + com.sap.sse.common.Util.Pair awsPair = getSurroundingFixes(getAwsTrack(deviceSerialNumber), timePoint); Speed aws = getAWS(timePoint, awsPair); - Pair gpsPair = getSurroundingFixes(getGpsTrack(deviceSerialNumber), timePoint); + com.sap.sse.common.Util.Pair gpsPair = getSurroundingFixes(getGpsTrack(deviceSerialNumber), timePoint); Position pos = getPosition(timePoint, gpsPair); - Pair hdgPair = getSurroundingFixes(getHdgTrack(deviceSerialNumber), timePoint); - Pair hdgmPair = getSurroundingFixes(getHdgmTrack(deviceSerialNumber), timePoint); + com.sap.sse.common.Util.Pair hdgPair = getSurroundingFixes(getHdgTrack(deviceSerialNumber), timePoint); + com.sap.sse.common.Util.Pair hdgmPair = getSurroundingFixes(getHdgmTrack(deviceSerialNumber), timePoint); Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos); if (awa != null && aws != null && pos != null && heading != null) { Bearing apparentWindDirection = heading.add(awa); @@ -215,9 +214,9 @@ public class IgtimiWindReceiver implements BulkFixReceiver { * So again, my personal preference would be to work with the data that should be the most accurate * (COG/SOG) and consider algorithms that handle smoothing of that data best." */ - Pair sogPair = getSurroundingFixes(getSogTrack(deviceSerialNumber), timePoint); + com.sap.sse.common.Util.Pair sogPair = getSurroundingFixes(getSogTrack(deviceSerialNumber), timePoint); Speed sog = getSOG(timePoint, sogPair); - Pair cogPair = getSurroundingFixes(getCogTrack(deviceSerialNumber), timePoint); + com.sap.sse.common.Util.Pair cogPair = getSurroundingFixes(getCogTrack(deviceSerialNumber), timePoint); Bearing cog = getCOG(timePoint, cogPair); SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog); SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog); @@ -228,7 +227,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { return result; } - private Bearing getAWA(TimePoint timePoint, Pair awaPair) { + private Bearing getAWA(TimePoint timePoint, com.sap.sse.common.Util.Pair awaPair) { final Bearing awaFrom; if (awaPair.getA() == null) { if (awaPair.getB() == null) { @@ -248,7 +247,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { return awaFrom == null ? null : awaFrom.reverse(); } - private Speed getAWS(TimePoint timePoint, Pair awsPair) { + private Speed getAWS(TimePoint timePoint, com.sap.sse.common.Util.Pair awsPair) { final Speed aws; if (awsPair.getA() == null) { if (awsPair.getB() == null) { @@ -268,7 +267,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { return aws; } - private Speed getSOG(TimePoint timePoint, Pair sogPair) { + private Speed getSOG(TimePoint timePoint, com.sap.sse.common.Util.Pair sogPair) { final Speed sog; if (sogPair.getA() == null) { if (sogPair.getB() == null) { @@ -288,7 +287,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { return sog; } - private Bearing getCOG(TimePoint timePoint, Pair cogPair) { + private Bearing getCOG(TimePoint timePoint, com.sap.sse.common.Util.Pair cogPair) { final Bearing sog; if (cogPair.getA() == null) { if (cogPair.getB() == null) { @@ -308,7 +307,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { return sog; } - private Position getPosition(TimePoint timePoint, Pair gpsPair) { + private Position getPosition(TimePoint timePoint, com.sap.sse.common.Util.Pair gpsPair) { final Position pos; if (gpsPair.getA() == null) { if (gpsPair.getB() == null) { @@ -328,7 +327,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { return pos; } - private Bearing getHDG(TimePoint timePoint, Pair hdgPair) { + private Bearing getHDG(TimePoint timePoint, com.sap.sse.common.Util.Pair hdgPair) { final Bearing hdg; if (hdgPair.getA() == null) { if (hdgPair.getB() == null) { @@ -348,7 +347,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { return hdg; } - private Bearing getHDGM(TimePoint timePoint, Pair hdgmPair) { + private Bearing getHDGM(TimePoint timePoint, com.sap.sse.common.Util.Pair hdgmPair) { final Bearing hdgm; if (hdgmPair.getA() == null) { if (hdgmPair.getB() == null) { @@ -368,7 +367,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver { return hdgm; } - private Bearing getHeading(TimePoint timePoint, Pair hdgPair, Pair hdgmPair, Position position) throws ClassNotFoundException, IOException, ParseException { + private Bearing getHeading(TimePoint timePoint, com.sap.sse.common.Util.Pair hdgPair, com.sap.sse.common.Util.Pair hdgmPair, Position position) throws ClassNotFoundException, IOException, ParseException { final Bearing trueHeading; Bearing hdg = getHDG(timePoint, hdgPair); if (hdg != null) { @@ -397,10 +396,10 @@ public class IgtimiWindReceiver implements BulkFixReceiver { return acc; } - private Pair getSurroundingFixes(Track track, TimePoint timePoint) { + private com.sap.sse.common.Util.Pair getSurroundingFixes(Track track, TimePoint timePoint) { T left = track.getLastFixAtOrBefore(timePoint); T right = track.getFirstFixAtOrAfter(timePoint); - Pair result = new Pair<>(left, right); + com.sap.sse.common.Util.Pair result = new com.sap.sse.common.Util.Pair<>(left, right); return result; } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/LiveDataConnectionFactoryImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/LiveDataConnectionFactoryImpl.java index ad8baef432b..3916995de57 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/LiveDataConnectionFactoryImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/LiveDataConnectionFactoryImpl.java @@ -6,10 +6,10 @@ import java.util.Map; import java.util.Set; import java.util.logging.Logger; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.Account; import com.sap.sailing.domain.igtimiadapter.LiveDataConnection; import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl; +import com.sap.sse.common.Util; public class LiveDataConnectionFactoryImpl implements LiveDataConnectionFactory { private static final Logger logger = Logger.getLogger(LiveDataConnectionFactoryImpl.class.getName()); 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 8b9aecbaa12..945d5ac68d2 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 @@ -21,14 +21,13 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.igtimiadapter.Account; import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver; import com.sap.sailing.domain.igtimiadapter.LiveDataConnection; import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; import com.sap.sailing.domain.igtimiadapter.impl.FixFactory; import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl; +import com.sap.sse.common.Util; public class WebSocketConnectionManager extends WebSocketAdapter implements LiveDataConnection { private static final Logger logger = Logger.getLogger(WebSocketConnectionManager.class.getName()); @@ -144,8 +143,8 @@ public class WebSocketConnectionManager extends WebSocketAdapter implements Live } } - public Pair getIgtimiServerTimePointAndWhenItWasReceived() { - return new Pair(igtimiServerTimepoint, localTimepointWhenServerTimepointWasReceived); + public com.sap.sse.common.Util.Pair getIgtimiServerTimePointAndWhenItWasReceived() { + return new com.sap.sse.common.Util.Pair(igtimiServerTimepoint, localTimepointWhenServerTimepointWasReceived); } @Override diff --git a/java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF index a6b892b6d84..f5877dcf5ff 100755 --- a/java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF @@ -11,10 +11,23 @@ Require-Bundle: com.sap.sailing.mongodb, com.mongodb.driver;bundle-version="2.6.2", com.sap.sailing.domain.common, com.sap.sailing.domain.shared.android, - com.sap.sailing.server.gateway.serialization;bundle-version="1.0.0" + com.sap.sailing.server.gateway.serialization;bundle-version="1.0.0", + com.sap.sse.common Export-Package: com.sap.sailing.domain.persistence, - com.sap.sailing.domain.persistence.impl;x-friends:="com.sap.sailing.mongodb.test,com.sap.sailing.domain.swisstimingadapter.test,com.sap.sailing.domain.persistence.test", - com.sap.sailing.domain.persistence.media + com.sap.sailing.domain.persistence.impl; + x-friends:="com.sap.sailing.domain.persistence.test, + com.sap.sailing.domain.swisstimingadapter.test, + com.sap.sailing.mongodb.test, + com.sap.sailing.server.gateway.test, + com.sap.sailing.domain.racelogtrackingadapter.testsupport, + com.sap.sailing.domain.racelogtrackingadapter.test", + com.sap.sailing.domain.persistence.media, + com.sap.sailing.domain.persistence.racelog.tracking, + com.sap.sailing.domain.persistence.racelog.tracking.impl; + x-friends:="com.sap.sailing.domain.racelogtrackingadapter.testsupport, + com.sap.sailing.mongodb.test, + com.sap.sailing.domain.racelogtrackingadapter.test, + com.sap.sailing.server" Bundle-ActivationPolicy: lazy Import-Package: org.json.simple, org.json.simple.parser, diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java index 721b2a7d4ec..ad5b3f1d655 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java @@ -11,12 +11,15 @@ import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.RegattaRegistry; +import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.base.configuration.DeviceConfiguration; import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.leaderboard.EventResolver; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver; import com.sap.sailing.domain.leaderboard.LeaderboardRegistry; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogIdentifier; @@ -46,6 +49,13 @@ public interface DomainObjectFactory { RaceIdentifier loadRaceIdentifier(DBObject dbObject); /** + * Loads the leaderboard group that has name as its name. + *

+ * + * If the leaderboard group does not yet have a UUID as its {@link LeaderboardGroup#getId() ID}, a new random UUID + * is generated, assigned to the leaderboard group, and the leaderboard group is stored again (incremental + * migration). + * * @param leaderboardRegistry * if not null, then before creating and loading the leaderboard it is looked up in this * registry and only loaded if not found there. If leaderboardRegistry is null, @@ -84,7 +94,18 @@ public interface DomainObjectFactory { Event loadEvent(String name); Iterable loadAllEvents(); + + /** + * The {@link MongoObjectFactory#storeEvent(Event)} method stores events and their links to leaderboard groups. + * Loading the same data has to happen in two slices because there are cyclic references between events and + * leaderboard groups, and the usual loading order, e.g., in RacingEventService, is to first load the + * events, then the leaderboard groups. So the links between them can only be resolved after both types of objects + * have finished loading. This method implements this step of loading and establishing the links. + */ + void loadLeaderboardGroupLinksForEvents(EventResolver eventResolver, LeaderboardGroupResolver leaderboardGroupResolver); + Iterable loadAllRemoteSailingServerReferences(); + Regatta loadRegatta(String name, TrackedRegattaRegistry trackedRegattaRegistry); Iterable loadAllRegattas(TrackedRegattaRegistry trackedRegattaRegistry); diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java index 556171816cd..b30a2603fc4 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java @@ -8,6 +8,7 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.configuration.DeviceConfiguration; import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; @@ -63,9 +64,20 @@ public interface MongoObjectFactory { void renameLeaderboardGroup(String oldName, String newName); /** - * Stores the event with its name, venue and the venue's course areas. The regattas obtained by - * {@link Event#getRegattas()} are not stored by this call. They need to be stored separately - * by calls to {@link #storeRegatta} where a reference to their owning event is stored. + * Stores the event with its name, venue and the venue's course areas, as well as the links to the + * {@link Event#getLeaderboardGroups() leaderboard groups associated with the event}. These links are stored + * separately and are not loaded by the corresponding {@link DomainObjectFactory#loadEvent(String)} call again. The + * rationale behind this is that the events are usually loaded as the first thing in a server. The leaderboard + * groups are not yet loaded at that time, so we usually cannot establish the links at the time the event is loaded. + * Instead, a separate call, {@link DomainObjectFactory#loadLeaderboardGroupLinksForEvents}, is required after both, + * the events and the leaderboard groups have been loaded, to establish the links again. + *

+ * + * The regattas obtained by {@link Event#getRegattas()} are not stored by this call. They need to be stored + * separately by calls to {@link #storeRegatta} where a reference to their owning event is stored. + *

+ * + * */ void storeEvent(Event event); @@ -79,6 +91,14 @@ public interface MongoObjectFactory { */ void removeEvent(Serializable id); + /** + * Stores a registered sailing server + * @param serves the servers to store + */ + void storeSailingServer(RemoteSailingServerReference server); + + void removeSailingServer(String name); + /** * Stores the regatta together with its name, {@link Series} definitions and an optional link to the * {@link Event} to which the regatta belongs. @@ -109,7 +129,6 @@ public interface MongoObjectFactory { void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration); void removeDeviceConfiguration(DeviceConfigurationMatcher matcher); - void removeRaceLog(RaceLogIdentifier identifier); } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/PersistenceFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/PersistenceFactory.java index 9c07d6e31cd..c70f7cf4348 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/PersistenceFactory.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/PersistenceFactory.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.persistence; import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; import com.sap.sailing.domain.persistence.impl.PersistenceFactoryImpl; import com.sap.sailing.mongodb.MongoDBService; @@ -9,6 +10,9 @@ public interface PersistenceFactory { DomainObjectFactory getDefaultDomainObjectFactory(); DomainObjectFactory getDomainObjectFactory(MongoDBService mongoDBService, DomainFactory baseDomainFactory); + DomainObjectFactory getDomainObjectFactory(MongoDBService mongoDBService, DomainFactory baseDomainFactory, TypeBasedServiceFinderFactory serviceFinderFactory); MongoObjectFactory getDefaultMongoObjectFactory(); MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDBService); + MongoObjectFactory getDefaultMongoObjectFactory(TypeBasedServiceFinderFactory serviceFinderFactory); + MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDBService, TypeBasedServiceFinderFactory serviceFinderFactory); } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java index f0597d3226f..14452399bfa 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java @@ -6,14 +6,13 @@ import org.osgi.framework.BundleContext; import com.sap.sailing.mongodb.MongoDBService; public class Activator implements BundleActivator { - @Override - public void start(BundleContext context) throws Exception { - for (CollectionNames name : CollectionNames.values()) - MongoDBService.INSTANCE.registerExclusively(CollectionNames.class, name.name()); - } - - @Override - public void stop(BundleContext context) throws Exception { - } + @Override + public void start(BundleContext context) throws Exception { + for (CollectionNames name : CollectionNames.values()) + MongoDBService.INSTANCE.registerExclusively(CollectionNames.class, name.name()); + } + @Override + public void stop(BundleContext context) throws Exception { + } } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java index 32e658d47db..4ab14d7457e 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java @@ -1,5 +1,8 @@ package com.sap.sailing.domain.persistence.impl; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.tracking.GPSFix; + /** * Defines literals providing the names for MongoDB collections. The literal documentation described the semantics * of the collection identified by that literal. @@ -25,11 +28,22 @@ public enum CollectionNames { LEADERBOARD_GROUPS, /** - * Top-level event information about events such as Kiel Week 2011, or IDM Travemünde 2011, including name and + * Top-level event information about events such as Kieler Woche 2011, or IDM Travemuende 2011, including name and * course areas. */ EVENTS, + /** + * The links that connect events to leaderboard groups; each element stores the ID of an event and a list + * of leaderboard group IDs. + */ + LEADERBOARD_GROUP_LINKS_FOR_EVENTS, + + /** + * Stores the registered sailing servers. + */ + SAILING_SERVERS, + /** * Stores regatta definitions including their series layout and fleets and race columns. Regattas can reference * the event from the {@link #EVENTS} collection to which they belong. @@ -62,5 +76,15 @@ public enum CollectionNames { /** * Stores configurations for mobile devices. */ - CONFIGURATIONS; + CONFIGURATIONS, + + /** + * Stores {@link GPSFix}es + */ + GPS_FIXES, + + /** + * Metadata for the GPSFixes, grouped by Device Identifier. + */ + GPS_FIXES_METADATA, } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index 510a433dc19..5a6a131ae7b 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -1,6 +1,8 @@ package com.sap.sailing.domain.persistence.impl; import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -41,6 +43,7 @@ import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.RegattaRegistry; +import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.Venue; import com.sap.sailing.domain.base.Waypoint; @@ -53,6 +56,7 @@ import com.sap.sailing.domain.base.impl.CourseDataImpl; import com.sap.sailing.domain.base.impl.EventImpl; import com.sap.sailing.domain.base.impl.FleetImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.RemoteSailingServerReferenceImpl; import com.sap.sailing.domain.base.impl.SeriesImpl; import com.sap.sailing.domain.base.impl.VenueImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; @@ -67,6 +71,7 @@ import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimeRange; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; @@ -75,18 +80,23 @@ import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.RGBColor; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.common.impl.TimeRangeImpl; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections; import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections.LeaderboardCorrectionsResolvedListener; +import com.sap.sailing.domain.leaderboard.EventResolver; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver; import com.sap.sailing.domain.leaderboard.LeaderboardRegistry; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.domain.leaderboard.ScoringScheme; @@ -100,6 +110,8 @@ import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRul import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; import com.sap.sailing.domain.persistence.DomainObjectFactory; import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler; import com.sap.sailing.domain.racelog.CompetitorResults; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; @@ -120,9 +132,19 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; import com.sap.sailing.domain.racelog.impl.CompetitorResultsImpl; import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; import com.sap.sailing.domain.racelog.impl.RaceLogImpl; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.impl.PlaceHolderDeviceIdentifierSerializationHandler; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindTrack; @@ -134,6 +156,7 @@ import com.sap.sailing.server.gateway.deserialization.impl.CompetitorJsonDeseria import com.sap.sailing.server.gateway.deserialization.impl.DeviceConfigurationJsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.Helpers; import com.sap.sailing.server.gateway.deserialization.impl.RegattaConfigurationJsonDeserializer; +import com.sap.sse.common.Util; public class DomainObjectFactoryImpl implements DomainObjectFactory { private static final Logger logger = Logger.getLogger(DomainObjectFactoryImpl.class.getName()); @@ -143,9 +166,26 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { private RaceLogEventRestoreFactory raceLogEventFactory; private final DomainFactory baseDomainFactory; + private final TypeBasedServiceFinder deviceIdentifierServiceFinder; + private final TypeBasedServiceFinderFactory serviceFinderFactory; + /** + * Uses null as the {@link TypeBasedServiceFinder}, meaning that no {@link DeviceIdentifier}s can be loaded + * using this instance of a {@link DomainObjectFactory}. + */ public DomainObjectFactoryImpl(DB db, DomainFactory baseDomainFactory) { + this(db, baseDomainFactory, /* deviceTypeServiceFinder */ null); + } + + public DomainObjectFactoryImpl(DB db, DomainFactory baseDomainFactory, TypeBasedServiceFinderFactory serviceFinderFactory) { super(); + this.serviceFinderFactory = serviceFinderFactory; + if (serviceFinderFactory != null) { + this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class); + this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler()); + } else { + this.deviceIdentifierServiceFinder = null; + } this.baseDomainFactory = baseDomainFactory; this.competitorDeserializer = CompetitorJsonDeserializer.create(baseDomainFactory); this.database = db; @@ -161,7 +201,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return new WindImpl(loadPosition(object), loadTimePoint(object), loadSpeedWithBearing(object)); } - private Position loadPosition(DBObject object) { + public Position loadPosition(DBObject object) { Number latNumber = (Number) object.get(FieldNames.LAT_DEG.name()); Double lat = latNumber == null ? null : latNumber.doubleValue(); Number lngNumber = (Number) object.get(FieldNames.LNG_DEG.name()); @@ -173,23 +213,36 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } } - private TimePoint loadTimePoint(DBObject object, FieldNames field) { + public static TimePoint loadTimePoint(DBObject object, String fieldName) { TimePoint result = null; - Number timePointAsNumber = (Number) object.get(field.name()); + Number timePointAsNumber = (Number) object.get(fieldName); if (timePointAsNumber != null) { result = new MillisecondsTimePoint(timePointAsNumber.longValue()); } return result; } + public static TimePoint loadTimePoint(DBObject object, FieldNames field) { + return loadTimePoint(object, field.name()); + } + + public static TimeRange loadTimeRange(DBObject object, FieldNames field) { + DBObject timeRangeObj = (DBObject) object.get(field.name()); + if (timeRangeObj == null) { + return null; + } + TimePoint from = loadTimePoint(timeRangeObj, FieldNames.FROM_MILLIS); + TimePoint to = loadTimePoint(timeRangeObj, FieldNames.TO_MILLIS); + return new TimeRangeImpl(from, to); + } /** * Loads a {@link TimePoint} on the given object at {@link FieldNames#TIME_AS_MILLIS}. */ - private TimePoint loadTimePoint(DBObject object) { + public TimePoint loadTimePoint(DBObject object) { return loadTimePoint(object, FieldNames.TIME_AS_MILLIS); } - private SpeedWithBearing loadSpeedWithBearing(DBObject object) { + public SpeedWithBearing loadSpeedWithBearing(DBObject object) { return new KnotSpeedWithBearingImpl(((Number) object.get(FieldNames.KNOT_SPEED.name())).doubleValue(), new DegreeBearingImpl(((Number) object.get(FieldNames.DEGREE_BEARING.name())).doubleValue())); } @@ -642,7 +695,16 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { Set leaderboardGroups = new HashSet(); try { for (DBObject o : leaderboardGroupCollection.find()) { - leaderboardGroups.add(loadLeaderboardGroup(o, regattaRegistry, leaderboardRegistry)); + boolean hasUUID = o.containsField(FieldNames.LEADERBOARD_GROUP_UUID.name()); + final LeaderboardGroup leaderboardGroup = loadLeaderboardGroup(o, regattaRegistry, leaderboardRegistry); + leaderboardGroups.add(leaderboardGroup); + if (!hasUUID) { + // in an effort to migrate leaderboard groups without ID to such that have a UUID as their ID, we need + // to write a leaderboard group to the database again after it just received a UUID for the first time: + logger.info("Existing LeaderboardGroup " + leaderboardGroup.getName() + + " received a UUID during migration; updating the leaderboard group in the database"); + new MongoObjectFactoryImpl(database).storeLeaderboardGroup(leaderboardGroup); + } } } catch (Exception e) { logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard groups."); @@ -655,6 +717,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { private LeaderboardGroup loadLeaderboardGroup(DBObject o, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); String name = (String) o.get(FieldNames.LEADERBOARD_GROUP_NAME.name()); + UUID uuid = (UUID) o.get(FieldNames.LEADERBOARD_GROUP_UUID.name()); + if (uuid == null) { + uuid = UUID.randomUUID(); + logger.info("Leaderboard group "+name+" receives UUID "+uuid+" in a migration effort"); + // migration: leaderboard groups that don't yet have a UUID receive a random one + } String description = (String) o.get(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name()); boolean displayGroupsInReverseOrder = false; // default value Object displayGroupsInReverseOrderObj = o.get(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name()); @@ -677,7 +745,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } } logger.info("loaded leaderboard group "+name); - LeaderboardGroupImpl result = new LeaderboardGroupImpl(name, description, displayGroupsInReverseOrder, leaderboards); + LeaderboardGroupImpl result = new LeaderboardGroupImpl(uuid, name, description, displayGroupsInReverseOrder, leaderboards); Object overallLeaderboardIdOrName = o.get(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name()); if (overallLeaderboardIdOrName != null) { final DBObject dbOverallLeaderboard; @@ -786,7 +854,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { if (windFixesFoundByName.hasNext()) { List windFixesToMigrate = new ArrayList(); for (DBObject dbWind : windFixesFoundByName) { - Pair wind = loadWindFix(result, dbWind, millisecondsOverWhichToAverageWind); + Util.Pair wind = loadWindFix(result, dbWind, millisecondsOverWhichToAverageWind); // write the wind fix with the new ID-based key and remove the legacy wind fix from the DB windFixesToMigrate.add(new MongoObjectFactoryImpl(database).storeWindTrackEntry(race, regattaName, wind.getB(), wind.getA())); @@ -806,7 +874,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return result; } - private Pair loadWindFix(Map result, DBObject dbWind, long millisecondsOverWhichToAverageWind) { + private Util.Pair loadWindFix(Map result, DBObject dbWind, long millisecondsOverWhichToAverageWind) { Wind wind = loadWind((DBObject) dbWind.get(FieldNames.WIND.name())); WindSourceType windSourceType = WindSourceType.valueOf((String) dbWind.get(FieldNames.WIND_SOURCE_NAME.name())); WindSource windSource; @@ -823,7 +891,30 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { result.put(windSource, track); } track.add(wind); - return new Pair(wind, windSource); + return new Util.Pair(wind, windSource); + } + + @Override + public void loadLeaderboardGroupLinksForEvents(EventResolver eventResolver, + LeaderboardGroupResolver leaderboardGroupResolver) { + DBCollection links = database.getCollection(CollectionNames.LEADERBOARD_GROUP_LINKS_FOR_EVENTS.name()); + for (Object o : links.find()) { + DBObject dbLink = (DBObject) o; + UUID eventId = (UUID) dbLink.get(FieldNames.EVENT_ID.name()); + Event event = eventResolver.getEvent(eventId); + if (event == null) { + logger.info("Found leaderboard group IDs for event with ID "+eventId+" but couldn't find that event."); + } else { + @SuppressWarnings("unchecked") + List leaderboardGroupIDs = (List) dbLink.get(FieldNames.LEADERBOARD_GROUP_UUID.name()); + for (UUID leaderboardGroupID : leaderboardGroupIDs) { + LeaderboardGroup leaderboardGroup = leaderboardGroupResolver.getLeaderboardGroupByID(leaderboardGroupID); + if (leaderboardGroup != null) { + event.addLeaderboardGroup(leaderboardGroup); + } + } + } + } } @Override @@ -852,12 +943,43 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } } catch (Exception e) { logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load events."); - logger.log(Level.SEVERE, "loadEvents", e); + logger.log(Level.SEVERE, "loadAllEvents", e); } return result; } + + private RemoteSailingServerReference loadSailingSever(DBObject serverDBObject) { + RemoteSailingServerReference result = null; + String name = (String) serverDBObject.get(FieldNames.SERVER_NAME.name()); + String urlAsString = (String) serverDBObject.get(FieldNames.SERVER_URL.name()); + try { + URL serverUrl = new URL(urlAsString); + result = new RemoteSailingServerReferenceImpl(name, serverUrl); + } catch (MalformedURLException e) { + logger.log(Level.SEVERE, "Can't load the sailing server with URL " + urlAsString, e); + } + return result; + } + + @Override + public Iterable loadAllRemoteSailingServerReferences() { + ArrayList result = new ArrayList(); + DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name()); + try { + for (DBObject o : serverCollection.find()) { + if (loadSailingSever(o) != null) { + result.add(loadSailingSever(o)); + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load sailing server instances URLs."); + logger.log(Level.SEVERE, "loadAllSailingServers", e); + } + return result; + } + /** * An event doesn't store its regattas; it's the regatta that stores a reference to its event; the regatta * needs to add itself to the event when loaded or instantiated. @@ -870,6 +992,26 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { boolean isPublic = eventDBObject.get(FieldNames.EVENT_IS_PUBLIC.name()) != null ? (Boolean) eventDBObject.get(FieldNames.EVENT_IS_PUBLIC.name()) : false; Venue venue = loadVenue((DBObject) eventDBObject.get(FieldNames.VENUE.name())); Event result = new EventImpl(name, startDate, endDate, venue, isPublic, id); + BasicDBList imageURLs = (BasicDBList) eventDBObject.get(FieldNames.EVENT_IMAGE_URLS.name()); + if (imageURLs != null) { + for (Object imageURL : imageURLs) { + try { + result.addImageURL(new URL((String) imageURL)); + } catch (MalformedURLException e) { + logger.severe("Error parsing image URL "+imageURL+" for event "+name+". Ignoring this image URL."); + } + } + } + BasicDBList videoURLs = (BasicDBList) eventDBObject.get(FieldNames.EVENT_VIDEO_URLS.name()); + if (videoURLs != null) { + for (Object videoURL : videoURLs) { + try { + result.addVideoURL(new URL((String) videoURL)); + } catch (MalformedURLException e) { + logger.severe("Error parsing video URL "+videoURL+" for event "+name+". Ignoring this video URL."); + } + } + } return result; } @@ -927,7 +1069,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { BasicDBList dbSeries = (BasicDBList) dbRegatta.get(FieldNames.REGATTA_SERIES.name()); Iterable series = loadSeries(dbSeries, trackedRegattaRegistry); RaceLogStore raceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore( - new MongoObjectFactoryImpl(database), + new MongoObjectFactoryImpl(database, serviceFinderFactory), this); Serializable courseAreaId = (Serializable) dbRegatta.get(FieldNames.COURSE_AREA_ID.name()); @@ -1090,7 +1232,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { List eventsToMigrate = loadRaceLogEvents(result, query); if (!eventsToMigrate.isEmpty()) { // ... migrate them... - MongoRaceLogStoreVisitor storeVisitor = new MongoRaceLogStoreVisitor(identifier, new MongoObjectFactoryImpl(database)); + MongoRaceLogStoreVisitor storeVisitor = new MongoRaceLogStoreVisitor(identifier, new MongoObjectFactoryImpl(database, serviceFinderFactory)); for (RaceLogEvent event : eventsToMigrate) { event.accept(storeVisitor); } @@ -1165,6 +1307,22 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return loadRaceLogRaceLogProtestStartTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogWindFixEvent.class.getSimpleName())) { return loadRaceLogRaceLogWindFixEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(DeviceCompetitorMappingEvent.class.getSimpleName())) { + return loadRaceLogDeviceCompetitorMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(DeviceMarkMappingEvent.class.getSimpleName())) { + return loadRaceLogDeviceMarkMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(DenoteForTrackingEvent.class.getSimpleName())) { + return loadRaceLogDenoteForTrackingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(StartTrackingEvent.class.getSimpleName())) { + return loadRaceLogStartEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RevokeEvent.class.getSimpleName())) { + return loadRaceLogRevokeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RegisterCompetitorEvent.class.getSimpleName())) { + return loadRaceLogRegisterCompetitorEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(DefineMarkEvent.class.getSimpleName())) { + return loadRaceLogDefineMarkEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(CloseOpenEndedDeviceMappingEvent.class.getSimpleName())) { + return loadRaceLogCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } throw new IllegalStateException(String.format("Unknown RaceLogEvent type %s", eventClass)); @@ -1176,6 +1334,80 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return raceLogEventFactory.createWindFixEvent(createdAt, author, logicalTimePoint, id, competitors, passId, wind); } + private RaceLogEvent loadRaceLogDeviceCompetitorMappingEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + DeviceIdentifier device = null; + try { + device = loadDeviceId(deviceIdentifierServiceFinder, + (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); + e.printStackTrace(); + } + Competitor mappedTo = baseDomainFactory.getExistingCompetitorById( + (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name())); + TimePoint from = loadTimePoint(dbObject, FieldNames.RACE_LOG_FROM); + TimePoint to = loadTimePoint(dbObject, FieldNames.RACE_LOG_TO); + return raceLogEventFactory.createDeviceCompetitorMappingEvent(createdAt, author, logicalTimePoint, id, device, mappedTo, passId, from, to); + } + + private RaceLogEvent loadRaceLogDeviceMarkMappingEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + DeviceIdentifier device = null; + try { + device = loadDeviceId(deviceIdentifierServiceFinder, + (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); + e.printStackTrace(); + } + Mark mappedTo = baseDomainFactory.getOrCreateMark( + (Serializable) dbObject.get(FieldNames.MARK_ID.name()), null); + TimePoint from = loadTimePoint(dbObject, FieldNames.RACE_LOG_FROM); + TimePoint to = loadTimePoint(dbObject, FieldNames.RACE_LOG_TO); + return raceLogEventFactory.createDeviceMarkMappingEvent(createdAt, author, logicalTimePoint, id, device, mappedTo, passId, from, to); + } + + private RaceLogEvent loadRaceLogDenoteForTrackingEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + String raceName = (String) dbObject.get(FieldNames.RACE_NAME.name()); + BoatClass boatClass = baseDomainFactory.getOrCreateBoatClass((String) dbObject.get(FieldNames.BOAT_CLASS_NAME.name())); + Serializable raceId = (Serializable) dbObject.get(FieldNames.RACE_ID.name()); + return raceLogEventFactory.createDenoteForTrackingEvent(createdAt, author, logicalTimePoint, id, passId, raceName, boatClass, raceId); + } + + private RaceLogEvent loadRaceLogStartEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + return raceLogEventFactory.createStartTrackingEvent(createdAt, author, logicalTimePoint, id, passId); + } + + private RaceLogEvent loadRaceLogRevokeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Serializable revokedEventId = Helpers.tryUuidConversion( + (Serializable) dbObject.get(FieldNames.RACE_LOG_REVOKED_EVENT_ID.name())); + return raceLogEventFactory.createRevokeEvent(createdAt, author, logicalTimePoint, id, passId, revokedEventId); + } + + private RaceLogEvent loadRaceLogRegisterCompetitorEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + assert competitors.size() == 1 : "Should have exactly one competitor for RegisterCompetitorEvent"; + return raceLogEventFactory.createRegisterCompetitorEvent(createdAt, author, logicalTimePoint, id, passId, competitors.get(0)); + } + + private RaceLogEvent loadRaceLogDefineMarkEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Mark mark = loadMark((DBObject) dbObject.get(FieldNames.RACE_LOG_MARK.name())); + return raceLogEventFactory.createDefineMarkEvent(createdAt, author, logicalTimePoint, id, passId, mark); + } + + private RaceLogEvent loadRaceLogCloseOpenEndedDeviceMappingEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Serializable deviceMappingEventId = Helpers.tryUuidConversion((Serializable) dbObject.get(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name())); + TimePoint closingTimePoint = loadTimePoint(dbObject, FieldNames.RACE_LOG_CLOSING_TIMEPOINT); + return raceLogEventFactory.createCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, passId, + deviceMappingEventId, closingTimePoint); + } + private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { TimePoint protestStartTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_PROTEST_START_TIME); @@ -1262,7 +1494,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { MaxPointsReason maxPointsReason = MaxPointsReason.valueOf((String) dbObject.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name())); - Triple positionedCompetitor = new Triple(competitorId, competitorName, maxPointsReason); + Util.Triple positionedCompetitor = new Util.Triple(competitorId, competitorName, maxPointsReason); positionedCompetitors.add(positionedCompetitor); } return positionedCompetitors; @@ -1440,7 +1672,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { try { for (DBObject dbObject : configurationCollection.find()) { - Pair entry = loadConfigurationEntry(dbObject); + Util.Pair entry = loadConfigurationEntry(dbObject); result.put(entry.getA(), entry.getB()); } } catch (Exception e) { @@ -1451,10 +1683,10 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return result.entrySet(); } - private Pair loadConfigurationEntry(DBObject dbObject) { + private Util.Pair loadConfigurationEntry(DBObject dbObject) { DBObject matcherObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_MATCHER.name()); DBObject configObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_CONFIG.name()); - return new Pair(loadConfigurationMatcher(matcherObject), + return new Util.Pair(loadConfigurationMatcher(matcherObject), loadConfiguration(configObject)); } @@ -1484,4 +1716,19 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } return configuration; } + + private DeviceIdentifier loadDeviceId( + TypeBasedServiceFinder deviceIdentifierServiceFinder, DBObject deviceId) + throws TransformationException, NoCorrespondingServiceRegisteredException { + String deviceType = (String) deviceId.get(FieldNames.DEVICE_TYPE.name()); + Object deviceTypeId = deviceId.get(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name()); + String stringRepresentation = (String) deviceId.get(FieldNames.DEVICE_STRING_REPRESENTATION.name()); + + try { + return deviceIdentifierServiceFinder.findService(deviceType).deserialize(deviceTypeId, deviceType, stringRepresentation); + } catch (TransformationException e) { + return new PlaceHolderDeviceIdentifierSerializationHandler().deserialize( + stringRepresentation, deviceType, stringRepresentation); + } + } } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java index 457a7c3bf07..f3d133e6b70 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java @@ -14,6 +14,8 @@ public enum FieldNames { EVENT_IS_PUBLIC, EVENT_START_DATE, EVENT_END_DATE, + EVENT_IMAGE_URLS, + EVENT_VIDEO_URLS, REGATTA_NAME, REGATTA_ID, @@ -39,7 +41,7 @@ public enum FieldNames { VENUE, VENUE_NAME, - + COURSE_AREAS, COURSE_AREA_NAME, COURSE_AREA_ID, @@ -64,7 +66,7 @@ public enum FieldNames { LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_COLUMN_FACTORS, // leaderboard group - LEADERBOARD_GROUP_NAME, LEADERBOARD_GROUP_DESCRIPTION, LEADERBOARD_GROUP_LEADERBOARDS, LEADERBOARD_GROUP_OVERALL_LEADERBOARD, + LEADERBOARD_GROUP_UUID, LEADERBOARD_GROUP_NAME, LEADERBOARD_GROUP_DESCRIPTION, LEADERBOARD_GROUP_LEADERBOARDS, LEADERBOARD_GROUP_OVERALL_LEADERBOARD, LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER, // race ID to regatta name @@ -77,16 +79,29 @@ public enum FieldNames { WAYPOINT_PASSINGINSTRUCTIONS, CONTROLPOINT, CONTROLPOINT_CLASS, MARK_ID, MARK_COLOR, MARK_NAME, MARK_PATTERN, MARK_SHAPE, MARK_TYPE, CONTROLPOINT_VALUE, CONTROLPOINTWITHTWOMARKS_ID, CONTROLPOINTWITHTWOMARKS_NAME, CONTROLPOINTWITHTWOMARKS_LEFT, CONTROLPOINTWITHTWOMARKS_RIGHT, RACE_LOG_POSITIONED_COMPETITORS, RACE_LOG_PATHFINDER_ID, RACE_LOG_GATE_LINE_OPENING_TIME, RACE_LOG_GOLF_DOWN_TIME, RACE_LOG_START_PROCEDURE_TYPE, - RACE_LOG_PROTEST_START_TIME, RACE_LOG_COURSE_DESIGN_NAME, + RACE_LOG_PROTEST_START_TIME, RACE_LOG_COURSE_DESIGN_NAME, RACE_LOG_REVOKED_EVENT_ID, RACE_LOG_FROM, RACE_LOG_TO, RACE_LOG_MARK, RACE_LOG_DEVICE_MAPPING_EVENT_ID, + RACE_LOG_CLOSING_TIMEPOINT, /** @deprecated Replaced by WAYPOINT_PASSINGINSTRUCTIONS, still used to migrate old races*/ @Deprecated WAYPOINT_PASSINGSIDE, /** @deprecated Replaced by CONTROLPOINTWITHTWOMARKS_ID, still used to migrate old races*/ @Deprecated GATE_ID, /** @deprecated Replaced by CONTROLPOINTWITHTWOMARKS_NAME, still used to migrate old races*/ @Deprecated GATE_NAME, /** @deprecated Replaced by CONTROLPOINTWITHTWOMARKS_LEFT, still used to migrate old races*/ @Deprecated GATE_LEFT, /** @deprecated Replaced by CONTROLPOINTWITHTWOMARKS_RIGHT, still used to migrate old races*/ @Deprecated GATE_RIGHT, - + + // sailing server + SERVER_NAME, + SERVER_URL, // configurations CONFIGURATION_MATCHER, CONFIGURATION_CONFIG, CONFIGURATION_MATCHER_ID, CONFIGURATION_MATCHER_TYPE, CONFIGURATION_MATCHER_CLIENTS, CONFIGURATION_CONFIG_ALLOWED_COURSE_AREA_NAMES, CONFIGURATION_CONFIG_MIN_ROUNDS, CONFIGURATION_CONFIG_MAX_ROUNDS, CONFIGURATION_CONFIG_RESULTS_MAIL, - CONFIGURATION_CONFIG_PROCEDURES; + CONFIGURATION_CONFIG_PROCEDURES, + + // GPSFixes + DEVICE_TYPE_SPECIFIC_ID, DEVICE_ID, DEVICE_TYPE, GPSFIX_TYPE, GPSFIX, DEVICE_STRING_REPRESENTATION, + + // GPSFixes metadata + TIMERANGE, NUM_FIXES, + + // Timespan + FROM_MILLIS, TO_MILLIS, } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index c43f0a97082..7721bd2e175 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.persistence.impl; import java.io.Serializable; +import java.net.URL; import java.util.List; import java.util.Map.Entry; import java.util.logging.Level; @@ -11,6 +12,7 @@ import org.json.simple.JSONObject; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.DBObject; @@ -27,6 +29,7 @@ import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.base.Venue; @@ -44,10 +47,14 @@ import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimeRange; import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WithID; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; @@ -56,6 +63,8 @@ import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler; import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.racelog.RaceLogEvent; @@ -72,6 +81,16 @@ import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; import com.sap.sailing.domain.tracking.Positioned; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; @@ -81,15 +100,31 @@ 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.DeviceConfigurationJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.RegattaConfigurationJsonSerializer; +import com.sap.sse.common.Util; public class MongoObjectFactoryImpl implements MongoObjectFactory { private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName()); private final DB database; private final CompetitorJsonSerializer competitorSerializer = CompetitorJsonSerializer.create(); + private final TypeBasedServiceFinder deviceIdentifierServiceFinder; + /** + * Uses null for the device type service finder and hence will be unable to store device identifiers. + * Use this constructor only for testing purposes or in cases where there will happen absolutely no access to + * {@link DeviceIdentifier} objects. + */ public MongoObjectFactoryImpl(DB database) { - super(); + this(database, /* deviceTypeServiceFinder */ null); + } + + public MongoObjectFactoryImpl(DB database, TypeBasedServiceFinderFactory serviceFinderFactory) { this.database = database; + if (serviceFinderFactory != null) { + this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class); + this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler()); + } else { + this.deviceIdentifierServiceFinder = null; + } } @Override @@ -105,33 +140,46 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } - private void storeTimePoint(TimePoint timePoint, DBObject result, FieldNames field) { + public static void storeTimePoint(TimePoint timePoint, DBObject result, String fieldName) { if (timePoint != null) { - result.put(field.name(), timePoint.asMillis()); + result.put(fieldName, timePoint.asMillis()); } } - private void storeTimed(Timed timed, DBObject result) { + public static void storeTimePoint(TimePoint timePoint, DBObject result, FieldNames field) { + storeTimePoint(timePoint, result, field.name()); + } + + public static void storeTimeRange(TimeRange timeRange, DBObject result, FieldNames field) { + if (timeRange != null) { + DBObject timeRangeObj = new BasicDBObject(); + storeTimePoint(timeRange.from(), timeRangeObj, FieldNames.FROM_MILLIS); + storeTimePoint(timeRange.to(), timeRangeObj, FieldNames.TO_MILLIS); + result.put(field.name(), timeRangeObj); + } + } + + public void storeTimed(Timed timed, DBObject result) { if (timed.getTimePoint() != null) { storeTimePoint(timed.getTimePoint(), result, FieldNames.TIME_AS_MILLIS); } } - private void storeSpeedWithBearing(SpeedWithBearing speedWithBearing, DBObject result) { + public void storeSpeedWithBearing(SpeedWithBearing speedWithBearing, DBObject result) { storeSpeed(speedWithBearing, result); storeBearing(speedWithBearing.getBearing(), result); } - private void storeBearing(Bearing bearing, DBObject result) { + public void storeBearing(Bearing bearing, DBObject result) { result.put(FieldNames.DEGREE_BEARING.name(), bearing.getDegrees()); } - private void storeSpeed(Speed speed, DBObject result) { + public void storeSpeed(Speed speed, DBObject result) { result.put(FieldNames.KNOT_SPEED.name(), speed.getKnots()); } - private void storePositioned(Positioned positioned, DBObject result) { + public void storePositioned(Positioned positioned, DBObject result) { if (positioned.getPosition() != null) { result.put(FieldNames.LAT_DEG.name(), positioned.getPosition().getLatDeg()); result.put(FieldNames.LNG_DEG.name(), positioned.getPosition().getLngDeg()); @@ -150,6 +198,23 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } + public DBCollection getGPSFixCollection() { + DBCollection gpsFixCollection = database.getCollection(CollectionNames.GPS_FIXES.name()); + DBObject index = new BasicDBObject(); + index.put(FieldNames.DEVICE_ID.name(), null); + index.put(FieldNames.TIME_AS_MILLIS.name(), null); + gpsFixCollection.ensureIndex(index); + return gpsFixCollection; + } + + public DBCollection getGPSFixMetadataCollection() { + DBCollection collection = database.getCollection(CollectionNames.GPS_FIXES_METADATA.name()); + DBObject index = new BasicDBObject(); + index.put(FieldNames.DEVICE_ID.name(), null); + collection.ensureIndex(index); + return collection; + } + /** * @param regattaName * the regatta name is stored only for human readability purposes because a time stamp may be a bit unhandy for @@ -373,6 +438,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { } BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName()); BasicDBObject dbLeaderboardGroup = new BasicDBObject(); + dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), leaderboardGroup.getId()); dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName()); dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription()); dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name(), leaderboardGroup.isDisplayGroupsInReverseOrder()); @@ -417,6 +483,25 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { leaderboardGroupCollection.update(query, update, /* upsert */ true, /* multi */ false, WriteConcern.SAFE); } + @Override + public void storeSailingServer(RemoteSailingServerReference server) { + DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name()); + serverCollection.ensureIndex(FieldNames.SERVER_NAME.name()); + DBObject query = new BasicDBObject(); + query.put(FieldNames.SERVER_NAME.name(), server.getName()); + DBObject serverDBObject = new BasicDBObject(); + serverDBObject.put(FieldNames.SERVER_NAME.name(), server.getName()); + serverDBObject.put(FieldNames.SERVER_URL.name(), server.getURL().toExternalForm()); + serverCollection.update(query, serverDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); + } + + @Override + public void removeSailingServer(String name) { + DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name()); + BasicDBObject query = new BasicDBObject(FieldNames.SERVER_NAME.name(), name); + serverCollection.remove(query); + } + @Override public void storeEvent(Event event) { DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name()); @@ -431,7 +516,28 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { eventDBObject.put(FieldNames.EVENT_IS_PUBLIC.name(), event.isPublic()); DBObject venueDBObject = getVenueAsDBObject(event.getVenue()); eventDBObject.put(FieldNames.VENUE.name(), venueDBObject); + BasicDBList imageURLs = new BasicDBList(); + for (URL imageURL : event.getImageURLs()) { + imageURLs.add(imageURL.toString()); + } + eventDBObject.put(FieldNames.EVENT_IMAGE_URLS.name(), imageURLs); + BasicDBList videoURLs = new BasicDBList(); + for (URL videoURL : event.getVideoURLs()) { + videoURLs.add(videoURL.toString()); + } + eventDBObject.put(FieldNames.EVENT_VIDEO_URLS.name(), videoURLs); eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); + // now store the links to the leaderboard groups + DBCollection linksCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUP_LINKS_FOR_EVENTS.name()); + linksCollection.ensureIndex(FieldNames.EVENT_ID.name()); + BasicDBList lgUUIDs = new BasicDBList(); + for (LeaderboardGroup lg : event.getLeaderboardGroups()) { + lgUUIDs.add(lg.getId()); + } + DBObject dbLinks = new BasicDBObject(); + dbLinks.put(FieldNames.EVENT_ID.name(), event.getId()); + dbLinks.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), lgUUIDs); + linksCollection.update(query, dbLinks, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); } @Override @@ -538,7 +644,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { if (fleet instanceof FleetImpl) { dbFleet.put(FieldNames.FLEET_ORDERING.name(), ((FleetImpl) fleet).getOrdering()); if(fleet.getColor() != null) { - Triple colorAsRGB = fleet.getColor().getAsRGB(); + com.sap.sse.common.Util.Triple colorAsRGB = fleet.getColor().getAsRGB(); // we save the color as a integer value representing the RGB values int colorAsInt = (256 * 256 * colorAsRGB.getC()) + colorAsRGB.getB() * 256 + colorAsRGB.getA(); dbFleet.put(FieldNames.FLEET_COLOR.name(), colorAsInt); @@ -654,25 +760,81 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartProcedureChangedEvent event) { DBObject result = new BasicDBObject(); - result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier())); + storeRaceLogIdentifier(raceLogIdentifier, result); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartProcedureChangedEvent(event)); return result; } public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogProtestStartTimeEvent event) { DBObject result = new BasicDBObject(); - result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier())); + storeRaceLogIdentifier(raceLogIdentifier, result); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogProtestStartTimeEvent(event)); return result; } public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogWindFixEvent event) { DBObject result = new BasicDBObject(); - result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier())); + storeRaceLogIdentifier(raceLogIdentifier, result); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogWindFix(event)); return result; } + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, DeviceCompetitorMappingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceCompetitorMappingEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, DeviceMarkMappingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceMarkMappingEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, DenoteForTrackingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDenoteForTrackingEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, StartTrackingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTrackingEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RevokeEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRevokeEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RegisterCompetitorEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRegisterCompetitorEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, DefineMarkEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDefineMarkEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, CloseOpenEndedDeviceMappingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCloseOpenEndedDeviceMappingEvent(event)); + return result; + } + private Object storeRaceLogWindFix(RaceLogWindFixEvent event) { DBObject result = new BasicDBObject(); storeRaceLogEventProperties(event, result); @@ -705,12 +867,87 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } - private DBObject storeRaceLogStartTimeEvent(RaceLogStartTimeEvent startTimeEvent) { + private void storeRaceLogDeviceMappingEvent(DeviceMappingEvent event, DBObject result) { + DBObject deviceId = null; + try { + deviceId = storeDeviceId(deviceIdentifierServiceFinder, event.getDevice()); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not store deviceId for RaceLogEvent", e); + e.printStackTrace(); + } + result.put(FieldNames.DEVICE_ID.name(), deviceId); + if (event.getFrom() != null) { + storeTimePoint(event.getFrom(), result, FieldNames.RACE_LOG_FROM); + } + if (event.getTo() != null) { + storeTimePoint(event.getTo(), result, FieldNames.RACE_LOG_TO); + } + } + + private Object storeRaceLogDeviceCompetitorMappingEvent(DeviceCompetitorMappingEvent event) { DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(startTimeEvent, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTimeEvent.class.getSimpleName()); - storeTimePoint(startTimeEvent.getStartTime(), result, FieldNames.RACE_LOG_EVENT_START_TIME); - result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), startTimeEvent.getNextStatus().name()); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), DeviceCompetitorMappingEvent.class.getSimpleName()); + storeRaceLogDeviceMappingEvent(event, result); + result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId()); + return result; + } + + private Object storeRaceLogDeviceMarkMappingEvent(DeviceMarkMappingEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), DeviceMarkMappingEvent.class.getSimpleName()); + storeRaceLogDeviceMappingEvent(event, result); + result.put(FieldNames.MARK_ID.name(), event.getMappedTo().getId()); + return result; + } + + private Object storeRaceLogDenoteForTrackingEvent(DenoteForTrackingEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), DenoteForTrackingEvent.class.getSimpleName()); + result.put(FieldNames.RACE_NAME.name(), event.getRaceName()); + result.put(FieldNames.BOAT_CLASS_NAME.name(), event.getBoatClass().getName()); + result.put(FieldNames.RACE_ID.name(), event.getRaceId()); + return result; + } + + private Object storeRaceLogStartTrackingEvent(StartTrackingEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), StartTrackingEvent.class.getSimpleName()); + return result; + } + + private Object storeRaceLogRevokeEvent(RevokeEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RevokeEvent.class.getSimpleName()); + result.put(FieldNames.RACE_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId()); + return result; + } + + private Object storeRaceLogRegisterCompetitorEvent(RegisterCompetitorEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RegisterCompetitorEvent.class.getSimpleName()); + return result; + } + + private Object storeRaceLogDefineMarkEvent(DefineMarkEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), DefineMarkEvent.class.getSimpleName()); + result.put(FieldNames.RACE_LOG_MARK.name(), storeMark(event.getMark())); + return result; + } + + private Object storeRaceLogCloseOpenEndedDeviceMappingEvent(CloseOpenEndedDeviceMappingEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), CloseOpenEndedDeviceMappingEvent.class.getSimpleName()); + result.put(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId()); + storeTimePoint(event.getClosingTimePoint(), result, FieldNames.RACE_LOG_CLOSING_TIMEPOINT); return result; } @@ -724,6 +961,15 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } + private DBObject storeRaceLogStartTimeEvent(RaceLogStartTimeEvent startTimeEvent) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(startTimeEvent, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTimeEvent.class.getSimpleName()); + storeTimePoint(startTimeEvent.getStartTime(), result, FieldNames.RACE_LOG_EVENT_START_TIME); + result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), startTimeEvent.getNextStatus().name()); + return result; + } + private void storeRaceLogEventProperties(RaceLogEvent event, DBObject result) { // for compatibility reasons we reuse the field name of Timed storeTimePoint(event.getLogicalTimePoint(), result, FieldNames.TIME_AS_MILLIS); @@ -802,17 +1048,17 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } - private BasicDBList storePositionedCompetitors(List> positionedCompetitors) { + private BasicDBList storePositionedCompetitors(List> positionedCompetitors) { BasicDBList dbList = new BasicDBList(); if (positionedCompetitors != null) { - for (Triple competitorPair : positionedCompetitors) { + for (com.sap.sse.common.Util.Triple competitorPair : positionedCompetitors) { dbList.add(storePositionedCompetitor(competitorPair)); } } return dbList; } - private DBObject storePositionedCompetitor(Triple competitorTriple) { + private DBObject storePositionedCompetitor(com.sap.sse.common.Util.Triple competitorTriple) { DBObject result = new BasicDBObject(); result.put(FieldNames.COMPETITOR_ID.name(), competitorTriple.getA()); result.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), competitorTriple.getB()); @@ -947,6 +1193,20 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { configurationsCollections.remove(query); } + public static DBObject storeDeviceId( + TypeBasedServiceFinder deviceIdentifierServiceFinder, DeviceIdentifier device) + throws TransformationException, NoCorrespondingServiceRegisteredException { + String type = device.getIdentifierType(); + DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type); + com.sap.sse.common.Util.Pair pair = handler.serialize(device); + type = pair.getA(); + Object deviceTypeSpecificId = pair.getB(); + return new BasicDBObjectBuilder() + .add(FieldNames.DEVICE_TYPE.name(), type) + .add(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), deviceTypeSpecificId) + .add(FieldNames.DEVICE_STRING_REPRESENTATION.name(), device.getStringRepresentation()).get(); + } + void storeRaceLogEventEvent(DBObject eventEntry) { getRaceLogCollection().insert(eventEntry); logger.fine("Inserted event entry into mongo race log collection"); diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java index 5e0e9bc25ba..39b0108f90e 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java @@ -1,114 +1,169 @@ -package com.sap.sailing.domain.persistence.impl; - -import java.util.logging.Logger; - -import com.mongodb.DBObject; -import com.sap.sailing.domain.persistence.MongoObjectFactory; -import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogEventVisitor; -import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; -import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogFlagEvent; -import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; -import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent; -import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent; -import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; -import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; -import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; -import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; - -public class MongoRaceLogStoreVisitor implements RaceLogEventVisitor { - - private final RaceLogIdentifier raceLogIdentifier; - private final MongoObjectFactoryImpl mongoObjectFactory; - - final static Logger logger = Logger.getLogger(MongoRaceLogStoreVisitor.class.getName()); - - public MongoRaceLogStoreVisitor(RaceLogIdentifier identifier, MongoObjectFactory mongoObjectFactory) { - super(); - this.raceLogIdentifier = identifier; - this.mongoObjectFactory = (MongoObjectFactoryImpl) mongoObjectFactory; - } - - @Override - public void visit(RaceLogFlagEvent event) { - DBObject flagEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(flagEventTrackEntry); - } - - @Override - public void visit(RaceLogPassChangeEvent event) { - DBObject passChangeEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(passChangeEventTrackEntry); - } - - @Override - public void visit(RaceLogRaceStatusEvent event) { - DBObject raceStatusEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(raceStatusEventTrackEntry); - } - - @Override - public void visit(RaceLogStartTimeEvent event) { - DBObject startTimeEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(startTimeEventTrackEntry); - } - - @Override - public void visit(RaceLogCourseAreaChangedEvent event) { - DBObject courseAreaChangedEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(courseAreaChangedEventTrackEntry); - } - - @Override - public void visit(RaceLogCourseDesignChangedEvent event) { - DBObject courseDesignChangedEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(courseDesignChangedEventTrackEntry); - } - - @Override - public void visit(RaceLogFinishPositioningListChangedEvent event) { - DBObject finishPositioningListChangedEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(finishPositioningListChangedEventTrackEntry); - } - - @Override - public void visit(RaceLogFinishPositioningConfirmedEvent event) { - DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(object); - } - - @Override - public void visit(RaceLogPathfinderEvent event) { - DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(object); - } - - @Override - public void visit(RaceLogGateLineOpeningTimeEvent event) { - DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(object); - } - - @Override - public void visit(RaceLogStartProcedureChangedEvent event) { - DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(object); - } - - @Override - public void visit(RaceLogProtestStartTimeEvent event) { - DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(object); - } - - @Override - public void visit(RaceLogWindFixEvent event) { - DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); - mongoObjectFactory.storeRaceLogEventEvent(object); - } - -} +package com.sap.sailing.domain.persistence.impl; + +import java.util.logging.Logger; + +import com.mongodb.DBObject; +import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; +import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogFlagEvent; +import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent; +import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent; +import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; + +public class MongoRaceLogStoreVisitor implements RaceLogEventVisitor { + + private final RaceLogIdentifier raceLogIdentifier; + private final MongoObjectFactoryImpl mongoObjectFactory; + + final static Logger logger = Logger.getLogger(MongoRaceLogStoreVisitor.class.getName()); + + public MongoRaceLogStoreVisitor(RaceLogIdentifier identifier, MongoObjectFactory mongoObjectFactory) { + super(); + this.raceLogIdentifier = identifier; + this.mongoObjectFactory = (MongoObjectFactoryImpl) mongoObjectFactory; + } + + @Override + public void visit(RaceLogFlagEvent event) { + DBObject flagEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(flagEventTrackEntry); + } + + @Override + public void visit(RaceLogPassChangeEvent event) { + DBObject passChangeEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(passChangeEventTrackEntry); + } + + @Override + public void visit(RaceLogRaceStatusEvent event) { + DBObject raceStatusEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(raceStatusEventTrackEntry); + } + + @Override + public void visit(RaceLogStartTimeEvent event) { + DBObject startTimeEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(startTimeEventTrackEntry); + } + + @Override + public void visit(RaceLogCourseAreaChangedEvent event) { + DBObject courseAreaChangedEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(courseAreaChangedEventTrackEntry); + } + + @Override + public void visit(RaceLogCourseDesignChangedEvent event) { + DBObject courseDesignChangedEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(courseDesignChangedEventTrackEntry); + } + + @Override + public void visit(RaceLogFinishPositioningListChangedEvent event) { + DBObject finishPositioningListChangedEventTrackEntry = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(finishPositioningListChangedEventTrackEntry); + } + + @Override + public void visit(RaceLogFinishPositioningConfirmedEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(RaceLogPathfinderEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(RaceLogGateLineOpeningTimeEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(RaceLogStartProcedureChangedEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(RaceLogProtestStartTimeEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(RaceLogWindFixEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(DeviceCompetitorMappingEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(DeviceMarkMappingEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(DenoteForTrackingEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(StartTrackingEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(RevokeEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(RegisterCompetitorEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(DefineMarkEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(CloseOpenEndedDeviceMappingEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/PersistenceFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/PersistenceFactoryImpl.java index adfd2ce63e6..f6906190d71 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/PersistenceFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/PersistenceFactoryImpl.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.persistence.impl; import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; import com.sap.sailing.domain.persistence.DomainObjectFactory; import com.sap.sailing.domain.persistence.MongoObjectFactory; import com.sap.sailing.domain.persistence.PersistenceFactory; @@ -11,7 +12,6 @@ public class PersistenceFactoryImpl implements PersistenceFactory { private final MongoObjectFactory defaultMongoObjectFactory; public PersistenceFactoryImpl() { - super(); this.defaultDomainObjectFactory = new DomainObjectFactoryImpl(MongoDBService.INSTANCE.getDB(), DomainFactory.INSTANCE); this.defaultMongoObjectFactory = new MongoObjectFactoryImpl(MongoDBService.INSTANCE.getDB()); } @@ -31,14 +31,29 @@ public class PersistenceFactoryImpl implements PersistenceFactory { return new DomainObjectFactoryImpl(mongoDBService.getDB(), baseDomainFactory); } + @Override + public DomainObjectFactory getDomainObjectFactory(MongoDBService mongoDBService, DomainFactory baseDomainFactory, + TypeBasedServiceFinderFactory serviceFinderFactory) { + return new DomainObjectFactoryImpl(mongoDBService.getDB(), baseDomainFactory, serviceFinderFactory); + } + @Override public MongoObjectFactory getDefaultMongoObjectFactory() { return defaultMongoObjectFactory; } + + @Override + public MongoObjectFactory getDefaultMongoObjectFactory(TypeBasedServiceFinderFactory serviceFinderFactory) { + return new MongoObjectFactoryImpl(this.defaultMongoObjectFactory.getDatabase(), serviceFinderFactory); + } @Override public MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDBService) { return new MongoObjectFactoryImpl(mongoDBService.getDB()); } + @Override + public MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDBService, TypeBasedServiceFinderFactory serviceFinderFactory) { + return new MongoObjectFactoryImpl(mongoDBService.getDB(), serviceFinderFactory); + } } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/TripleSerializer.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/TripleSerializer.java index 7fda33ae5b9..2fc7244eaa6 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/TripleSerializer.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/TripleSerializer.java @@ -2,10 +2,10 @@ package com.sap.sailing.domain.persistence.impl; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; public class TripleSerializer { - public static DBObject serialize(Triple value) { + public static DBObject serialize(Util.Triple value) { DBObject container = new BasicDBObject(); container.put("a", value.getA()); container.put("b", value.getB()); diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/DeviceIdentifierMongoHandler.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/DeviceIdentifierMongoHandler.java new file mode 100644 index 00000000000..13b310f404d --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/DeviceIdentifierMongoHandler.java @@ -0,0 +1,6 @@ +package com.sap.sailing.domain.persistence.racelog.tracking; + +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifierSerializationHandler; + +public interface DeviceIdentifierMongoHandler extends DeviceIdentifierSerializationHandler { +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/GPSFixMongoHandler.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/GPSFixMongoHandler.java new file mode 100644 index 00000000000..4f84f42ff20 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/GPSFixMongoHandler.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.persistence.racelog.tracking; + +import com.mongodb.DBObject; +import com.sap.sailing.domain.common.racelog.tracking.TransformationHandler; +import com.sap.sailing.domain.tracking.GPSFix; + +/** + * For GPS Fixes, the fully qualified class name is the type used to identify + * the handlers, as the fixes are domain objects and are not intended to carry + * some separate type attribute. + * + * @author Fredrik Teschke + * + */ +public interface GPSFixMongoHandler extends TransformationHandler { +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/MongoGPSFixStore.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/MongoGPSFixStore.java new file mode 100644 index 00000000000..8e6a07efb10 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/MongoGPSFixStore.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.persistence.racelog.tracking; + +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; + +public interface MongoGPSFixStore extends GPSFixStore { + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/MongoGPSFixStoreFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/MongoGPSFixStoreFactory.java new file mode 100644 index 00000000000..071feef5ef5 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/MongoGPSFixStoreFactory.java @@ -0,0 +1,22 @@ +package com.sap.sailing.domain.persistence.racelog.tracking; + +import java.net.UnknownHostException; + +import com.mongodb.MongoException; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoGPSFixStoreFactoryImpl; + +public interface MongoGPSFixStoreFactory { + MongoGPSFixStoreFactory INSTANCE = new MongoGPSFixStoreFactoryImpl(); + + /** + * Gets the default Mongo store based on the properties mongo.hostname[=localhost], mongo.port[=27017] + * and mongo.dbName[=<contents of constant {@link #DEFAULT_DB_NAME}>] specified in the bundle context + */ + MongoGPSFixStore getMongoGPSFixStore(MongoObjectFactory mongoObjectFactory, DomainObjectFactory domainObjectFactory, + TypeBasedServiceFinderFactory serviceFinderFactory) + throws UnknownHostException, MongoException; + +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/GPSFixMongoHandlerImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/GPSFixMongoHandlerImpl.java new file mode 100644 index 00000000000..6f768e29d1f --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/GPSFixMongoHandlerImpl.java @@ -0,0 +1,40 @@ +package com.sap.sailing.domain.persistence.racelog.tracking.impl; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl; +import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl; +import com.sap.sailing.domain.persistence.racelog.tracking.GPSFixMongoHandler; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.impl.GPSFixImpl; + +public class GPSFixMongoHandlerImpl implements GPSFixMongoHandler { + private final MongoObjectFactoryImpl mof; + private final DomainObjectFactoryImpl dof; + + public GPSFixMongoHandlerImpl(MongoObjectFactory mof, DomainObjectFactory dof) { + this.mof = (MongoObjectFactoryImpl) mof; + this.dof = (DomainObjectFactoryImpl) dof; + } + + @Override + public DBObject transformForth(GPSFix fix) throws IllegalArgumentException { + DBObject result = new BasicDBObject(); + mof.storeTimed(fix, result); + mof.storePositioned(fix, result); + return result; + } + + @Override + public GPSFix transformBack(DBObject object) { + DBObject dbObject = (DBObject) object; + TimePoint timePoint = dof.loadTimePoint(dbObject); + Position position = dof.loadPosition(dbObject); + return new GPSFixImpl(position, timePoint); + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/GPSFixMovingMongoHandlerImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/GPSFixMovingMongoHandlerImpl.java new file mode 100644 index 00000000000..df778585c8a --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/GPSFixMovingMongoHandlerImpl.java @@ -0,0 +1,43 @@ +package com.sap.sailing.domain.persistence.racelog.tracking.impl; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl; +import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl; +import com.sap.sailing.domain.persistence.racelog.tracking.GPSFixMongoHandler; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; + +public class GPSFixMovingMongoHandlerImpl implements GPSFixMongoHandler { + private final MongoObjectFactoryImpl mof; + private final DomainObjectFactoryImpl dof; + + public GPSFixMovingMongoHandlerImpl(MongoObjectFactory mof, DomainObjectFactory dof) { + this.mof = (MongoObjectFactoryImpl) mof; + this.dof = (DomainObjectFactoryImpl) dof; + } + + @Override + public DBObject transformForth(GPSFix fix) throws IllegalArgumentException { + DBObject result = new BasicDBObject(); + mof.storeTimed(fix, result); + mof.storePositioned(fix, result); + mof.storeSpeedWithBearing(((GPSFixMoving) fix).getSpeed(), result); + return result; + } + + @Override + public GPSFix transformBack(DBObject dbObject) { + TimePoint timePoint = dof.loadTimePoint(dbObject); + Position position = dof.loadPosition(dbObject); + SpeedWithBearing speed = dof.loadSpeedWithBearing(dbObject); + return new GPSFixMovingImpl(position, timePoint, speed); + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreFactoryImpl.java new file mode 100644 index 00000000000..25bc965db36 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreFactoryImpl.java @@ -0,0 +1,19 @@ +package com.sap.sailing.domain.persistence.racelog.tracking.impl; + +import java.net.UnknownHostException; + +import com.mongodb.MongoException; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.persistence.racelog.tracking.MongoGPSFixStore; +import com.sap.sailing.domain.persistence.racelog.tracking.MongoGPSFixStoreFactory; + +public class MongoGPSFixStoreFactoryImpl implements MongoGPSFixStoreFactory { + @Override + public MongoGPSFixStore getMongoGPSFixStore(MongoObjectFactory mongoObjectFactory, + DomainObjectFactory domainObjectFactory, TypeBasedServiceFinderFactory serviceFinderFactory) + throws UnknownHostException, MongoException { + return new MongoGPSFixStoreImpl(mongoObjectFactory, domainObjectFactory, serviceFinderFactory); + } +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreImpl.java new file mode 100755 index 00000000000..af5f023411b --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoGPSFixStoreImpl.java @@ -0,0 +1,232 @@ +package com.sap.sailing.domain.persistence.racelog.tracking.impl; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.DBCollection; +import com.mongodb.DBCursor; +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimeRange; +import com.sap.sailing.domain.common.impl.TimeRangeImpl; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl; +import com.sap.sailing.domain.persistence.impl.FieldNames; +import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.persistence.racelog.tracking.GPSFixMongoHandler; +import com.sap.sailing.domain.persistence.racelog.tracking.MongoGPSFixStore; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMapping; +import com.sap.sailing.domain.racelog.tracking.GPSFixReceivedListener; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceCompetitorMappingFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceMarkMappingFinder; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sse.common.Util; + +/** + * At the moment, the timerange covered by the fixes for a device, and the number of + * fixes for a device are stored in a metadata collection. Should be changed, see bug 1982. + * @author Fredrik Teschke + * + */ +public class MongoGPSFixStoreImpl implements MongoGPSFixStore { + private static final Logger logger = Logger.getLogger(MongoGPSFixStore.class.getName()); + private final TypeBasedServiceFinder fixServiceFinder; + private final TypeBasedServiceFinder deviceServiceFinder; + private final DBCollection fixesCollection; + private final DBCollection metadataCollection; + private final MongoObjectFactoryImpl mongoOF; + private final Map> listeners = new HashMap<>(); + + public MongoGPSFixStoreImpl(MongoObjectFactory mongoObjectFactory, + DomainObjectFactory domainObjectFactory, TypeBasedServiceFinderFactory serviceFinderFactory) { + mongoOF = (MongoObjectFactoryImpl) mongoObjectFactory; + if (serviceFinderFactory != null) { + fixServiceFinder = serviceFinderFactory.createServiceFinder(GPSFixMongoHandler.class); + deviceServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class); + } else { + fixServiceFinder = null; + deviceServiceFinder = null; + } + fixesCollection = mongoOF.getGPSFixCollection(); + metadataCollection = mongoOF.getGPSFixMetadataCollection(); + } + + private GPSFix loadGPSFix(DBObject object) throws TransformationException, NoCorrespondingServiceRegisteredException { + String type = (String) object.get(FieldNames.GPSFIX_TYPE.name()); + DBObject fixObject = (DBObject) object.get(FieldNames.GPSFIX.name()); + return fixServiceFinder.findService(type).transformBack(fixObject); + } + + private void loadTrack(DynamicGPSFixTrack track, DeviceIdentifier device, + TimePoint from, TimePoint to, boolean inclusive) throws NoCorrespondingServiceRegisteredException, + TransformationException { + long fromMillis = from.asMillis() - (inclusive ? 1 : 0); + long toMillis = to.asMillis() + (inclusive ? 1 : 0); + + Object dbDeviceId = MongoObjectFactoryImpl.storeDeviceId(deviceServiceFinder, device); + + DBObject query = QueryBuilder.start(FieldNames.DEVICE_ID.name()).is(dbDeviceId) + .and(FieldNames.TIME_AS_MILLIS.name()).greaterThan(fromMillis) + .and(FieldNames.TIME_AS_MILLIS.name()).lessThan(toMillis).get(); + + DBCursor result = fixesCollection.find(query); + for (DBObject fixObject : result) { + try { + @SuppressWarnings("unchecked") + FixT fix = (FixT) loadGPSFix(fixObject); + track.add(fix); + } catch (TransformationException e) { + logger.log(Level.WARNING, "Could not read fix from MongoDB: " + fixObject); + } catch (ClassCastException e) { + String type = (String) fixObject.get(FieldNames.GPSFIX_TYPE.name()); + logger.log(Level.WARNING, "Unexpected fix type (" + type + ") encountered when trying to load track for " + track.getTrackedItem()); + } + } + } + + @Override + public void loadCompetitorTrack(DynamicGPSFixTrack track, RaceLog raceLog, Competitor competitor) + throws NoCorrespondingServiceRegisteredException, TransformationException{ + List> mappings = new DeviceCompetitorMappingFinder(raceLog).analyze().get(competitor); + + if (mappings != null) { + for (DeviceMapping mapping : mappings) { + loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true /*inclusive*/); + } + } + } + + @Override + public void loadMarkTrack(DynamicGPSFixTrack track, RaceLog raceLog, Mark mark) + throws NoCorrespondingServiceRegisteredException, TransformationException{ + List> mappings = new DeviceMarkMappingFinder(raceLog).analyze().get(mark); + + if (mappings != null) { + for (DeviceMapping mapping : mappings) { + loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true /*inclusive*/); + } + } + } + + @Override + public void storeFix(DeviceIdentifier device, GPSFix fix) { + try { + Object dbDeviceId = MongoObjectFactoryImpl.storeDeviceId(deviceServiceFinder, device); + String type = fix.getClass().getName(); + Object fixObject = fixServiceFinder.findService(type).transformForth(fix); + DBObject entry = new BasicDBObjectBuilder().add(FieldNames.DEVICE_ID.name(), dbDeviceId) + .add(FieldNames.GPSFIX_TYPE.name(), type).add(FieldNames.GPSFIX.name(), fixObject).get(); + mongoOF.storeTimed(fix, entry); + + fixesCollection.insert(entry); + + TimeRange oldTimeRange = getTimeRangeCoveredByFixes(device); + long oldNumFixes = getNumberOfFixes(device); + + DBObject newMetadata = new BasicDBObject(); + newMetadata.put(FieldNames.DEVICE_ID.name(), MongoObjectFactoryImpl.storeDeviceId(deviceServiceFinder, device)); + newMetadata.put(FieldNames.NUM_FIXES.name(), oldNumFixes + 1); + + TimePoint newFrom = fix.getTimePoint(); + TimePoint newTo = fix.getTimePoint(); + if (oldTimeRange != null) { + if (oldTimeRange.from().before(newFrom)) { + newFrom = oldTimeRange.from(); + } + if (oldTimeRange.to().after(newTo)) { + newTo = oldTimeRange.to(); + } + } + MongoObjectFactoryImpl.storeTimeRange(new TimeRangeImpl(newFrom, newTo), newMetadata, FieldNames.TIMERANGE); + metadataCollection.update(getDeviceQuery(device), newMetadata, /*create if not existent*/ true, + /*update multiple*/ false); + } catch (TransformationException e) { + logger.log(Level.WARNING, "Could not store fix in MongoDB"); + e.printStackTrace(); + } + notifyListeners(device, fix); + } + + private void notifyListeners(DeviceIdentifier device, GPSFix fix) { + for (GPSFixReceivedListener listener : Util.get(listeners, device, Collections.emptySet())) { + listener.fixReceived(device, fix); + } + } + + @Override + public synchronized void addListener(GPSFixReceivedListener listener, DeviceIdentifier device) { + Util.addToValueSet(listeners, device, listener); + } + + @Override + public synchronized void removeListener(GPSFixReceivedListener listener) { + for (Set set : listeners.values()) { + set.remove(listener); + } + } + + @Override + public void loadCompetitorTrack(DynamicGPSFixTrack track, DeviceMapping mapping) + throws TransformationException, NoCorrespondingServiceRegisteredException { + loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true /*inclusive*/); + } + + @Override + public void loadMarkTrack(DynamicGPSFixTrack track, DeviceMapping mapping) + throws TransformationException, NoCorrespondingServiceRegisteredException { + loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true /*inclusive*/); + } + + private DBObject getDeviceQuery(DeviceIdentifier device) + throws TransformationException, NoCorrespondingServiceRegisteredException { + Object dbDeviceId = MongoObjectFactoryImpl.storeDeviceId(deviceServiceFinder, device); + DBObject query = QueryBuilder.start(FieldNames.DEVICE_ID.name()).is(dbDeviceId).get(); + return query; + } + + private DBObject findMetadataObject(DeviceIdentifier device) + throws TransformationException, NoCorrespondingServiceRegisteredException { + DBObject query = getDeviceQuery(device); + DBObject result = metadataCollection.findOne(query); + return result; + } + + @Override + public TimeRange getTimeRangeCoveredByFixes(DeviceIdentifier device) + throws TransformationException, NoCorrespondingServiceRegisteredException { + DBObject result = findMetadataObject(device); + if (result == null) { + return null; + } + return DomainObjectFactoryImpl.loadTimeRange(result, FieldNames.TIMERANGE); + } + + @Override + public long getNumberOfFixes(DeviceIdentifier device) throws TransformationException, NoCorrespondingServiceRegisteredException { + DBObject result = findMetadataObject(device); + if (result == null) { + return 0; + } + return ((Number) result.get(FieldNames.NUM_FIXES.name())).longValue(); + } +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/PlaceHolderDeviceIdentifierMongoHandler.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/PlaceHolderDeviceIdentifierMongoHandler.java new file mode 100644 index 00000000000..5657f067858 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/PlaceHolderDeviceIdentifierMongoHandler.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.persistence.racelog.tracking.impl; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.impl.PlaceHolderDeviceIdentifierSerializationHandler; + +public class PlaceHolderDeviceIdentifierMongoHandler extends PlaceHolderDeviceIdentifierSerializationHandler +implements DeviceIdentifierMongoHandler { + + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + return deserialize((String) serialized, type, stringRepresentation); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/.classpath b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.classpath new file mode 100644 index 00000000000..e8ea977a694 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/.gitignore b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.gitignore new file mode 100644 index 00000000000..ba077a4031a --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.gitignore @@ -0,0 +1 @@ +bin diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/.project b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.project new file mode 100644 index 00000000000..30f37c10090 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.domain.racelogtrackingadapter.test + + + + + + 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.domain.racelogtrackingadapter.test/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..eb01f948334 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Thu Jan 19 09:52:18 CET 2012 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 00000000000..82689ad754c --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +#Thu Aug 25 17:11:24 CEST 2011 +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.racelogtrackingadapter.test/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..72fab3f84ae --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/META-INF/MANIFEST.MF @@ -0,0 +1,20 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Smartphone Adapter Test +Bundle-SymbolicName: com.sap.sailing.domain.racelogtrackingadapter.test +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Fragment-Host: com.sap.sailing.domain.racelogtrackingadapter +Require-Bundle: org.junit4;bundle-version="4.8.2", + com.sap.sailing.domain.racelogtrackingadapter.testsupport, + org.mockito.mockito-core;bundle-version="1.9.5", + org.objenesis;bundle-version="1.3.0", + org.hamcrest;bundle-version="1.1.0", + com.mongodb.driver;bundle-version="2.6.2", + com.sap.sse.common +Import-Package: com.sap.sailing.domain.persistence.impl, + com.sap.sailing.domain.persistence.racelog.tracking.impl, + com.sap.sailing.domain.tractracadapter, + com.sap.sailing.server.impl, + javax.servlet.http;version="2.5.0" diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/build.properties b/java/com.sap.sailing.domain.racelogtrackingadapter.test/build.properties new file mode 100644 index 00000000000..6b53438d1ce --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + ., diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/pom.xml b/java/com.sap.sailing.domain.racelogtrackingadapter.test/pom.xml new file mode 100644 index 00000000000..ebb5dc2927c --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.domain.racelogtrackingadapter.test + eclipse-test-plugin + diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/.gitignore b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java new file mode 100644 index 00000000000..a3b09fb3b5e --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java @@ -0,0 +1,83 @@ +package com.sap.sailing.domain.racelogtracking.test; + +import static com.sap.sse.common.Util.size; +import static junit.framework.Assert.assertEquals; + +import org.junit.After; +import org.junit.Before; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Mark; +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.MillisecondsTimePoint; +import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl; +import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoGPSFixStoreImpl; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; +import com.sap.sailing.domain.racelog.impl.RaceLogImpl; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelog.tracking.test.mock.MockDeviceAndSessioinIdentifierWithGPSFixesDeserializer; +import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory; +import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.Track; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.gateway.deserialization.impl.DeviceAndSessionIdentifierWithGPSFixesDeserializer; +import com.sap.sailing.server.impl.RacingEventServiceImpl; + +public class AbstractGPSFixStoreTest { + protected RacingEventService service; + protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory(); + DeviceAndSessionIdentifierWithGPSFixesDeserializer deserializer = + new MockDeviceAndSessioinIdentifierWithGPSFixesDeserializer(); + protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a"); + protected RaceLog raceLog; + protected GPSFixStore store; + protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null); + protected final Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark"); + + private final RaceLogEventAuthor author = new RaceLogEventAuthorImpl("author", 0); + + protected GPSFixMoving createFix(long millis, double lat, double lng, double knots, double degrees) { + return new GPSFixMovingImpl(new DegreePosition(lat, lng), + new MillisecondsTimePoint(millis), new KnotSpeedWithBearingImpl(knots, new DegreeBearingImpl(degrees))); + } + + @Before + public void setServiceAndRaceLog() { + service = new RacingEventServiceImpl(null, null, serviceFinderFactory); + raceLog = new RaceLogImpl("racelog"); + store = new MongoGPSFixStoreImpl(service.getMongoObjectFactory(), service.getDomainObjectFactory(), + serviceFinderFactory); + } + + @After + public void after() { + MongoObjectFactoryImpl mongoOF = (MongoObjectFactoryImpl) service.getMongoObjectFactory(); + mongoOF.getGPSFixCollection().drop(); + mongoOF.getGPSFixMetadataCollection().drop(); + } + + protected void map(Competitor comp, DeviceIdentifier device, long from, long to) { + raceLog.add(RaceLogEventFactory.INSTANCE.createDeviceCompetitorMappingEvent(MillisecondsTimePoint.now(), author, device, + comp, 0, new MillisecondsTimePoint(from), new MillisecondsTimePoint(to))); + } + + protected void map(Mark mark, DeviceIdentifier device, long from, long to) { + raceLog.add(RaceLogEventFactory.INSTANCE.createDeviceMarkMappingEvent(MillisecondsTimePoint.now(), author, device, + mark, 0, new MillisecondsTimePoint(from), new MillisecondsTimePoint(to))); + } + + protected void testLength(Track track, long expected) { + track.lockForRead(); + assertEquals(expected, size(track.getRawFixes())); + track.unlockAfterRead(); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractJsonOverHttpTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractJsonOverHttpTest.java new file mode 100644 index 00000000000..0014db454a9 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractJsonOverHttpTest.java @@ -0,0 +1,76 @@ +package com.sap.sailing.domain.racelogtracking.test; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.json.simple.JSONValue; +import org.json.simple.parser.ParseException; + +public abstract class AbstractJsonOverHttpTest { + protected static final String URL_BASE = "127.0.0.1"; + protected static final int PORT = 8888; + protected static final String URL_SS = "/sailingserver"; + protected static final String URL_TR = "/tracking"; + protected static final String URL_RF = "/recordFixes"; + + protected String getUrl(String endOfUrl) { + return "http://" + URL_BASE + ":" + PORT + endOfUrl; + } + + protected String executeRequest(String method, String targetURL, String body) throws IOException { + // Create connection + URL url = new URL(targetURL); + System.out.println(url); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod(method); + connection.setRequestProperty("Content-Type", "application/json"); + + connection.setUseCaches(false); + connection.setDoInput(true); + + // Send request + if (method.equals("POST")) { + connection.setDoOutput(true); + DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + wr.writeBytes(body); + } + + InputStream is = null; + boolean error = false; + if (connection.getResponseCode() >= 400) { + is = connection.getErrorStream(); + error = true; + } else { + is = connection.getInputStream(); + } + + if (is == null) { + return ""; + } + + BufferedReader rd = new BufferedReader(new InputStreamReader(is)); + String line; + StringBuffer response = new StringBuffer(); + while ((line = rd.readLine()) != null) { + response.append(line); + response.append('\r'); + } + rd.close(); + if (error) { + System.err.println(response.toString()); + return ""; + } + + return response.toString(); + } + + protected Object getJson(String url) throws IllegalStateException, IOException, ParseException { + return JSONValue.parseWithException(executeRequest("GET", url, "")); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java new file mode 100644 index 00000000000..90c87a40def --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java @@ -0,0 +1,152 @@ +package com.sap.sailing.domain.racelogtracking.test.impl; + +import static com.sap.sse.common.Util.size; +import static junit.framework.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.FileNotFoundException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.DomainFactory; +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.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.TimePoint; +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.MillisecondsTimePoint; +import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.HighPoint; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory; +import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; +import com.sap.sailing.domain.racelogtracking.impl.RaceLogTrackingAdapterFactoryImpl; +import com.sap.sailing.domain.tracking.Track; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.impl.RacingEventServiceImpl; + +public class CreateAndTrackWithRaceLogTest { + private RacingEventService service; + + private final Fleet fleet = new FleetImpl("fleet"); + private final String columnName = "column"; + private RegattaLeaderboard leaderboard; + private RaceLogTrackingAdapter adapter; + private Regatta regatta; + private RaceLogEventAuthor author; + private GPSFixStore gpsFixStore; + + private long time = 0; + + private static final RaceLogEventFactory factory = RaceLogEventFactory.INSTANCE; + + @Before + public void setup() { + service = new RacingEventServiceImpl(true, new MockSmartphoneImeiServiceFinderFactory()); + gpsFixStore = service.getGPSFixStore(); + service.getMongoObjectFactory().getDatabase().dropDatabase(); + author = service.getServerAuthor(); + Series series = new SeriesImpl("series", false, Collections.singletonList(fleet), + Collections.singletonList(columnName), service); + regatta = service.createRegatta("regatta", "Laser", UUID.randomUUID(), Collections.singletonList(series), + false, new HighPoint(), UUID.randomUUID()); + leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); + adapter = RaceLogTrackingAdapterFactoryImpl.INSTANCE.getAdapter(DomainFactory.INSTANCE); + } + + @Test + public void hasRaceLog() { + assertNotNull(leaderboard.getRaceColumnByName(columnName).getRaceLog(fleet)); + } + + private TimePoint t() { + return new MillisecondsTimePoint(time++); + } + + @Rule + public ExpectedException exception = ExpectedException.none(); + @Test + public void cantAddBeforeDenoting() throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception { + RaceColumn column = leaderboard.getRaceColumnByName(columnName); + exception.expect(NotDenotedForRaceLogTrackingException.class); + adapter.startTracking(service, leaderboard, column, fleet); + } + + private void testSize(Track track, int expected) { + track.lockForRead(); + assertEquals(expected, size(track.getRawFixes())); + track.unlockAfterRead(); + } + + @Test + public void canDenote_Add_Track() throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception { + + RaceColumn column = leaderboard.getRaceColumnByName(columnName); + RaceLog raceLog = column.getRaceLog(fleet); + + //can denote racelog for tracking + assertTrue(raceLog.isEmpty()); + adapter.denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, "race"); + assertFalse(raceLog.isEmpty()); + + //add a mapping and one fix in, one out of mapping + Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null); + DeviceIdentifier dev1 = new SmartphoneImeiIdentifier("dev1"); + raceLog.add(factory.createDeviceCompetitorMappingEvent(t(), author, dev1, comp1, 0, new MillisecondsTimePoint(0), new MillisecondsTimePoint(10))); + gpsFixStore.storeFix(dev1, new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(5), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(5)))); + gpsFixStore.storeFix(dev1, new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(15), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(5)))); + + raceLog.add(factory.createRegisterCompetitorEvent(t(), author, 0, comp1)); + + //start tracking + adapter.startTracking(service, leaderboard, column, fleet); + + //now there is a trackedrace + TrackedRace race = column.getTrackedRace(fleet); + assertNotNull(race); + + //one fix should have been loaded from store + testSize(race.getTrack(comp1), 1); + + //further fix arrives in race + gpsFixStore.storeFix(dev1, new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(7), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(5)))); + gpsFixStore.storeFix(dev1, new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(14), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(5)))); //outside mapping range + testSize(race.getTrack(comp1), 2); + + //add another mapping on the fly, other old fixes should be loaded + raceLog.add(factory.createDeviceCompetitorMappingEvent(t(), author, dev1, comp1, 0, new MillisecondsTimePoint(11), new MillisecondsTimePoint(20))); + testSize(race.getTrack(comp1), 4); + + //add another fix in new mapping range + gpsFixStore.storeFix(dev1, new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(18), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(5)))); + testSize(race.getTrack(comp1), 5); + + //stop tracking, then no more fixes arrive at race + service.getRaceTrackerById(raceLog.getId()).stop(); + gpsFixStore.storeFix(dev1, new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(8), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(5)))); + testSize(race.getTrack(comp1), 5); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/RecordFixesTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/RecordFixesTest.java new file mode 100644 index 00000000000..ee723302e75 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/RecordFixesTest.java @@ -0,0 +1,64 @@ +package com.sap.sailing.domain.racelogtracking.test.impl; + +import static com.sap.sse.common.Util.size; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.test.mock.MockDeviceAndSessioinIdentifierWithGPSFixesDeserializer; +import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; +import com.sap.sailing.domain.racelogtracking.servlet.RecordFixesPostServlet; +import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl; +import com.sap.sailing.server.gateway.deserialization.impl.DeviceAndSessionIdentifierWithGPSFixesDeserializer; +import com.sap.sse.common.Util; + +public class RecordFixesTest extends AbstractGPSFixStoreTest { + private RecordFixesPostServlet servlet; + private final DeviceAndSessionIdentifierWithGPSFixesDeserializer deserializer = + new MockDeviceAndSessioinIdentifierWithGPSFixesDeserializer(); + private final DeviceIdentifier device = new SmartphoneImeiIdentifier("a"); + + @Before + public void setupServlet() { + servlet = new RecordFixesPostServlet(); + servlet = spy(servlet); + when(servlet.getRequestDeserializer()).thenReturn(deserializer); + doReturn(service).when(servlet).getService(); + } + + @Test + public void areFixesStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException { + map(comp, device, 0, 600); + + int timepoint = 343; + List fixes = new ArrayList<>(); + fixes.add(createFix(timepoint, 0, 0, 0, 0)); + fixes.add(createFix(timepoint+1, 0, 0, 0, 0)); + Util.Triple> data = new Util.Triple<>(device, null, fixes); + servlet.process(null, data); + + DynamicGPSFixMovingTrackImpl track = new DynamicGPSFixMovingTrackImpl<>(comp, 0); + store.loadCompetitorTrack(track, raceLog, comp); + track.lockForRead(); + assertEquals(2, size(track.getRawFixes())); + assertEquals(timepoint, track.getFirstRawFix().getTimePoint().asMillis()); + assertTrue(track.getFirstRawFix() instanceof GPSFixMoving); + track.unlockAfterRead(); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java new file mode 100644 index 00000000000..da892e26fe7 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java @@ -0,0 +1,85 @@ +package com.sap.sailing.domain.racelogtracking.test.impl; + +import static junit.framework.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Ignore; +import org.junit.Test; + +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.DomainFactory; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.impl.TimeRangeImpl; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; +import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest; +import com.sap.sailing.domain.tracking.TrackedRegatta; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; +import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl; + +public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest { + @Test + @Ignore + public void areFixesStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException { + Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null); + Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2"); + DeviceIdentifier device2 = new SmartphoneImeiIdentifier("imei2"); + DeviceIdentifier device3 = new SmartphoneImeiIdentifier("imei3"); + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er"); + + Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] {new WaypointImpl(mark), new WaypointImpl(mark2)})); + RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] {comp, comp2})); + + map(comp, device, 0, 600); + map(comp2, device2, 0, 600); + //reuse device for two marks + map(mark, device3, 0, 600); + map(mark2, device3, 0, 600); + + store.storeFix(device, createFix(100, 10, 20, 30, 40)); + store.storeFix(device, createFix(200, 10, 20, 30, 40)); + store.storeFix(device2, createFix(100, 10, 20, 30, 40)); + store.storeFix(device3, createFix(100, 10, 20, 30, 40)); + store.storeFix(device3, createFix(100, 10, 20, 30, 40)); + + TrackedRegatta regatta = new TrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, "regatta", boatClass, null, null, "a", null)); + DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, Collections.emptyList(), + EmptyWindStore.INSTANCE, store, 0, 0, 0); + trackedRace.attachRaceLog(raceLog); + trackedRace.waitUntilLoadingFromGPSFixStoreComplete(); + + testLength(trackedRace.getTrack(comp), 2); + testLength(trackedRace.getTrack(comp2), 1); + testLength(trackedRace.getOrCreateTrack(mark), 1); + testLength(trackedRace.getOrCreateTrack(mark2), 1); + } + + @Test + public void metadataStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException { + assertEquals(0, store.getNumberOfFixes(device)); + assertEquals(null, store.getTimeRangeCoveredByFixes(device)); + + map(comp, device, 0, 600); + + store.storeFix(device, createFix(100, 10, 20, 30, 40)); + store.storeFix(device, createFix(200, 10, 20, 30, 40)); + + assertEquals(2, store.getNumberOfFixes(device)); + assertEquals(TimeRangeImpl.create(100, 200), store.getTimeRangeCoveredByFixes(device)); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/manual/PostFixes.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/manual/PostFixes.java new file mode 100644 index 00000000000..ba4030bb612 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/manual/PostFixes.java @@ -0,0 +1,107 @@ +package com.sap.sailing.domain.racelogtracking.test.manual; + +import java.io.IOException; +import java.io.Serializable; +import java.text.ParseException; +import java.util.Arrays; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +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.MillisecondsTimePoint; +import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; +import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiJsonHandler; +import com.sap.sailing.domain.racelogtracking.test.AbstractJsonOverHttpTest; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.impl.GPSFixImpl; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.server.gateway.serialization.impl.DeviceAndSessionIdentifierWithGPSFixesSerializer; +import com.sap.sailing.server.gateway.serialization.impl.DeviceIdentifierJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.GPSFixMovingNmeaDTOJsonSerializer; +import com.sap.sse.common.Util; + +public class PostFixes extends AbstractJsonOverHttpTest { + protected static final SmartphoneImeiIdentifier device = new SmartphoneImeiIdentifier("a"); + + private final DeviceAndSessionIdentifierWithGPSFixesSerializer fixSerializer = + new DeviceAndSessionIdentifierWithGPSFixesSerializer<>(DeviceIdentifierJsonSerializer.create( + new SmartphoneImeiJsonHandler(), SmartphoneImeiIdentifier.TYPE), new GPSFixMovingNmeaDTOJsonSerializer()); + + private void recordFix(GPSFixMoving... fix) throws IOException { + String request = fixSerializer.serialize( + new Util.Triple>( + device, null, Arrays.asList(fix))).toJSONString(); + + executeRequest("POST", getUrl(URL_TR + "/recordFixes"), request); + } + + private void recordFixes() { + final Timer timer = new Timer(); + final Position p1 = new DegreePosition(54.427119, 10.182583); + final Position p2 = new DegreePosition(54.435707, 10.200779); + final Position p3 = new DegreePosition(54.425921, 10.188248); + + final int nFirstLeg = 100; + final int nSecondLeg = 100; + final int n = nFirstLeg + nSecondLeg; + long period = 1000; + + timer.scheduleAtFixedRate(new TimerTask() { + int i = 0; + GPSFix lastFix; + + @Override + public void run() { + if (i++ > n) + timer.cancel(); + + Position start; + Position end; + int iLeg = i; + int nLeg; + + if (i < nFirstLeg) { + start = p1; + end = p2; + nLeg = nFirstLeg; + } else { + start = p2; + end = p3; + iLeg -= nFirstLeg; + nLeg = nSecondLeg; + } + + Distance distance = start.getDistance(end); + Bearing bearing = start.getBearingGreatCircle(end); + Position boat = start.translateGreatCircle(bearing, distance.scale(((double) iLeg) / nLeg)); + GPSFix fix = new GPSFixImpl(boat, MillisecondsTimePoint.now()); + SpeedWithBearing speed = lastFix == null ? new KnotSpeedWithBearingImpl(0, new DegreeBearingImpl(0)) + : lastFix.getSpeedAndBearingRequiredToReach(fix); + lastFix = fix; + + try { + recordFix(new GPSFixMovingImpl(fix.getPosition(), fix.getTimePoint(), speed)); + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 0, period); + } + + public void run() throws IOException, IllegalStateException, ParseException, InterruptedException { + recordFixes(); + } + + public static void main(String[] args) throws IllegalStateException, IOException, ParseException, + InterruptedException { + new PostFixes().run(); + } +} diff --git a/java/de.csenk.gwt.ws/.classpath b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.classpath old mode 100755 new mode 100644 similarity index 82% rename from java/de.csenk.gwt.ws/.classpath rename to java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.classpath index 8533223089a..4c62a8048e6 --- a/java/de.csenk.gwt.ws/.classpath +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.classpath @@ -1,8 +1,7 @@ - - + diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.project b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.project new file mode 100644 index 00000000000..f3c0beddc15 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.domain.racelogtrackingadapter.testsupport + + + + + + 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/de.csenk.gwt.ws/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.settings/org.eclipse.jdt.core.prefs old mode 100755 new mode 100644 similarity index 100% rename from java/de.csenk.gwt.ws/.settings/org.eclipse.jdt.core.prefs rename to java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.settings/org.eclipse.jdt.core.prefs diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..57124c65247 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/META-INF/MANIFEST.MF @@ -0,0 +1,20 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Testsupport +Bundle-SymbolicName: com.sap.sailing.domain.racelogtrackingadapter.testsupport +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Require-Bundle: com.sap.sailing.domain.shared.android, + com.sap.sailing.domain, + com.sap.sailing.domain.common, + com.mongodb.driver, + com.sap.sailing.domain.persistence, + com.sap.sailing.domain.racelogtrackingadapter, + com.sap.sailing.server.gateway.serialization;bundle-version="1.0.0", + org.json.simple;bundle-version="1.1.0", + org.mockito.mockito-core;bundle-version="1.9.5", + org.hamcrest;bundle-version="1.1.0", + org.junit4;bundle-version="4.8.2", + com.sap.sse.common +Export-Package: com.sap.sailing.domain.racelog.tracking.test.mock diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/build.properties b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/build.properties new file mode 100644 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/pom.xml b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/pom.xml new file mode 100644 index 00000000000..d7d10b95748 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.domain.racelogtrackingadapter.testsupport + eclipse-plugin + diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockDeviceAndSessioinIdentifierWithGPSFixesDeserializer.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockDeviceAndSessioinIdentifierWithGPSFixesDeserializer.java new file mode 100644 index 00000000000..8d2b930fbe6 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockDeviceAndSessioinIdentifierWithGPSFixesDeserializer.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.server.gateway.deserialization.impl.DeviceAndSessionIdentifierWithGPSFixesDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.DeviceIdentifierJsonDeserializer; + +public class MockDeviceAndSessioinIdentifierWithGPSFixesDeserializer extends +DeviceAndSessionIdentifierWithGPSFixesDeserializer { + + public MockDeviceAndSessioinIdentifierWithGPSFixesDeserializer() { + super(new MockGPSFixJsonServiceFinder(), + DeviceIdentifierJsonDeserializer.create(new SmartphoneImeiJsonHandler(), SmartphoneImeiIdentifier.TYPE)); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockEmptyServiceFinder.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockEmptyServiceFinder.java new file mode 100644 index 00000000000..a7ece4343d5 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockEmptyServiceFinder.java @@ -0,0 +1,19 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; + +public class MockEmptyServiceFinder implements TypeBasedServiceFinder { + private ServiceType fallback; + + @Override + public ServiceType findService(String type) throws NoCorrespondingServiceRegisteredException { + return fallback; + } + + @Override + public void setFallbackService(ServiceType fallback) { + this.fallback = fallback; + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockEmptyServiceFinderFactory.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockEmptyServiceFinderFactory.java new file mode 100644 index 00000000000..e2a149c1fdb --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockEmptyServiceFinderFactory.java @@ -0,0 +1,19 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; + +/** + * A simplified implementation of the {@link TypeBasedServiceFinder} interface that, when the device type + * {@link SmartphoneImeiIdentifier#TYPE} is requested, returns a specific handler that was passed to this object's + * constructor. + * + * @author Fredrik Teschke + * + */ +public class MockEmptyServiceFinderFactory implements TypeBasedServiceFinderFactory { + @Override + public TypeBasedServiceFinder createServiceFinder(Class clazz) { + return new MockEmptyServiceFinder(); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixJsonHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixJsonHandler.java new file mode 100644 index 00000000000..4fe20028382 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixJsonHandler.java @@ -0,0 +1,24 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.impl.GPSFixMovingJsonDeserializer; +import com.sap.sailing.server.gateway.serialization.impl.GPSFixMovingJsonSerializer; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.GPSFixJsonHandler; + +public class MockGPSFixJsonHandler implements GPSFixJsonHandler { + + @Override + public JSONObject transformForth(GPSFix object) { + return new GPSFixMovingJsonSerializer().serialize((GPSFixMoving) object); + } + + @Override + public GPSFix transformBack(JSONObject json) throws JsonDeserializationException { + return new GPSFixMovingJsonDeserializer().deserialize(json); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixJsonServiceFinder.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixJsonServiceFinder.java new file mode 100644 index 00000000000..c8a49b5cc75 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixJsonServiceFinder.java @@ -0,0 +1,21 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.server.gateway.deserialization.impl.GPSFixMovingJsonDeserializer; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.GPSFixJsonHandler; + +public class MockGPSFixJsonServiceFinder implements TypeBasedServiceFinder { + private final MockGPSFixJsonHandler handler = new MockGPSFixJsonHandler(); + + @Override + public GPSFixJsonHandler findService(String fixType) { + if (fixType.equals(GPSFixMovingJsonDeserializer.TYPE)) { + return handler; + } + return null; + } + + @Override + public void setFallbackService(GPSFixJsonHandler fallback) { + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixMongoServiceFinder.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixMongoServiceFinder.java new file mode 100644 index 00000000000..6fba478c88e --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixMongoServiceFinder.java @@ -0,0 +1,26 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl; +import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl; +import com.sap.sailing.domain.persistence.racelog.tracking.GPSFixMongoHandler; +import com.sap.sailing.domain.persistence.racelog.tracking.impl.GPSFixMongoHandlerImpl; +import com.sap.sailing.domain.persistence.racelog.tracking.impl.GPSFixMovingMongoHandlerImpl; +import com.sap.sailing.domain.tracking.impl.GPSFixImpl; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; + +public class MockGPSFixMongoServiceFinder implements TypeBasedServiceFinder { + private final MongoObjectFactoryImpl mof = new MongoObjectFactoryImpl(null); + private final DomainObjectFactoryImpl dof = new DomainObjectFactoryImpl(null, null); + + @Override + public GPSFixMongoHandler findService(String fixType) { + if (fixType.equals(GPSFixMovingImpl.class.getName())) return new GPSFixMovingMongoHandlerImpl(mof, dof); + if (fixType.equals(GPSFixImpl.class.getName())) return new GPSFixMongoHandlerImpl(mof, dof); + return null; + } + + @Override + public void setFallbackService(GPSFixMongoHandler fallback) { + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockSmartphoneImeiJsonServiceFinder.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockSmartphoneImeiJsonServiceFinder.java new file mode 100644 index 00000000000..2a9622b0b52 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockSmartphoneImeiJsonServiceFinder.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; + +/** + * A simplified implementation of the {@link TypeBasedServiceFinder} interface that, when the device type + * {@link SmartphoneImeiIdentifier#TYPE} is requested, returns a specific handler that was passed to this object's + * constructor. + * + * @author Fredrik Teschke + * + */ +public class MockSmartphoneImeiJsonServiceFinder implements TypeBasedServiceFinder { + private final SmartphoneImeiJsonHandler handler = new SmartphoneImeiJsonHandler(); + private DeviceIdentifierJsonHandler fallback; + + @Override + public DeviceIdentifierJsonHandler findService(String deviceType) { + if (deviceType.equals(SmartphoneImeiIdentifier.TYPE)) return handler; + return fallback; + } + + @Override + public void setFallbackService(DeviceIdentifierJsonHandler fallback) { + this.fallback = fallback; + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockSmartphoneImeiMongoServiceFinder.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockSmartphoneImeiMongoServiceFinder.java new file mode 100644 index 00000000000..a1353e1813d --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockSmartphoneImeiMongoServiceFinder.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; + +/** + * A simplified implementation of the {@link TypeBasedServiceFinder} interface that, when the device type + * {@link SmartphoneImeiIdentifier#TYPE} is requested, returns a specific handler that was passed to this object's + * constructor. + * + * @author Fredrik Teschke + * + */ +public class MockSmartphoneImeiMongoServiceFinder implements TypeBasedServiceFinder { + private final SmartphoneImeiMongoHandler handler = new SmartphoneImeiMongoHandler(); + private DeviceIdentifierMongoHandler fallback; + + @Override + public DeviceIdentifierMongoHandler findService(String deviceType) { + if (deviceType.equals(SmartphoneImeiIdentifier.TYPE)) return handler; + return fallback; + } + + @Override + public void setFallbackService(DeviceIdentifierMongoHandler fallback) { + this.fallback = fallback; + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockSmartphoneImeiServiceFinderFactory.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockSmartphoneImeiServiceFinderFactory.java new file mode 100644 index 00000000000..0a7c9ef35ab --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockSmartphoneImeiServiceFinderFactory.java @@ -0,0 +1,36 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import java.util.HashMap; +import java.util.Map; + +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.persistence.racelog.tracking.GPSFixMongoHandler; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.GPSFixJsonHandler; + +/** + * A simplified implementation of the {@link TypeBasedServiceFinder} interface that, when the device type + * {@link SmartphoneImeiIdentifier#TYPE} is requested, returns a specific handler that was passed to this object's + * constructor. + * + * @author Fredrik Teschke + * + */ +public class MockSmartphoneImeiServiceFinderFactory implements TypeBasedServiceFinderFactory { + Map, TypeBasedServiceFinder> serviceFinders = new HashMap, TypeBasedServiceFinder>(); + + public MockSmartphoneImeiServiceFinderFactory() { + serviceFinders.put(DeviceIdentifierMongoHandler.class, new MockSmartphoneImeiMongoServiceFinder()); + serviceFinders.put(DeviceIdentifierJsonHandler.class, new MockSmartphoneImeiJsonServiceFinder()); + serviceFinders.put(GPSFixMongoHandler.class, new MockGPSFixMongoServiceFinder()); + serviceFinders.put(GPSFixJsonHandler.class, new MockGPSFixJsonServiceFinder()); + } + + @SuppressWarnings("unchecked") + @Override + public TypeBasedServiceFinder createServiceFinder(Class clazz) { + return (TypeBasedServiceFinder) serviceFinders.get(clazz); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiIdentifier.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiIdentifier.java new file mode 100644 index 00000000000..b4a50799206 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiIdentifier.java @@ -0,0 +1,48 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; + + +public class SmartphoneImeiIdentifier implements DeviceIdentifier { + private static final long serialVersionUID = -1830014229310181702L; + + public static final String TYPE = "smartphoneImei"; + + private final String imei; + + public SmartphoneImeiIdentifier(String imei) { + this.imei = imei; + } + + public String getImei() { + return imei; + } + + @Override + public String getIdentifierType() { + return TYPE; + } + + @Override + public String toString() { + return "IMEI " + imei; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SmartphoneImeiIdentifier) { + return ((SmartphoneImeiIdentifier) obj).imei.equals(imei); + } + return false; + } + + @Override + public int hashCode() { + return imei.hashCode(); + } + + @Override + public String getStringRepresentation() { + return imei; + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiJsonHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiJsonHandler.java new file mode 100644 index 00000000000..2bb12bbfd3b --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiJsonHandler.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; + +public class SmartphoneImeiJsonHandler extends SmartphoneImeiSerializationHandler +implements DeviceIdentifierJsonHandler { + + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + return deserialize((String) serialized, type, stringRepresentation); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiMongoHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiMongoHandler.java new file mode 100644 index 00000000000..39c94d2fac2 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiMongoHandler.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; + +public class SmartphoneImeiMongoHandler extends SmartphoneImeiSerializationHandler +implements DeviceIdentifierMongoHandler { + + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + return deserialize((String) serialized, type, stringRepresentation); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiSerializationHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiSerializationHandler.java new file mode 100644 index 00000000000..1f5e4485067 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiSerializationHandler.java @@ -0,0 +1,21 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sse.common.Util; + +public class SmartphoneImeiSerializationHandler { + private SmartphoneImeiIdentifier castIdentifier(DeviceIdentifier identifier) throws TransformationException { + if (!(identifier instanceof SmartphoneImeiIdentifier)) + throw new TransformationException("Expected a SmartphoneImeiIdentifier, got instead: " + identifier); + return (SmartphoneImeiIdentifier) identifier; + } + + public Util.Pair serialize(DeviceIdentifier deviceIdentifier) throws TransformationException { + return new Util.Pair(SmartphoneImeiIdentifier.TYPE, castIdentifier(deviceIdentifier).getImei()); + } + + public DeviceIdentifier deserialize(String input, String type, String stringRep) throws TransformationException { + return new SmartphoneImeiIdentifier(stringRep); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiStringSerializationHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiStringSerializationHandler.java new file mode 100644 index 00000000000..c6fcc9c94e6 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiStringSerializationHandler.java @@ -0,0 +1,8 @@ +package com.sap.sailing.domain.racelog.tracking.test.mock; + +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifierStringSerializationHandler; + +public class SmartphoneImeiStringSerializationHandler extends SmartphoneImeiSerializationHandler +implements DeviceIdentifierStringSerializationHandler { + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath b/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath new file mode 100644 index 00000000000..e8ea977a694 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.gitignore b/java/com.sap.sailing.domain.racelogtrackingadapter/.gitignore new file mode 100644 index 00000000000..ba077a4031a --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.gitignore @@ -0,0 +1 @@ +bin diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.project b/java/com.sap.sailing.domain.racelogtrackingadapter/.project new file mode 100644 index 00000000000..6d8ea47c43e --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.domain.racelogtrackingadapter + + + + + + 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.domain.racelogtrackingadapter/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..9e859eb5b06 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Thu Jan 19 09:51:40 CET 2012 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 00000000000..eb2d1c6a2b2 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +#Thu Aug 25 16:46:04 CEST 2011 +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..e45e64c6877 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF @@ -0,0 +1,28 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: RaceLog Tracking Adapter +Bundle-SymbolicName: com.sap.sailing.domain.racelogtrackingadapter +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-ActivationPolicy: lazy +Require-Bundle: com.sap.sailing.domain, + com.sap.sailing.domain.common, + com.sap.sailing.domain.shared.android, + com.sap.sailing.server.gateway.serialization;bundle-version="1.0.0", + com.sap.sailing.domain.persistence;bundle-version="1.0.0", + com.sap.sailing.server.gateway;bundle-version="1.0.0", + com.sap.sailing.server;bundle-version="1.0.0", + com.googlecode.java-diff-utils;bundle-version="1.3.0", + com.mongodb.driver, + com.sap.sse.common +Import-Package: com.mongodb, + javax.servlet;version="2.6.0", + org.json.simple, + org.json.simple.parser, + org.osgi.framework;version="1.6.0" +Export-Package: com.sap.sailing.domain.racelogtracking, + com.sap.sailing.domain.racelogtracking.impl;x-friends:="com.sap.sailing.domain.racelogtrackingadapter.testsupport,com.sap.sailing.gwt.ui.test", + com.sap.sailing.domain.racelogtracking.servlet;x-friends:="com.sap.sailing.domain.racelogtrackingadapter.test" +Web-ContextPath: /tracking +Bundle-Activator: com.sap.sailing.domain.racelogtracking.impl.Activator diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/WEB-INF/web.xml b/java/com.sap.sailing.domain.racelogtrackingadapter/WEB-INF/web.xml new file mode 100644 index 00000000000..7f11b027765 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/WEB-INF/web.xml @@ -0,0 +1,17 @@ + + + + + RecordFixesPostServlet + com.sap.sailing.domain.racelogtracking.servlet.RecordFixesPostServlet + + + RecordFixesPostServlet + /recordFixes + + + diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties new file mode 100644 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/pom.xml b/java/com.sap.sailing.domain.racelogtrackingadapter/pom.xml new file mode 100644 index 00000000000..d51b52b606d --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.domain.racelogtrackingadapter + eclipse-plugin + diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifier.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifier.java new file mode 100644 index 00000000000..cbedce015fd --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifier.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.racelogtracking; + +import java.util.UUID; + +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; + +/** + * A device identifier used to identify a non-existent, virtual device, which is used only once as an + * identifier to add a single fix to a track (i.e. pinging the location of that track's item). + * + * @author Fredrik Teschke + * + */ +public interface PingDeviceIdentifier extends DeviceIdentifier { + public static final String TYPE = "PING"; + UUID getId(); +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifierImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifierImpl.java new file mode 100644 index 00000000000..d08d7f72ff8 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifierImpl.java @@ -0,0 +1,46 @@ +package com.sap.sailing.domain.racelogtracking; + +import java.util.UUID; + +public class PingDeviceIdentifierImpl implements PingDeviceIdentifier { + private static final long serialVersionUID = -4049961972156611640L; + + + private final UUID id; + + public PingDeviceIdentifierImpl() { + this.id = UUID.randomUUID(); + } + + public PingDeviceIdentifierImpl(UUID id) { + this.id = id; + } + + @Override + public String getIdentifierType() { + return TYPE; + } + + @Override + public UUID getId() { + return id; + } + + @Override + public String getStringRepresentation() { + return id.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PingDeviceIdentifier) { + return id.equals(((PingDeviceIdentifier) obj).getId()); + } + return false; + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java new file mode 100644 index 00000000000..40bbd2c82dc --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java @@ -0,0 +1,93 @@ +package com.sap.sailing.domain.racelogtracking; + +import java.util.Set; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.ControlPoint; +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.SharedDomainFactory; +import com.sap.sailing.domain.common.racelog.tracking.NotDenotableForRaceLogTrackingException; +import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; +import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; +import com.sap.sailing.domain.racelogtracking.impl.RaceLogRaceTracker; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.RacingEventService; + +public interface RaceLogTrackingAdapter { + /** + * Performs the necessary steps to ensure that the race is tracked (aka that a {@link TrackedRace} + * is created from the data in this {@code RaceLog}).

+ * The following steps are performed to achieve this: + *

    + *
  • Is the racelog denoted for tracking? If not, throw exception.
  • + *
  • Is a {@link RaceLogRaceTracker} already listening for this racelog? If not, add one.
  • + *
  • Is a {@link StartTrackingEvent} present in the racelog? If not, add one
  • + *
+ */ + void startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet) + throws NotDenotedForRaceLogTrackingException, Exception; + + RaceLogTrackingState getRaceLogTrackingState(RacingEventService service, RaceColumn raceColumn, Fleet fleet); + + /** + * Is a {@link RaceLogRaceTracker} already listening on this {@code raceLog}? + */ + boolean isRaceLogRaceTrackerAttached(RacingEventService service, RaceLog raceLog); + + /** + * Denotes the {@link RaceLog} for racelog-tracking, by inserting a {@link DenoteForTrackingEvent}. + * + * @throws NotDenotableForRaceLogTrackingException + * Fails, if no {@link RaceLog}, or a non-empty {@link RaceLog}, or one with attached + * {@link TrackedRace} is found already in place. + * Also fails, if the {@code leaderboard} is not a {@link RegattaLeaderboard}. + */ + void denoteRaceForRaceLogTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, + Fleet fleet, String raceName) throws NotDenotableForRaceLogTrackingException; + + /** + * Revoke the {@link DenoteForTrackingEvent}, and if it exists the {@link StartTrackingEvent}. + * This does not affect existing an {@link RaceLogRaceTracker} + * or {@link TrackedRace} for this {@code RaceLog}. + */ + void removeDenotationForRaceLogTracking(RacingEventService service, RaceLog raceLog); + + /** + * Denotes the entire {@link Leaderboard} for racelog-tracking, by calling the + * {@link #denoteRaceForRaceLogTracking(RacingEventService, Leaderboard, RaceColumn, Fleet, String)} method for each + * {@link RaceLog}. + */ + void denoteAllRacesForRaceLogTracking(RacingEventService service, Leaderboard leaderboard) + throws NotDenotableForRaceLogTrackingException; + + /** + * Add a fix to the {@link GPSFixStore}, and create a mapping with a virtual device for exactly that timepoint. + */ + void pingMark(RaceLog raceLogToAddTo, Mark mark, GPSFix gpsFix, RacingEventService service); + + /** + * Duplicate the course and competitor registrations in the newest {@link RaceLogCourseDesignChangedEvent} + * in {@code from} race log to the {@code to} race logs. + * The {@link Mark}s and {@link ControlPoint}s are duplicated and not reused. This also inserts the necessary + * {@link DefineMarkEvent}s into the {@code to} race logs. + */ + void copyCourseAndCompetitors(RaceLog from, Set to, SharedDomainFactory baseDomainFactory, + RacingEventService service); + + /** + * If not yet registered, register the competitors in {@code competitors}, + * and unregister all already registered competitors not in {@code competitors}. + */ + void registerCompetitors(RacingEventService service, RaceLog raceLog, Set competitors); +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapterFactory.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapterFactory.java new file mode 100644 index 00000000000..34b223fc676 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapterFactory.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.racelogtracking; + +import com.sap.sailing.domain.base.DomainFactory; + +public interface RaceLogTrackingAdapterFactory { + RaceLogTrackingAdapter getAdapter(DomainFactory baseDomainFactory); +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java new file mode 100644 index 00000000000..5433fcf2a5b --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java @@ -0,0 +1,83 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import java.util.Dictionary; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifierStringSerializationHandler; +import com.sap.sailing.domain.racelog.tracking.SmartphoneUUIDIdentifier; +import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifierImpl; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.GPSFixJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.GPSFixMovingJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.GPSFixMovingNmeaDTOJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.GPSFixNmeaDTOJsonDeserializer; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.GPSFixJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.GPSFixMovingJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.GPSFixMovingNmeaDTOJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.GPSFixNmeaDTOJsonSerializer; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.GPSFixJsonHandler; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.impl.GPSFixJsonHandlerImpl; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.impl.SmartphoneUUIDJsonHandler; + +public class Activator implements BundleActivator { + private static final Logger logger = Logger.getLogger(Activator.class.getName()); + + private Set> registrations = new HashSet<>(); + + private Dictionary getDict(String type) { + Dictionary properties = new Hashtable(); + properties.put(TypeBasedServiceFinder.TYPE, type); + return properties; + } + + private void registerGPSFixJsonService(BundleContext context, JsonDeserializer deserializer, + JsonSerializer serializer, String type) { + registrations.add(context.registerService(GPSFixJsonHandler.class, + new GPSFixJsonHandlerImpl(deserializer, serializer), getDict(type))); + } + + @Override + public void start(BundleContext context) throws Exception { + registrations.add(context.registerService(DeviceIdentifierMongoHandler.class, new SmartphoneUUIDMongoHandler(), getDict(SmartphoneUUIDIdentifier.TYPE))); + registrations.add(context.registerService(DeviceIdentifierJsonHandler.class, new SmartphoneUUIDJsonHandler(), getDict(SmartphoneUUIDIdentifier.TYPE))); + registrations.add(context.registerService(DeviceIdentifierStringSerializationHandler.class, new SmartphoneUUIDStringSerializationHandler(), getDict(SmartphoneUUIDIdentifier.TYPE))); + + registrations.add(context.registerService(DeviceIdentifierMongoHandler.class, new PingDeviceIdentifierMongoHandler(), getDict(PingDeviceIdentifierImpl.TYPE))); + registrations.add(context.registerService(DeviceIdentifierJsonHandler.class, new PingDeviceIdentifierJsonHandler(), getDict(PingDeviceIdentifierImpl.TYPE))); + + registrations.add(context.registerService(DeviceIdentifierMongoHandler.class, new TrackFileImportDeviceIdentifierMongoHandler(), getDict(TrackFileImportDeviceIdentifier.TYPE))); + registrations.add(context.registerService(DeviceIdentifierJsonHandler.class, new TrackFileImportDeviceIdentifierJsonHandler(), getDict(TrackFileImportDeviceIdentifier.TYPE))); + registrations.add(context.registerService(DeviceIdentifierStringSerializationHandler.class, new TrackFileImportDeviceIdentifierStringSerializationHandler(), getDict(TrackFileImportDeviceIdentifier.TYPE))); + + registerGPSFixJsonService(context, new GPSFixJsonDeserializer(), new GPSFixJsonSerializer(), GPSFixJsonDeserializer.TYPE); + registerGPSFixJsonService(context, new GPSFixMovingJsonDeserializer(), new GPSFixMovingJsonSerializer(), GPSFixMovingJsonDeserializer.TYPE); + registerGPSFixJsonService(context, new GPSFixNmeaDTOJsonDeserializer(), new GPSFixNmeaDTOJsonSerializer(), GPSFixNmeaDTOJsonDeserializer.TYPE); + registerGPSFixJsonService(context, new GPSFixMovingNmeaDTOJsonDeserializer(), new GPSFixMovingNmeaDTOJsonSerializer(), GPSFixMovingNmeaDTOJsonDeserializer.TYPE); + + registrations.add(context.registerService(RaceLogTrackingAdapterFactory.class, RaceLogTrackingAdapterFactoryImpl.INSTANCE, null)); + logger.log(Level.INFO, "Started "+context.getBundle().getSymbolicName()); + } + + @Override + public void stop(BundleContext context) throws Exception { + for (ServiceRegistration reg : registrations) { + reg.unregister(); + } + registrations.clear(); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierJsonHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierJsonHandler.java new file mode 100644 index 00000000000..aa1e690e85a --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierJsonHandler.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; + +public class PingDeviceIdentifierJsonHandler extends PingDeviceIdentifierSerializationHandler +implements DeviceIdentifierJsonHandler { + + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + return deserialize((String) serialized, type, stringRepresentation); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierMongoHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierMongoHandler.java new file mode 100644 index 00000000000..2a95aa119f7 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierMongoHandler.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; + +public class PingDeviceIdentifierMongoHandler extends PingDeviceIdentifierSerializationHandler +implements DeviceIdentifierMongoHandler { + + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + return deserialize((String) serialized, type, stringRepresentation); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierSerializationHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierSerializationHandler.java new file mode 100644 index 00000000000..e3049cacde8 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierSerializationHandler.java @@ -0,0 +1,29 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import java.util.UUID; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifier; +import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifierImpl; +import com.sap.sse.common.Util; + +public class PingDeviceIdentifierSerializationHandler { + private PingDeviceIdentifier castIdentifier(DeviceIdentifier identifier) throws TransformationException { + if (!(identifier instanceof PingDeviceIdentifier)) + throw new TransformationException("Expected a PingDeviceIdentifier, got instead: " + identifier); + return (PingDeviceIdentifier) identifier; + } + + public Util.Pair serialize(DeviceIdentifier deviceIdentifier) throws TransformationException { + return new Util.Pair(PingDeviceIdentifier.TYPE, castIdentifier(deviceIdentifier).getId().toString()); + } + + public DeviceIdentifier deserialize(String input, String type, String stringRep) throws TransformationException { + try { + return new PingDeviceIdentifierImpl(UUID.fromString(stringRep)); + } catch (IllegalArgumentException e) { + throw new TransformationException(e); + } + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierStringSerializationHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierStringSerializationHandler.java new file mode 100644 index 00000000000..b039e9c656c --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierStringSerializationHandler.java @@ -0,0 +1,8 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifierStringSerializationHandler; + +public class PingDeviceIdentifierStringSerializationHandler extends PingDeviceIdentifierSerializationHandler +implements DeviceIdentifierStringSerializationHandler { + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogConnectivityParams.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogConnectivityParams.java new file mode 100644 index 00000000000..fb4e6128a5f --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogConnectivityParams.java @@ -0,0 +1,101 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import java.util.UUID; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.DomainFactory; +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.racelog.tracking.RaceNotCreatedException; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.RaceInformationFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.RaceLogTrackingStateAnalyzer; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.RaceTracker; +import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sailing.server.RacingEventService; + +public class RaceLogConnectivityParams implements RaceTrackingConnectivityParameters { + private final RacingEventService service; + private final RaceColumn raceColumn; + private final Fleet fleet; + private final Leaderboard leaderboard; + private final long delayToLiveInMillis; + private final Regatta regatta; + private final DomainFactory domainFactory; + + public RaceLogConnectivityParams(RacingEventService service, Regatta regatta, RaceColumn raceColumn, Fleet fleet, + Leaderboard leaderboard, long delayToLiveInMillis, DomainFactory domainFactory) throws RaceNotCreatedException { + this.service = service; + this.regatta = regatta; + this.raceColumn = raceColumn; + this.fleet = fleet; + this.leaderboard = leaderboard; + this.delayToLiveInMillis = delayToLiveInMillis; + this.domainFactory = domainFactory; + + if (!new RaceLogTrackingStateAnalyzer(getRaceLog()).analyze().isForTracking()) { + throw new RaceNotCreatedException(String.format("Racelog (%s) is not denoted for tracking", getRaceLog())); + } + } + + @Override + public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, + GPSFixStore gpsFixStore) { + return createRaceTracker(regatta, trackedRegattaRegistry, windStore, gpsFixStore); + } + + @Override + public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, + WindStore windStore, GPSFixStore gpsFixStore) { + if (regatta == null) { + BoatClass boatClass = new RaceInformationFinder(getRaceLog()).analyze().getBoatClass(); + regatta = service.getOrCreateDefaultRegatta("RaceLog-tracking default Regatta", boatClass.getName(), + UUID.randomUUID()); + } + if (regatta == null) { + throw new RaceNotCreatedException("No regatta for race-log tracked race"); + } + DynamicTrackedRegatta trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(regatta); + return new RaceLogRaceTracker(trackedRegatta, this, windStore, gpsFixStore); + } + + @Override + public Object getTrackerID() { + return getRaceLog().getId(); + } + + @Override + public long getDelayToLiveInMillis() { + return delayToLiveInMillis; + } + + public RaceLog getRaceLog() { + return raceColumn.getRaceLog(fleet); + } + + public RaceColumn getRaceColumn() { + return raceColumn; + } + + public Fleet getFleet() { + return fleet; + } + + public Leaderboard getLeaderboard() { + return leaderboard; + } + + public RacingEventService getService() { + return service; + } + + public DomainFactory getDomainFactory() { + return domainFactory; + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java new file mode 100755 index 00000000000..e7129870952 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java @@ -0,0 +1,451 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.ControlPoint; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.CourseBase; +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.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.CourseDataImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.common.PassingInstruction; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; +import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; +import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; +import com.sap.sailing.domain.common.racelog.tracking.RaceNotCreatedException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.LastPublishedCourseDesignFinder; +import com.sap.sailing.domain.racelog.impl.BaseRaceLogEventVisitor; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMapping; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.GPSFixReceivedListener; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceCompetitorMappingFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceMappingFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceMarkMappingFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.RaceInformationFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.RaceLogTrackingStateAnalyzer; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.RegisteredCompetitorsAnalyzer; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.RaceTracker; +import com.sap.sailing.domain.tracking.RacesHandle; +import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; +import com.sap.sse.common.Util; + +import difflib.PatchFailedException; + +/** + * Track a race using the data defined in the {@link RaceLog}. If the events suggest that the race is already in the + * {@link RaceLogTrackingState#TRACKING} state, tracking commences immediately and existing fixes are loaded immediately + * from the database. + *

+ * Otherwise, the tracker waits until a {@link StartTrackingEvent} is received to perform these tasks. + * + * @author Fredrik Teschke + * + */ +public class RaceLogRaceTracker extends BaseRaceLogEventVisitor implements RaceTracker, GPSFixReceivedListener { + private final RaceLogConnectivityParams params; + private final WindStore windStore; + private final GPSFixStore gpsFixStore; + private final DynamicTrackedRegatta regatta; + + private Map>> competitorMappings = new HashMap<>(); + private Map>> markMappings = new HashMap<>(); + + private Map>> markMappingsByDevices = new HashMap<>(); + private Map>> competitorMappingsByDevices = new HashMap<>(); + + private DynamicTrackedRace trackedRace; + + private static final Logger logger = Logger.getLogger(RaceLogRaceTracker.class.getName()); + + public RaceLogRaceTracker(DynamicTrackedRegatta regatta, RaceLogConnectivityParams params, WindStore windStore, + GPSFixStore gpsFixStore) { + this.params = params; + this.windStore = windStore; + this.gpsFixStore = gpsFixStore; + this.regatta = regatta; + + // add a listener on the racelog, to be informed about relevant events + params.getRaceLog().addListener(this); + + logger.info(String.format("Created race-log tracker for: %s %s %s", params.getLeaderboard(), + params.getRaceColumn(), params.getFleet())); + + // load race for which tracking already started + if (new RaceLogTrackingStateAnalyzer(params.getRaceLog()).analyze() == RaceLogTrackingState.TRACKING) { + startTracking(null); + } + } + + @Override + public void stop() { + // mark passing calculator is automatically stopped, when the race status is set to {@link TrackedRaceStatusEnum#FINISHED} + trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, 100)); + + // remove listener on racelog + params.getRaceLog().removeListener(this); + + // remove listener for fixes + gpsFixStore.removeListener(this); + + logger.info(String.format("Stopped tracking race-log race %s %s %s", params.getLeaderboard(), + params.getRaceColumn(), params.getFleet())); + } + + @Override + public Regatta getRegatta() { + return regatta.getRegatta(); + } + + @Override + public Set getRaces() { + return trackedRace == null ? null : Collections.singleton(trackedRace.getRace()); + } + + @Override + public Set getRaceIdentifiers() { + return trackedRace == null ? null : Collections.singleton(trackedRace.getRaceIdentifier()); + } + + @Override + public RacesHandle getRacesHandle() { + return new RaceLogRacesHandle(this); + } + + @Override + public DynamicTrackedRegatta getTrackedRegatta() { + return regatta; + } + + @Override + public WindStore getWindStore() { + return windStore; + } + + @Override + public GPSFixStore getGPSFixStore() { + return gpsFixStore; + } + + @Override + public Object getID() { + return params.getRaceLog().getId(); + } + + private Map>> getNewCompetitorMappings() { + return new DeviceCompetitorMappingFinder(params.getRaceLog()).analyze(); + } + + private Map>> getNewMarkMappings() { + return new DeviceMarkMappingFinder(params.getRaceLog()).analyze(); + } + + /** + * Use mapping time ranges to set time start and end of tracking time for race + */ + private void updateStartAndEndOfTracking() { + TimePoint earliestMappingStart = new MillisecondsTimePoint(Long.MAX_VALUE); + TimePoint latestMappingEnd = new MillisecondsTimePoint(Long.MIN_VALUE); + + synchronized (competitorMappings) { + for (List> list : competitorMappings.values()) { + for (DeviceMapping mapping : list) { + if (mapping.getTimeRange().from().before(earliestMappingStart)) { + earliestMappingStart = mapping.getTimeRange().from(); + } + if (mapping.getTimeRange().to().after(latestMappingEnd)) { + latestMappingEnd = mapping.getTimeRange().to(); + } + } + } + } + + synchronized (markMappings) { + for (List> list : markMappings.values()) { + for (DeviceMapping mapping : list) { + if (mapping.getTimeRange().from().before(earliestMappingStart)) { + earliestMappingStart = mapping.getTimeRange().from(); + } + if (mapping.getTimeRange().to().after(latestMappingEnd)) { + latestMappingEnd = mapping.getTimeRange().to(); + } + } + } + } + + trackedRace.setStartOfTrackingReceived(latestMappingEnd); + } + + private boolean hasMappingAlreadyBeenLoaded( + DeviceMapping newMapping, List> oldMappings) { + if (oldMappings == null) { + return false; + } + for (DeviceMapping oldMapping : oldMappings) { + if (newMapping.getDevice() == oldMapping.getDevice() + && newMapping.getTimeRange().liesWithin(oldMapping.getTimeRange())) { + return true; + } + } + return false; + } + + private Map>> transformToMappingsByDevice( + Map>> mappings) { + + Map>> result = new HashMap<>(); + for (ItemT item : mappings.keySet()) { + for (DeviceMapping mapping : mappings.get(item)) { + List> list = result.get(mapping.getDevice()); + if (list == null) { + list = new ArrayList<>(); + result.put(mapping.getDevice(), list); + } + list.add(mapping); + } + } + + return result; + } + + private void updateMarkMappings(boolean loadIfNotCovered) { + // TODO remove fixes, if mappings have been removed + + synchronized (markMappings) { + // check if there are new time ranges not covered so far + Map>> newMappings = getNewMarkMappings(); + + if (loadIfNotCovered) { + for (Mark mark : newMappings.keySet()) { + DynamicGPSFixTrack track = trackedRace.getOrCreateTrack(mark); + List> oldMappings = markMappings.get(mark); + + if (oldMappings != null) { + for (DeviceMapping newMapping : newMappings.get(mark)) { + if (!hasMappingAlreadyBeenLoaded(newMapping, oldMappings)) { + try { + gpsFixStore.loadMarkTrack(track, newMapping); + } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { + logger.log(Level.WARNING, "Could not load mark track " + newMapping.getMappedTo()); + } + } + } + } + } + } + + markMappings.clear(); + markMappings.putAll(newMappings); + + markMappingsByDevices.clear(); + markMappingsByDevices.putAll(transformToMappingsByDevice(markMappings)); + } + + updateStartAndEndOfTracking(); + } + + @Override + public void visit(DeviceMarkMappingEvent event) { + if (trackedRace != null) { + updateMarkMappings(true); + gpsFixStore.addListener(this, event.getDevice()); + } + } + + private void updateCompetitorMappings(boolean loadIfNotCovered) { + // TODO remove fixes, if mappings have been removed + + synchronized (competitorMappings) { + // check if there are new time ranges not covered so far + Map>> newMappings = getNewCompetitorMappings(); + + if (loadIfNotCovered) { + for (Competitor competitor : newMappings.keySet()) { + DynamicGPSFixTrack track = trackedRace.getTrack(competitor); + List> oldMappings = competitorMappings.get(competitor); + + for (DeviceMapping newMapping : newMappings.get(competitor)) { + if (!hasMappingAlreadyBeenLoaded(newMapping, oldMappings)) { + try { + gpsFixStore.loadCompetitorTrack(track, newMapping); + } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { + logger.log(Level.WARNING, "Could not load competitor track " + newMapping.getMappedTo()); + } + } + } + } + } + + competitorMappings.clear(); + competitorMappings.putAll(newMappings); + + competitorMappingsByDevices.clear(); + competitorMappingsByDevices.putAll(transformToMappingsByDevice(competitorMappings)); + } + + updateStartAndEndOfTracking(); + } + + @Override + public void visit(DeviceCompetitorMappingEvent event) { + if (trackedRace != null) { + updateCompetitorMappings(true); + gpsFixStore.addListener(this, event.getDevice()); + } + } + + @Override + public void visit(StartTrackingEvent event) { + if (trackedRace == null) + startTracking(event); + } + + @Override + public void visit(RaceLogCourseDesignChangedEvent event) { + if (trackedRace == null) return; + + CourseBase base = new LastPublishedCourseDesignFinder(params.getRaceLog()).analyze(); + List> update = new ArrayList<>(); + + for (Waypoint waypoint : base.getWaypoints()) { + update.add(new Util.Pair<>(waypoint.getControlPoint(), waypoint.getPassingInstructions())); + } + + try { + trackedRace.getRace().getCourse().update(update, params.getDomainFactory()); + } catch (PatchFailedException e) { + logger.log(Level.WARNING, "Could not update course for race " + trackedRace.getRace().getName()); + } + } + + private void startTracking(StartTrackingEvent event) { + RaceLog raceLog = params.getRaceLog(); + RaceColumn raceColumn = params.getRaceColumn(); + Fleet fleet = params.getFleet(); + + DenoteForTrackingEvent denoteEvent = new RaceInformationFinder(raceLog).analyze(); + BoatClass boatClass = denoteEvent.getBoatClass(); + String raceName = denoteEvent.getRaceName(); + CourseBase courseBase = new LastPublishedCourseDesignFinder(raceLog).analyze(); + if (courseBase == null) { + courseBase = new CourseDataImpl("Default course for " + raceName); + logger.log(Level.FINE, "Using empty course in creation of race " + raceName); + } + + Course course = new CourseImpl(raceName + " course", courseBase.getWaypoints()); + + if (raceColumn.getTrackedRace(fleet) != null) { + try { + raceLog.revokeEvent(params.getService().getServerAuthor(), event); + } catch (NotRevokableException e) {} + throw new RaceNotCreatedException(String.format("Race for racelog (%s) has already been created", raceLog)); + } + + Iterable competitors = new RegisteredCompetitorsAnalyzer(raceLog).analyze(); + Serializable raceId = denoteEvent.getRaceId(); + final RaceDefinition raceDef = new RaceDefinitionImpl(raceName, course, boatClass, competitors, raceId); + + Iterable sidelines = Collections. emptyList(); + + // set race definition, so race is linked to leaderboard automatically + regatta.getRegatta().addRace(raceDef); + raceColumn.setRaceIdentifier(fleet, regatta.getRegatta().getRaceIdentifier(raceDef)); + + trackedRace = regatta.createTrackedRace(raceDef, sidelines, windStore, gpsFixStore, + params.getDelayToLiveInMillis(), WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, + boatClass.getApproximateManeuverDurationInMilliseconds(), null); + + trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, 0)); + + //add listeners for devices in mappings + for (List> mappings : new DeviceMappingFinder(params.getRaceLog()).analyze().values()) { + for (DeviceMapping mapping : mappings) { + gpsFixStore.addListener(this, mapping.getDevice()); + } + } + + // update the device mappings (without loading the fixes, as the TrackedRace does this itself on startup) + updateCompetitorMappings(false); + updateMarkMappings(false); + + // add mark passing detection + new MarkPassingCalculator(trackedRace, true); + + logger.info(String.format("Started tracking race-log race (%s)", raceLog)); + // this wakes up all waiting race handles + synchronized (this) { + this.notifyAll(); + } + } + + public RaceLogConnectivityParams getParams() { + return params; + } + + @Override + public void fixReceived(DeviceIdentifier device, GPSFix fix) { + TimePoint timePoint = fix.getTimePoint(); + if (markMappingsByDevices.get(device) != null) { + for (DeviceMapping mapping : markMappingsByDevices.get(device)) { + Mark mark = mapping.getMappedTo(); + if (mapping.getTimeRange().includes(timePoint)) { + trackedRace.recordFix(mark, fix); + } + } + } + + if (competitorMappingsByDevices.get(device) != null) { + for (DeviceMapping mapping : competitorMappingsByDevices.get(device)) { + Competitor comp = mapping.getMappedTo(); + if (mapping.getTimeRange().includes(timePoint)) { + if (fix instanceof GPSFixMoving) { + trackedRace.recordFix(comp, (GPSFixMoving) fix); + } else { + logger.log( + Level.WARNING, + String.format( + "Could not add fix for competitor (%s) in race (%s), as it is no GPSFixMoving, meaning it is missing COG/SOG values", + comp, params.getRaceLog())); + } + } + } + } + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRacesHandle.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRacesHandle.java new file mode 100644 index 00000000000..c3d20496915 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRacesHandle.java @@ -0,0 +1,70 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import java.util.Collections; +import java.util.Set; + +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.RaceTracker; +import com.sap.sailing.domain.tracking.RacesHandle; + +public class RaceLogRacesHandle implements RacesHandle { + private final RaceLogRaceTracker tracker; + + public RaceLogRacesHandle(RaceLogRaceTracker tracker) { + this.tracker = tracker; + } + + @Override + public Regatta getRegatta() { + return tracker.getRegatta(); + } + + @Override + public Set getRaces() { + return getRaces(-1); + } + + @Override + public Set getRaces(long timeoutInMilliseconds) { + long start = System.currentTimeMillis(); + Set raceDefs = tracker.getRaces(); + + RaceDefinition result = null; + if (raceDefs != null && !raceDefs.isEmpty()) { + result = tracker.getRaces().iterator().next(); + } + boolean interrupted = false; + synchronized (tracker) { + while ((timeoutInMilliseconds == -1 || System.currentTimeMillis() - start < timeoutInMilliseconds) + && !interrupted && result == null) { + try { + if (timeoutInMilliseconds == -1) { + tracker.wait(); + } else { + long timeToWait = timeoutInMilliseconds - (System.currentTimeMillis() - start); + if (timeToWait > 0) { + tracker.wait(timeToWait); + } + } + result = tracker.getRaces().isEmpty() ? null : tracker.getRaces().iterator().next(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } + return result == null ? null : Collections.singleton(result); + } + + @Override + public DynamicTrackedRegatta getTrackedRegatta() { + return tracker.getTrackedRegatta(); + } + + @Override + public RaceTracker getRaceTracker() { + return tracker; + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterFactoryImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterFactoryImpl.java new file mode 100644 index 00000000000..fbd1048b99a --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterFactoryImpl.java @@ -0,0 +1,19 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; + +public enum RaceLogTrackingAdapterFactoryImpl implements RaceLogTrackingAdapterFactory { + INSTANCE; + private RaceLogTrackingAdapter adapter; + + @Override + public RaceLogTrackingAdapter getAdapter(DomainFactory baseDomainFactory) { + if (adapter == null) { + adapter = new RaceLogTrackingAdapterImpl(baseDomainFactory); + } + return adapter; + } + +} 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 new file mode 100755 index 00000000000..91545bc1743 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -0,0 +1,303 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.ControlPoint; +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.DomainFactory; +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.Regatta; +import com.sap.sailing.domain.base.SharedDomainFactory; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.CourseDataImpl; +import com.sap.sailing.domain.common.PassingInstruction; +import com.sap.sailing.domain.common.RegattaIdentifier; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.NotDenotableForRaceLogTrackingException; +import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; +import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; +import com.sap.sailing.domain.common.racelog.tracking.RaceLogRaceTrackerExistsException; +import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.Revokable; +import com.sap.sailing.domain.racelog.analyzing.impl.LastPublishedCourseDesignFinder; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.AllEventsOfTypeFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.LastEventOfTypeFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.RaceLogTrackingStateAnalyzer; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.RegisteredCompetitorsAnalyzer; +import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifierImpl; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.RacesHandle; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.RacingEventService; +import com.sap.sse.common.Util; + +public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { + private static final Logger logger = Logger.getLogger(RaceLogTrackingAdapterImpl.class.getName()); + + private final DomainFactory domainFactory; + private final long delayToLiveInMillis; + + public RaceLogTrackingAdapterImpl(DomainFactory domainFactory) { + this.domainFactory = domainFactory; + this.delayToLiveInMillis = TrackedRace.DEFAULT_LIVE_DELAY_IN_MILLISECONDS; + } + + @Override + public void startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet) + throws NotDenotedForRaceLogTrackingException, Exception { + RaceLog raceLog = raceColumn.getRaceLog(fleet); + RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze(); + if (! raceLogTrackingState.isForTracking()) { + throw new NotDenotedForRaceLogTrackingException(); + } + RegattaIdentifier regatta = ((RegattaLeaderboard) leaderboard).getRegatta().getRegattaIdentifier(); + + if (raceLogTrackingState != RaceLogTrackingState.TRACKING) { + RaceLogEvent event = RaceLogEventFactory.INSTANCE.createStartTrackingEvent(MillisecondsTimePoint.now(), + service.getServerAuthor(), raceLog.getCurrentPassId()); + raceLog.add(event); + } + + if (! isRaceLogRaceTrackerAttached(service, raceLog)) { + addTracker(service, regatta, leaderboard, raceColumn, fleet, -1); + } + } + + /** + * Adds a {@link RaceLogRaceTracker}. If a {@link StartTrackingEvent} is already present in the {@code RaceLog} + * linked to the {@code raceColumn} and {@code fleet}, a {@code TrackedRace} is created immediately and tracking begins. + * Otherwise, the {@code RaceLogRaceTracker} waits until a {@code StartTrackingEvent} is added to perform these actions. + * The race first has to be denoted for racelog tracking. + */ + private RacesHandle addTracker(RacingEventService service, RegattaIdentifier regattaToAddTo, Leaderboard leaderboard, + RaceColumn raceColumn, Fleet fleet, long timeoutInMilliseconds) throws RaceLogRaceTrackerExistsException, Exception { + RaceLog raceLog = raceColumn.getRaceLog(fleet); + assert ! isRaceLogRaceTrackerAttached(service, raceLog) : new RaceLogRaceTrackerExistsException( + leaderboard.getName() + " - " + raceColumn.getName() + " - " + fleet.getName()); + + Regatta regatta = regattaToAddTo == null ? null : service.getRegatta(regattaToAddTo); + RaceLogConnectivityParams params = new RaceLogConnectivityParams(service, regatta, raceColumn, fleet, + leaderboard, delayToLiveInMillis, domainFactory); + return service.addRace(regattaToAddTo, params, timeoutInMilliseconds); + } + + @Override + public void denoteRaceForRaceLogTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, + Fleet fleet, String raceName) throws NotDenotableForRaceLogTrackingException { + + BoatClass boatClass = null; + if (leaderboard instanceof RegattaLeaderboard) { + RegattaLeaderboard rLeaderboard = (RegattaLeaderboard) leaderboard; + boatClass = rLeaderboard.getRegatta().getBoatClass(); + } else { + throw new NotDenotableForRaceLogTrackingException("Can only denote races in RegattaLeaderboards for RaceLog-tracking"); + } + + if (raceName == null) { + raceName = leaderboard.getName() + " " + raceColumn.getName() + " " + fleet.getName(); + } + + RaceLog raceLog = raceColumn.getRaceLog(fleet); + assert raceLog != null : new NotDenotableForRaceLogTrackingException("No RaceLog found in place"); + + if (new RaceLogTrackingStateAnalyzer(raceLog).analyze().isForTracking()) { + throw new NotDenotableForRaceLogTrackingException("Already denoted for tracking"); + } + + RaceLogEvent event = RaceLogEventFactory.INSTANCE.createDenoteForTrackingEvent(MillisecondsTimePoint.now(), + service.getServerAuthor(), raceLog.getCurrentPassId(), raceName, boatClass, UUID.randomUUID()); + raceLog.add(event); + } + + @Override + public void denoteAllRacesForRaceLogTracking(final RacingEventService service, final Leaderboard leaderboard) + throws NotDenotableForRaceLogTrackingException { + if (leaderboard instanceof FlexibleLeaderboard) { + throw new NotDenotableForRaceLogTrackingException("Can only use regatta leaderboards for RaceLog-tracking"); + } + for (RaceColumn column : leaderboard.getRaceColumns()) { + for (Fleet fleet : column.getFleets()) { + try { + denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, null); + } catch (NotDenotableForRaceLogTrackingException e) {} + } + } + } + + @Override + public boolean isRaceLogRaceTrackerAttached(RacingEventService service, RaceLog raceLog) { + return service.getRaceTrackerById(raceLog.getId()) != null; + } + + @Override + public RaceLogTrackingState getRaceLogTrackingState(RacingEventService service, RaceColumn raceColumn, Fleet fleet) { + return new RaceLogTrackingStateAnalyzer(raceColumn.getRaceLog(fleet)).analyze(); + } + + private Waypoint duplicateWaypoint(Waypoint waypoint, Map controlPointDuplicationCache, + Map markDuplicationCache, SharedDomainFactory baseDomainFactory) { + ControlPoint oldCP = waypoint.getControlPoint(); + ControlPoint newCP = null; + PassingInstruction pi = waypoint.getPassingInstructions(); + if (controlPointDuplicationCache.get(oldCP) != null) { + newCP = controlPointDuplicationCache.get(oldCP); + } else { + Mark[] newMarks = new Mark[Util.size(oldCP.getMarks())]; + int i = 0; + for (Mark oldMark : oldCP.getMarks()) { + newMarks[i] = null; + if (markDuplicationCache.get(oldMark) != null) { + newMarks[i] = markDuplicationCache.get(oldMark); + } else { + newMarks[i] = baseDomainFactory.getOrCreateMark(UUID.randomUUID(), oldMark.getName(), oldMark.getType(), + oldMark.getColor(), oldMark.getShape(), oldMark.getPattern()); + markDuplicationCache.put(oldMark, newMarks[i]); + } + i++; + } + switch (newMarks.length) { + case 1: + newCP = newMarks[0]; + break; + case 2: + newCP = baseDomainFactory.createControlPointWithTwoMarks(newMarks[0], newMarks[1], oldCP.getName()); + break; + default: + logger.log(Level.WARNING, "Don't know how to duplicate CP with more than 2 marks"); + throw new RuntimeException("Don't know how to duplicate CP with more than 2 marks"); + } + controlPointDuplicationCache.put(oldCP, newCP); + } + + return baseDomainFactory.createWaypoint(newCP, pi); + } + + private void revokeAlreadyDefinedMarks(RaceLog raceLog, RaceLogEventAuthor author) { + List markEvents = new AllEventsOfTypeFinder(raceLog, true, DefineMarkEvent.class).analyze(); + for (RaceLogEvent event : markEvents) { + try { + raceLog.revokeEvent(author, event); + } catch (NotRevokableException e) {} + } + } + + @Override + public void copyCourseAndCompetitors(RaceLog fromRaceLog, Set toRaceLogs, SharedDomainFactory baseDomainFactory, + RacingEventService service) { + CourseBase course = new LastPublishedCourseDesignFinder(fromRaceLog).analyze(); + Set competitors = new RegisteredCompetitorsAnalyzer(fromRaceLog).analyze(); + + for (RaceLog toRaceLog : toRaceLogs) { + CourseBase to = new CourseDataImpl("Copy of \"" + course.getName()); + TimePoint now = MillisecondsTimePoint.now(); + if (course == null || ! new RaceLogTrackingStateAnalyzer(toRaceLog).analyze().isForTracking()) { + continue; + } + int i = 0; + Map controlPointDuplicationCache = new HashMap(); + revokeAlreadyDefinedMarks(toRaceLog, service.getServerAuthor()); + Map markDuplicationCache = new HashMap(); + for (Waypoint oldWaypoint : course.getWaypoints()) { + to.addWaypoint(i++, duplicateWaypoint(oldWaypoint, controlPointDuplicationCache, markDuplicationCache, baseDomainFactory)); + } + + for (Mark mark : markDuplicationCache.values()) { + RaceLogEvent event = RaceLogEventFactory.INSTANCE.createDefineMarkEvent(now, service.getServerAuthor(), + toRaceLog.getCurrentPassId(), mark); + toRaceLog.add(event); + } + + int passId = toRaceLog.getCurrentPassId(); + + RaceLogEvent newCourseEvent = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent( + now, service.getServerAuthor(), passId, to); + toRaceLog.add(newCourseEvent); + + registerCompetitors(service, toRaceLog, competitors); + } + } + + @Override + public void pingMark(RaceLog raceLog, Mark mark, GPSFix gpsFix, RacingEventService service) { + DeviceIdentifier device = new PingDeviceIdentifierImpl(); + TimePoint time = gpsFix.getTimePoint(); + + RaceLogEvent mapping = RaceLogEventFactory.INSTANCE.createDeviceMarkMappingEvent(time, + service.getServerAuthor(), device, mark, raceLog.getCurrentPassId(), time, time); + raceLog.add(mapping); + try { + service.getGPSFixStore().storeFix(device, gpsFix); + } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { + logger.log(Level.WARNING, "Could not pint mark " + mark); + } + } + + @Override + public void removeDenotationForRaceLogTracking(RacingEventService service, RaceLog raceLog) { + Revokable denoteForTrackingEvent = (Revokable) new LastEventOfTypeFinder(raceLog, true, DenoteForTrackingEvent.class).analyze(); + Revokable startTrackingEvent = (Revokable) new LastEventOfTypeFinder(raceLog, true, StartTrackingEvent.class).analyze(); + try { + raceLog.revokeEvent(service.getServerAuthor(), denoteForTrackingEvent); + raceLog.revokeEvent(service.getServerAuthor(), startTrackingEvent); + } catch (NotRevokableException e) {} + } + + @Override + public void registerCompetitors(RacingEventService service, RaceLog raceLog, Set competitors) { + Set alreadyRegistered = new HashSet(new RegisteredCompetitorsAnalyzer(raceLog).analyze()); + Set toBeRegistered = new HashSet(); + + for (Competitor c : competitors) { + toBeRegistered.add(c); + } + + Set toBeRemoved = new HashSet(alreadyRegistered); + toBeRemoved.removeAll(toBeRegistered); + toBeRegistered.removeAll(alreadyRegistered); + + //register + for (Competitor c : toBeRegistered) { + raceLog.add(RaceLogEventFactory.INSTANCE.createRegisterCompetitorEvent(MillisecondsTimePoint.now(), + service.getServerAuthor(), raceLog.getCurrentPassId(), c)); + } + + //unregister + for (RaceLogEvent event : raceLog.getUnrevokedEventsDescending()) { + if (event instanceof RegisterCompetitorEvent) { + RegisterCompetitorEvent registerEvent = (RegisterCompetitorEvent) event; + if (toBeRemoved.contains(registerEvent.getCompetitor())) { + try { + raceLog.revokeEvent(service.getServerAuthor(), (RegisterCompetitorEvent) event); + } catch (NotRevokableException e) {} + } + } + } + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDMongoHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDMongoHandler.java new file mode 100644 index 00000000000..00c500d4baa --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDMongoHandler.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.SmartphoneUUIDSerializationHandler; + +public class SmartphoneUUIDMongoHandler extends SmartphoneUUIDSerializationHandler +implements DeviceIdentifierMongoHandler { + + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + return deserialize((String) serialized, type, stringRepresentation); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDStringSerializationHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDStringSerializationHandler.java new file mode 100644 index 00000000000..9f47a25f841 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDStringSerializationHandler.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifierStringSerializationHandler; +import com.sap.sailing.domain.racelog.tracking.SmartphoneUUIDSerializationHandler; + +public class SmartphoneUUIDStringSerializationHandler extends SmartphoneUUIDSerializationHandler +implements DeviceIdentifierStringSerializationHandler { + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierJsonHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierJsonHandler.java new file mode 100644 index 00000000000..7af401b6cec --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierJsonHandler.java @@ -0,0 +1,58 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import java.util.UUID; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; +import com.sap.sse.common.Util; + +public class TrackFileImportDeviceIdentifierJsonHandler implements DeviceIdentifierJsonHandler { + private static enum Fields {UUID, FILE_NAME, TRACK_NAME, UPLOADED_MILLIS}; + + public static TimePoint loadTimePoint(Object object) { + if (object == null) { + return null; + } + return new MillisecondsTimePoint(((Number) object).longValue()); + } + + public static Long storeTimePoint(TimePoint timePoint) { + if (timePoint == null) { + return null; + } + return timePoint.asMillis(); + } + + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + if (serialized == null) { + throw new TransformationException("Received nothing to deserialize"); + } + JSONObject json = (JSONObject) serialized; + UUID uuid = UUID.fromString((String) json.get(Fields.UUID.name())); + String fileName = (String) json.get(Fields.FILE_NAME.name()); + String trackName = (String) json.get(Fields.TRACK_NAME.name()); + TimePoint uploaded = loadTimePoint(json.get(Fields.UPLOADED_MILLIS.name())); + return new TrackFileImportDeviceIdentifierImpl(uuid, fileName, trackName, uploaded); + } + + @Override + public Util.Pair serialize(DeviceIdentifier deviceIdentifier) throws TransformationException { + TrackFileImportDeviceIdentifier id = TrackFileImportDeviceIdentifierImpl.cast(deviceIdentifier); + JSONObject result = new JSONObject(); + result.put(Fields.UUID.name(), id.getId().toString()); + result.put(Fields.FILE_NAME.name(), id.getFileName()); + result.put(Fields.TRACK_NAME.name(), id.getTrackName()); + result.put(Fields.UPLOADED_MILLIS.name(), storeTimePoint(id.getUploadedAt())); + return new Util.Pair(TrackFileImportDeviceIdentifier.TYPE, result); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierMongoHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierMongoHandler.java new file mode 100644 index 00000000000..def1acea245 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierMongoHandler.java @@ -0,0 +1,42 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import java.util.UUID; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; +import com.sap.sse.common.Util; + +public class TrackFileImportDeviceIdentifierMongoHandler implements DeviceIdentifierMongoHandler { + private static enum Fields {UUID, FILE_NAME, TRACK_NAME, UPLOADED_MILLIS}; + + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + DBObject dbObject = (DBObject) serialized; + UUID uuid = UUID.fromString((String) dbObject.get(Fields.UUID.name())); + String fileName = (String) dbObject.get(Fields.FILE_NAME.name()); + String trackName = (String) dbObject.get(Fields.TRACK_NAME.name()); + TimePoint uploaded = TrackFileImportDeviceIdentifierJsonHandler. + loadTimePoint(dbObject.get(Fields.UPLOADED_MILLIS.name())); + return new TrackFileImportDeviceIdentifierImpl(uuid, fileName, trackName, uploaded); + } + + @Override + public Util.Pair serialize(DeviceIdentifier deviceIdentifier) throws TransformationException { + TrackFileImportDeviceIdentifier id = TrackFileImportDeviceIdentifierImpl.cast(deviceIdentifier); + DBObject result = new BasicDBObject(); + result.put(Fields.UUID.name(), id.getId().toString()); + result.put(Fields.FILE_NAME.name(), id.getFileName()); + result.put(Fields.TRACK_NAME.name(), id.getTrackName()); + result.put(Fields.UPLOADED_MILLIS.name(), TrackFileImportDeviceIdentifierJsonHandler. + storeTimePoint(id.getUploadedAt())); + return new Util.Pair(TrackFileImportDeviceIdentifier.TYPE, result); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierStringSerializationHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierStringSerializationHandler.java new file mode 100644 index 00000000000..784b2b48043 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierStringSerializationHandler.java @@ -0,0 +1,31 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import java.util.UUID; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifierStringSerializationHandler; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; +import com.sap.sse.common.Util; + +public class TrackFileImportDeviceIdentifierStringSerializationHandler implements + DeviceIdentifierStringSerializationHandler { + + @Override + public Util.Pair serialize(DeviceIdentifier deviceIdentifier) throws TransformationException { + TrackFileImportDeviceIdentifier id = TrackFileImportDeviceIdentifierImpl.cast(deviceIdentifier); + return new Util.Pair(TrackFileImportDeviceIdentifier.TYPE, id.getStringRepresentation()); + } + + @Override + /** + * Only uses the first 36 characters in the string to construct the UUID, ignoring file, track and date info. + */ + public DeviceIdentifier deserialize(String serialized, String type, String stringRepresentation) + throws TransformationException { + UUID uuid = UUID.fromString(serialized.substring(0, 36)); + return TrackFileImportDeviceIdentifierImpl.getOrCreate(uuid); + } + +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/servlet/RecordFixesPostServlet.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/servlet/RecordFixesPostServlet.java new file mode 100755 index 00000000000..ea666d68a2a --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/servlet/RecordFixesPostServlet.java @@ -0,0 +1,70 @@ +package com.sap.sailing.domain.racelogtracking.servlet; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; + +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.server.gateway.AbstractJsonPostServlet; +import com.sap.sailing.server.gateway.HttpExceptionWithMessage; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.DeviceAndSessionIdentifierWithGPSFixesDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.DeviceIdentifierJsonDeserializer; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.GPSFixJsonHandler; +import com.sap.sse.common.Util; + +public class RecordFixesPostServlet extends + AbstractJsonPostServlet>, Void> { + private static final long serialVersionUID = 2778739335260621119L; + private DeviceAndSessionIdentifierWithGPSFixesDeserializer deserializer; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + TypeBasedServiceFinder fixServiceFinder = getServiceFinderFactory().createServiceFinder( + GPSFixJsonHandler.class); + TypeBasedServiceFinder deviceServiceFinder = getServiceFinderFactory() + .createServiceFinder(DeviceIdentifierJsonHandler.class); + deserializer = new DeviceAndSessionIdentifierWithGPSFixesDeserializer(fixServiceFinder, + new DeviceIdentifierJsonDeserializer(deviceServiceFinder)); + } + + @Override + public JsonDeserializer>> getRequestDeserializer() { + return deserializer; + } + + @Override + public JsonSerializer getResponseSerializer() { + return null; + } + + @Override + public Void process(Map parameterValues, + Util.Triple> domainObject) throws HttpExceptionWithMessage { + DeviceIdentifier device = domainObject.getA(); + // might use the session id in the future + // Serializable sessionId = domainObject.getB(); + List fixes = domainObject.getC(); + + for (GPSFix fix : fixes) { + try { + getService().getGPSFixStore().storeFix(device, fix); + } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { + logger.log(Level.WARNING, "Could not load store fix from device " + device); + } + } + + return null; + } +} diff --git a/java/com.sap.sailing.domain.shared.android.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.shared.android.test/META-INF/MANIFEST.MF index c0155eeec7d..dd8cf445e1e 100644 --- a/java/com.sap.sailing.domain.shared.android.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.shared.android.test/META-INF/MANIFEST.MF @@ -10,4 +10,7 @@ Require-Bundle: com.sap.sailing.domain.common;bundle-version="1.0.0", org.junit4;bundle-version="4.8.2", org.objenesis;bundle-version="1.3.0", org.hamcrest;bundle-version="1.1.0", - org.mockito.mockito-core;bundle-version="1.9.5" + org.mockito.mockito-core;bundle-version="1.9.5", + com.sap.sailing.domain.test, + com.sap.sailing.domain.racelogtrackingadapter.testsupport;bundle-version="1.0.0", + com.sap.sse.common diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java index 36badea21b0..8ce10fa81eb 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java @@ -10,7 +10,6 @@ import java.util.UUID; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogEventAuthor; @@ -19,7 +18,7 @@ import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; public abstract class PassAwareRaceLogAnalyzerTest, ResultType> extends RaceLogAnalyzerTest { - protected class TargetPair extends Util.Pair, ResultType> { + protected class TargetPair extends com.sap.sse.common.Util.Pair, ResultType> { private static final long serialVersionUID = 7961879340006633439L; public TargetPair(Collection a, ResultType b) { diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java index 18f5ddf6d07..4204598a50b 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java @@ -22,6 +22,7 @@ import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer; +import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest { @@ -56,7 +57,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest eventsToAdd = null; + if (clientEvent == null) { + eventsToAdd = serverRaceLog.getEventsToDeliver(clientUUID); + } else { + eventsToAdd = serverRaceLog.add(clientEvent, clientUUID); + } + for (RaceLogEvent eventToAdd : eventsToAdd) { + clientRaceLog.add(eventToAdd); + } + } + + private void sendClientAEvent(RaceLogEvent event) { + sendClientEvent(clientARaceLog, event, clientAUUID); + } + + private void sendClientBEvent(RaceLogEvent event) { + sendClientEvent(clientBRaceLog, event, clientBUUID); + } + + private void sendClientCEvent(RaceLogEvent event) { + sendClientEvent(clientCRaceLog, event, clientCUUID); + } + + private static void assertNumUnrevoked(RaceLog raceLog, int expectedNum) { + raceLog.lockForRead(); + try { + assertEquals(expectedNum, Util.size(raceLog.getUnrevokedEvents())); + } finally { + raceLog.unlockAfterRead(); + } + } + + private static void assertNumAll(RaceLog raceLog, int expectedNum) { + raceLog.lockForRead(); + try { + assertEquals(expectedNum, Util.size(raceLog.getRawFixes())); + } finally { + raceLog.unlockAfterRead(); + } + } + + @Test + public void sameEventRevokedByTwoClientsBeforeSync() throws NotRevokableException { + DenoteForTrackingEvent event = RaceLogEventFactory.INSTANCE.createDenoteForTrackingEvent( + now(), serverAuthor, 0, "race", null, null); + serverRaceLog.add(event); + + //sync events to clients + sendClientAEvent(null); + sendClientBEvent(null); + sendClientCEvent(null); + + //they revoke the event independently + try { + clientARaceLog.revokeEvent(clientAAuthor, event); + } catch (NotRevokableException e) { + //expected, because A does not have sufficient priority + } + RevokeEvent revokeEventB = clientBRaceLog.revokeEvent(clientBAuthor, event); + RevokeEvent revokeEventC = clientCRaceLog.revokeEvent(clientCAuthor, event); + + sendClientBEvent(revokeEventB); + sendClientCEvent(revokeEventC); + + //update clients + sendClientAEvent(null); + sendClientBEvent(null); + sendClientCEvent(null); + + //check if the event has been revoked on the server and clients + assertNumUnrevoked(clientARaceLog, 0); + assertNumAll(clientARaceLog, 3); + assertNumUnrevoked(clientBRaceLog, 0); + assertNumAll(clientBRaceLog, 3); + assertNumUnrevoked(clientCRaceLog, 0); + assertNumAll(clientCRaceLog, 3); + assertNumUnrevoked(serverRaceLog, 0); + assertNumAll(serverRaceLog, 3); + } +} diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogTest.java index cbdc055cc66..a9a05dec572 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogTest.java @@ -21,7 +21,6 @@ import org.mockito.stubbing.Answer; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogEventAuthor; @@ -32,6 +31,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; import com.sap.sailing.domain.racelog.impl.RaceLogImpl; +import com.sap.sse.common.Util; public class RaceLogTest { diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/SerializeRaceLogEventsTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/SerializeRaceLogEventsTest.java new file mode 100755 index 00000000000..787a2696929 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/SerializeRaceLogEventsTest.java @@ -0,0 +1,106 @@ +package com.sap.sailing.domain.racelog.test; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; +import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; +import com.sap.sailing.domain.racelog.impl.RaceLogImpl; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; +import com.sap.sailing.domain.test.AbstractLeaderboardTest; +import com.sap.sse.common.Util; + +/** + * Perform a few tests around race log and race log event serialization / deserialization + * + * @author Axel Uhl (d043530) + * + */ +public class SerializeRaceLogEventsTest { + private ObjectOutputStream oos; + private ByteArrayOutputStream bos; + private RaceLog raceLog; + private RaceLogEventFactory eventFactory; + + @Before + public void setUp() throws IOException { + bos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(bos); + raceLog = new RaceLogImpl("Test Race Log"); + eventFactory = RaceLogEventFactory.INSTANCE; + } + + @Test + public void testSimpleRaceLogSerialization() throws IOException, ClassNotFoundException { + oos.writeObject(raceLog); + ObjectInputStream ois = getObjectInputStream(); + RaceLog rl = (RaceLog) ois.readObject(); + raceLog.lockForRead(); + rl.lockForRead(); + try { + assertEquals(Util.size(raceLog.getRawFixes()), Util.size(rl.getRawFixes())); + } finally { + rl.unlockAfterRead(); + raceLog.unlockAfterRead(); + } + } + + @Test + public void testRaceLogSerializationWithSingleEvent() throws IOException, ClassNotFoundException { + RaceLogStartTimeEvent startTimeEvent = eventFactory.createStartTimeEvent(MillisecondsTimePoint.now(), + new RaceLogEventAuthorImpl("Author Name", /* priority */0), /* passId */1, /* startTime */ + MillisecondsTimePoint.now()); + raceLog.add(startTimeEvent); + oos.writeObject(raceLog); + ObjectInputStream ois = getObjectInputStream(); + RaceLog rl = (RaceLog) ois.readObject(); + raceLog.lockForRead(); + rl.lockForRead(); + try { + assertEquals(Util.size(raceLog.getRawFixes()), Util.size(rl.getRawFixes())); + assertEquals(((RaceLogStartTimeEvent) raceLog.getFirstRawFix()).getStartTime(), ((RaceLogStartTimeEvent) rl.getFirstRawFix()).getStartTime()); + } finally { + rl.unlockAfterRead(); + raceLog.unlockAfterRead(); + } + } + + @Test + public void testRaceLogSerializationWithEventContainingDeviceIdentifier() throws IOException, ClassNotFoundException { + DeviceCompetitorMappingEvent mappingEvent = eventFactory.createDeviceCompetitorMappingEvent( + MillisecondsTimePoint.now(), new RaceLogEventAuthorImpl("Author Name", /* priority */0), new SmartphoneImeiIdentifier("1948364938463903"), + AbstractLeaderboardTest.createCompetitor("Test Competitor"), 0, new MillisecondsTimePoint(0), new MillisecondsTimePoint(10)); + raceLog.add(mappingEvent); + oos.writeObject(raceLog); + ObjectInputStream ois = getObjectInputStream(); + RaceLog rl = (RaceLog) ois.readObject(); + raceLog.lockForRead(); + rl.lockForRead(); + try { + DeviceCompetitorMappingEvent expected = ((DeviceCompetitorMappingEvent) raceLog.getFirstRawFix()); + DeviceCompetitorMappingEvent actual = ((DeviceCompetitorMappingEvent) rl.getFirstRawFix()); + + assertEquals(Util.size(raceLog.getRawFixes()), Util.size(rl.getRawFixes())); + assertEquals(expected.getDevice().getIdentifierType(), actual.getDevice().getIdentifierType()); + } finally { + rl.unlockAfterRead(); + raceLog.unlockAfterRead(); + } + } + + private ObjectInputStream getObjectInputStream() throws IOException { + return new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); + } +} diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/AbstractRaceLogTrackingTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/AbstractRaceLogTrackingTest.java new file mode 100644 index 00000000000..3a42509c476 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/AbstractRaceLogTrackingTest.java @@ -0,0 +1,31 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.test; + +import org.junit.Before; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventRestoreFactory; +import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; +import com.sap.sailing.domain.racelog.impl.RaceLogImpl; + +public abstract class AbstractRaceLogTrackingTest { + + protected RaceLog log; + protected final RaceLogEventAuthor author = new RaceLogEventAuthorImpl("author0", 0); + protected final RaceLogEventAuthor author1 = new RaceLogEventAuthorImpl("author1", 1); + + protected final TimePoint now = MillisecondsTimePoint.now(); + protected final RaceLogEventRestoreFactory factory = RaceLogEventRestoreFactory.INSTANCE; + + public AbstractRaceLogTrackingTest() { + super(); + } + + @Before + public void setup() { + log = new RaceLogImpl("log"); + } + +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java new file mode 100644 index 00000000000..9a561e23d91 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java @@ -0,0 +1,96 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.test; + +import static org.junit.Assert.assertEquals; + +import java.io.Serializable; +import java.util.List; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.impl.CompetitorImpl; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMapping; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceCompetitorMappingFinder; +import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; + +public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest { + private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null); + private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null); + private final DeviceIdentifier device = new SmartphoneImeiIdentifier("imei"); + + private int time = 0; + + private List> getMappings() { + return new DeviceCompetitorMappingFinder(log).analyze().get(competitor); + } + + private TimePoint t() { + return new MillisecondsTimePoint(time++); + } + + private TimePoint t(Long millis) { + return millis == null ? null : new MillisecondsTimePoint(millis); + } + + private Serializable addMapping(RaceLogEventAuthor author, DeviceIdentifier device, Long from, Long to, Competitor item) { + RaceLogEvent mapping = factory.createDeviceCompetitorMappingEvent(t(), author, t(), UUID.randomUUID(), device, item, 0, + t(from), t(to)); + log.add(mapping); + return mapping.getId(); + } + + private void closeMapping(RaceLogEventAuthor author, DeviceIdentifier device, Serializable mappingId, long millis) { + RaceLogEvent mapping = factory.createCloseOpenEndedDeviceMappingEvent(t(), author, t(), UUID.randomUUID(), 0, mappingId, + new MillisecondsTimePoint(millis)); + log.add(mapping); + } + + @Test + public void notDisturbedByMappingsForOtherItemsAndDevices() { + //two mappings for first competitor found? (competitor2 mappings should not "distract") + addMapping(author, device, 10L, 40L, competitor); + addMapping(author, device, 10L, 40L, competitor2); + addMapping(author, device, 20L, 30L, competitor2); + addMapping(author, device, 50L, 90L, competitor); + Assert.assertEquals(2, getMappings().size()); + + //non-overlap with new device + addMapping(author, new SmartphoneImeiIdentifier("imei2"), 110L, 150L, competitor); + Assert.assertEquals(3, getMappings().size()); + } + + @Test + public void openRangesAreClosed() { + //close one range + Serializable id = addMapping(author, device, 10L, null, competitor); + closeMapping(author, device, id, 20); + List> mappings = getMappings(); + assertEquals(1, mappings.size()); + DeviceMapping mapping = mappings.get(0); + assertEquals(10, mapping.getTimeRange().from().asMillis()); + assertEquals(20, mapping.getTimeRange().to().asMillis()); + + //another independent range + id = addMapping(author, device, 0L, null, competitor); + closeMapping(author, device, id, 5); + mappings = getMappings(); + assertEquals(2, mappings.size()); + + //another closing mapping, that should take precedence (because added later) + closeMapping(author, device, id, 3); + assertEquals(2, getMappings().size()); + assertEquals(3, getMappings().get(1).getTimeRange().to().asMillis()); + + //open-ended at end, not closed + addMapping(author, device, 30L, null, competitor); + assertEquals(3, getMappings().size()); + } +} diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/RevokeEventTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/RevokeEventTest.java new file mode 100644 index 00000000000..edf72f64188 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/RevokeEventTest.java @@ -0,0 +1,55 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.test; + +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sse.common.Util; + +public class RevokeEventTest extends AbstractRaceLogTrackingTest { + @Test + public void revokedEventIsExcluded() { + RaceLogEvent event = factory.createDeviceCompetitorMappingEvent(now, author, now, UUID.randomUUID(), null, null, 0, null, null); + RevokeEvent revokeEvent = factory.createRevokeEvent(now.plus(1), author, now.plus(1), UUID.randomUUID(), 0, event.getId()); + + log.add(event); + log.add(revokeEvent); + + log.lockForRead(); + Assert.assertFalse(Util.contains(log.getUnrevokedEvents(), event)); + log.unlockAfterRead(); + } + + @Test + public void eventRevokedByAuthorWithLowerPrioIsNotExcluded() { + RaceLogEvent event = factory.createDeviceCompetitorMappingEvent(now, author, now, UUID.randomUUID(), null, null, 0, null, null); + RevokeEvent revokeEvent = factory.createRevokeEvent(now.plus(1), author1, now.plus(1), UUID.randomUUID(), 0, event.getId()); + + log.add(event); + log.add(revokeEvent); + + log.lockForRead(); + Assert.assertTrue(Util.contains(log.getUnrevokedEvents(), event)); + log.unlockAfterRead(); + } + + @Test + public void revokingRevokeEventIsNotHarmful() { + RaceLogEvent event = factory.createDeviceCompetitorMappingEvent(now, author, now, UUID.randomUUID(), null, null, 0, null, null); + RevokeEvent revokeEvent = factory.createRevokeEvent(now.plus(1), author, now.plus(1), UUID.randomUUID(), 0, event.getId()); + RevokeEvent revokeEvent2 = factory.createRevokeEvent(now.plus(2), author, now.plus(2), UUID.randomUUID(), 2, revokeEvent.getId()); + + log.add(event); + log.add(revokeEvent); + + log.lockForRead(); + Assert.assertFalse(Util.contains(log.getUnrevokedEvents(), event)); + Assert.assertFalse(Util.contains(log.getUnrevokedEvents(), revokeEvent)); + Assert.assertFalse(Util.contains(log.getUnrevokedEvents(), revokeEvent2)); + log.unlockAfterRead(); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF index d4dca443728..a1ef0a1e855 100644 --- a/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF @@ -5,7 +5,9 @@ Bundle-SymbolicName: com.sap.sailing.domain.shared.android Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.6 -Require-Bundle: com.sap.sailing.domain.common;bundle-version="1.0.0" +Require-Bundle: com.sap.sailing.domain.common;bundle-version="1.0.0", + com.sap.sse.datamining.shared;bundle-version="1.0.0", + com.sap.sse.common Export-Package: com.sap.sailing.domain.base, com.sap.sailing.domain.base.configuration, com.sap.sailing.domain.base.configuration.impl, @@ -26,6 +28,10 @@ Export-Package: com.sap.sailing.domain.base, com.sap.sailing.domain.racelog.state.racingprocedure.impl, com.sap.sailing.domain.racelog.state.racingprocedure.rrs26, com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.impl, + com.sap.sailing.domain.racelog.tracking, + com.sap.sailing.domain.racelog.tracking.analyzing.impl, + com.sap.sailing.domain.racelog.tracking.events, + com.sap.sailing.domain.racelog.tracking.impl, com.sap.sailing.domain.tracking, com.sap.sailing.domain.tracking.impl, com.sap.sailing.util.impl diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Boat.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Boat.java index cf3bad7fec3..e431312c985 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Boat.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Boat.java @@ -1,9 +1,11 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; +import com.sap.sse.datamining.shared.annotations.Dimension; public interface Boat extends Named { BoatClass getBoatClass(); + @Dimension(messageKey="SailID") String getSailID(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatChangeListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatChangeListener.java new file mode 100755 index 00000000000..cc86744e89b --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatChangeListener.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.base; + +public interface BoatChangeListener { + void sailIdChanged(String oldSailId, String newSailId); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java index 94b193f11aa..21ce155598e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java @@ -1,7 +1,8 @@ package com.sap.sailing.domain.base; import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.Named; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sse.common.Named; public interface BoatClass extends Named, IsManagedBySharedDomainFactory { /** diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java index b078bf8162c..75d579beb6b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java @@ -1,13 +1,29 @@ package com.sap.sailing.domain.base; import com.sap.sailing.domain.common.Color; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.WithID; +import com.sap.sse.datamining.shared.annotations.Connector; +import com.sap.sse.common.Named; public interface Competitor extends Named, WithID, IsManagedBySharedDomainFactory { + @Connector Team getTeam(); + @Connector Boat getBoat(); Color getColor(); + + /** + * Adds a listener to this competitor. The listener is also added to the boat and the team for changes. + * Adding a listener that is already part of this competitor's listeners set remains without effect. + */ + void addCompetitorChangeListener(CompetitorChangeListener listener); + + /** + * Removes a listener from this competitor. The listener is also removed from the boat and the team. + * Trying to remove a listener that is not currently in the set of this competitor's listener remains + * without effect. + */ + void removeCompetitorChangeListener(CompetitorChangeListener listener); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java new file mode 100755 index 00000000000..f5a0b722976 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.base; + +import com.sap.sailing.domain.common.Color; + +public interface CompetitorChangeListener extends BoatChangeListener, NationalityChangeListener { + void colorChanged(Color oldColor, Color newColor); + + void nameChanged(String oldName, String newName); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/ControlPoint.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/ControlPoint.java index 74f1f11eaf1..19862b1c3dd 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/ControlPoint.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/ControlPoint.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.WithID; +import com.sap.sse.common.Named; public interface ControlPoint extends WithID, Named { Iterable getMarks(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseArea.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseArea.java index c170a53ecb7..cbed3e691cd 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseArea.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseArea.java @@ -2,8 +2,8 @@ package com.sap.sailing.domain.base; import java.util.UUID; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.WithID; +import com.sap.sse.common.Named; /** * A named area in a sailing {@link Venue} where races take place. Usually, the areas carry names according to the NATO diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseBase.java index 4f0af9cb73e..4ba324f79ed 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseBase.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseBase.java @@ -2,7 +2,7 @@ package com.sap.sailing.domain.base; import java.util.List; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; /** * Base interface for courses consisting of all static information, which might be shared diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java index bf261e94a5d..be7fec595ee 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java @@ -1,9 +1,11 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.common.Named; +import java.net.URL; + import com.sap.sailing.domain.common.Renamable; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WithID; +import com.sap.sse.common.Named; /** * Base interface for an Event consisting of all static information, which might be shared @@ -33,4 +35,50 @@ public interface EventBase extends Named, Renamable, WithID { boolean isPublic(); void setPublic(boolean isPublic); + + /** + * Returns a non-null live but unmodifiable collection of URLs pointing to image resources that can be + * used to represent the event, e.g., on a web page. + * + * @return a non-null value which may be empty + */ + Iterable getImageURLs(); + + void addImageURL(URL imageURL); + + void removeImageURL(URL imageURL); + + /** + * Replaces the {@link #getImageURLs() current contents of the image URL sequence} by the image URLs in + * imageURLs. + * + * @param imageURLs + * if null, the internal sequence of image URLs is cleared but remains valid (non- + * null) + */ + void setImageURLs(Iterable imageURLs); + + /** + * Returns a non-null live but unmodifiable collection of URLs pointing to video resources that can be + * used to represent the event, e.g., on a web page. + * + * @return a non-null value which may be empty + */ + Iterable getVideoURLs(); + + void addVideoURL(URL videoURL); + + void removeVideoURL(URL videoURL); + + /** + * Replaces the {@link #getVideoURLs() current contents of the video URL sequence} by the video URLs in + * videoURLs. + * + * @param videoURLs + * if null, the internal sequence of image URLs is cleared but remains valid (non- + * null) + */ + void setVideoURLs(Iterable videoURLs); + + Iterable getLeaderboardGroups(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Fleet.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Fleet.java index 35a5bd3c4f4..0c2a2688718 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Fleet.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Fleet.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.base; import com.sap.sailing.domain.common.Color; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; /** * For each fleet in a {@link Series} there is one {@link RaceDefinition race} per "race column." Competitor to fleet diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardBase.java new file mode 100755 index 00000000000..98e35f79e1c --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardBase.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.base; + +import com.sap.sse.common.Named; + +public interface LeaderboardBase extends Named { + /** + * If a display name for the leaderboard has been defined, + * this method returns it; otherwise, null is returned. + */ + String getDisplayName(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java new file mode 100755 index 00000000000..a73fe817219 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java @@ -0,0 +1,13 @@ +package com.sap.sailing.domain.base; + +import java.util.UUID; + +import com.sap.sailing.domain.common.Renamable; +import com.sap.sailing.domain.common.WithID; + +public interface LeaderboardGroupBase extends Renamable, WithID { + UUID getId(); + String getDescription(); + void setDescriptiom(String description); + boolean hasOverallLeaderboard(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardSearchResultBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardSearchResultBase.java new file mode 100755 index 00000000000..1ab3365fc2e --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardSearchResultBase.java @@ -0,0 +1,38 @@ +package com.sap.sailing.domain.base; + +import com.sap.sse.common.search.Hit; + +/** + * A generalization of LeaderboardSearchResult which has references to full-blown leaderboards, a regatta, + * full leaderboard groups and the entire event; this form requires only the respective ...Base flavors of + * events, leaderboards and leaderboard groups which allows for the creation of a copy in a server that has been + * receiving a search result from some other (remote) server, e.g., through JSON deserialization. + * + * @author Axel Uhl (D043530) + * + */ +public interface LeaderboardSearchResultBase extends Hit { + /** + * The leaderboard that matches the search query. + */ + LeaderboardBase getLeaderboard(); + + String getRegattaName(); + + String getBoatClassName(); + + /** + * If the leaderboard is part of one or more leaderboard groups, the leaderboard groups; an empty but + * valid (non-null) collection otherwise. + */ + Iterable getLeaderboardGroups(); + + /** + * If there is a connection between an {@link EventBase} and the {@link #getLeaderboard() leaderboard}, e.g., + * because the {@link EventBase} has a {@link EventBase#getLeaderboardGroups() leaderboard group} that is part of + * {@link #getLeaderboardGroups()} or the event has a course area attached to its {@link EventBase#getVenue() venue} + * that is referenced by the leaderboard as its {@link Leaderboard#getDefaultCourseArea() default course area}, the + * event is returned; null otherwise. + */ + EventBase getEvent(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Nationality.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Nationality.java index 90dd0488ad1..b2c09c8bc4f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Nationality.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Nationality.java @@ -1,9 +1,11 @@ package com.sap.sailing.domain.base; import com.sap.sailing.domain.common.CountryCode; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; +import com.sap.sse.datamining.shared.annotations.Dimension; public interface Nationality extends Named, WithImage, IsManagedBySharedDomainFactory { + @Dimension(messageKey="NationalityAcronym") String getThreeLetterIOCAcronym(); CountryCode getCountryCode(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/NationalityChangeListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/NationalityChangeListener.java new file mode 100755 index 00000000000..04171c3b454 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/NationalityChangeListener.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.base; + +import java.io.Serializable; + +public interface NationalityChangeListener extends Serializable { + void nationalityChanged(WithNationality what, Nationality oldNationality, Nationality newNationality); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Person.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Person.java index ffd727a9ec3..91cb81ec437 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Person.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Person.java @@ -2,7 +2,7 @@ package com.sap.sailing.domain.base; import java.util.Date; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; public interface Person extends Named, WithImage, WithNationality, WithDescription { Date getDateOfBirth(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SharedDomainFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SharedDomainFactory.java index 7df5596b864..ac14d39c9a2 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SharedDomainFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SharedDomainFactory.java @@ -95,4 +95,8 @@ public interface SharedDomainFactory extends CompetitorFactory { * Hm. */ DeviceConfigurationMatcher getOrCreateDeviceConfigurationMatcher(DeviceConfigurationMatcherType type, List clientIdentifiers); + + Mark getExistingMarkByIdAsString(String toStringRepresentationOfID); + + Mark getExistingMarkById(Serializable id); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Sideline.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Sideline.java index ecc99409350..dda15a12cd9 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Sideline.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Sideline.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.WithID; +import com.sap.sse.common.Named; /** * A side line in a race course diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Team.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Team.java index 13759ca92e0..5fdeb5f1bcd 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Team.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Team.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; public interface Team extends Named, WithNationality { Iterable getSailors(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Venue.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Venue.java index 4bf6995dbbf..6c48e75288e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Venue.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Venue.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.Renamable; +import com.sap.sse.common.Named; /** * Describes the place where a sailing event with one or more regattas takes place. diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Waypoint.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Waypoint.java index 84e734590a7..cf9f7cbf82f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Waypoint.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Waypoint.java @@ -1,9 +1,9 @@ package com.sap.sailing.domain.base; import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.WithID; +import com.sap.sse.common.Named; /** * Waypoints constitute {@link Course}s and demarcate their {@link Leg}s. A waypoint's position is defined by a diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java index 11603d89d7c..fbcce202067 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java @@ -1,5 +1,8 @@ package com.sap.sailing.domain.base; +import com.sap.sse.datamining.shared.annotations.Connector; + public interface WithNationality { + @Connector Nationality getNationality(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/EmptyRegattaConfiguration.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/EmptyRegattaConfiguration.java index f9832609406..799654d8425 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/EmptyRegattaConfiguration.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/EmptyRegattaConfiguration.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.base.configuration.impl; -import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.base.configuration.ConfigurationLoader; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.common.CourseDesignerMode; import com.sap.sailing.domain.common.racelog.RacingProcedureType; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatImpl.java index a16c72f28e1..eefb80ce6c8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatImpl.java @@ -1,17 +1,31 @@ package com.sap.sailing.domain.base.impl; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.HashSet; +import java.util.Set; + +import com.sap.sailing.domain.base.BoatChangeListener; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.common.impl.NamedImpl; +import com.sap.sse.common.Util; public class BoatImpl extends NamedImpl implements DynamicBoat { private static final long serialVersionUID = 3489730487528955788L; private final BoatClass boatClass; private String sailID; + private transient Set listeners; public BoatImpl(String name, BoatClass boatClass, String sailID) { super(name); this.boatClass = boatClass; this.sailID = sailID; + this.listeners = new HashSet(); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + listeners = new HashSet(); } @Override @@ -26,6 +40,32 @@ public class BoatImpl extends NamedImpl implements DynamicBoat { @Override public void setSailId(String newSailId) { - this.sailID = newSailId; + final String oldSailId = this.sailID; + if (!Util.equalsWithNull(oldSailId, newSailId)) { + this.sailID = newSailId; + for (BoatChangeListener listener : getListeners()) { + listener.sailIdChanged(oldSailId, newSailId); + } + } + } + + @Override + public void addBoatChangeListener(BoatChangeListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + @Override + public void removeCompetitorChangeListener(BoatChangeListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + private Iterable getListeners() { + synchronized (listeners) { + return new HashSet(listeners); + } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java index 38ed945fc75..bbe4932d708 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java @@ -1,10 +1,16 @@ package com.sap.sailing.domain.base.impl; +import java.io.IOException; +import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CompetitorChangeListener; import com.sap.sailing.domain.base.SharedDomainFactory; import com.sap.sailing.domain.common.Color; +import com.sap.sse.common.Util; public class CompetitorImpl implements DynamicCompetitor { private static final long serialVersionUID = 294603681016643157L; @@ -12,8 +18,8 @@ public class CompetitorImpl implements DynamicCompetitor { private final DynamicBoat boat; private final Serializable id; private String name; - private Color color; + private transient Set listeners; public CompetitorImpl(Serializable id, String name, Color color, DynamicTeam team, DynamicBoat boat) { this.id = id; @@ -21,6 +27,12 @@ public class CompetitorImpl implements DynamicCompetitor { this.team = team; this.boat = boat; this.color = color; + this.listeners = new HashSet(); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + listeners = new HashSet(); } @Override @@ -35,14 +47,20 @@ public class CompetitorImpl implements DynamicCompetitor { @Override public void setName(String newName) { - this.name = newName; + final String oldName = this.name; + if (!Util.equalsWithNull(oldName, newName)) { + this.name = newName; + for (CompetitorChangeListener listener : getListeners()) { + listener.nameChanged(oldName, newName); + } + } } @Override public Serializable getId() { return id; } - + @Override public DynamicTeam getTeam() { return team; @@ -64,6 +82,36 @@ public class CompetitorImpl implements DynamicCompetitor { } public void setColor(Color color) { - this.color = color; + final Color oldColor = this.color; + if (!Util.equalsWithNull(oldColor, color)) { + this.color = color; + for (CompetitorChangeListener listener : getListeners()) { + listener.colorChanged(oldColor, color); + } + } + } + + @Override + public void addCompetitorChangeListener(CompetitorChangeListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + getBoat().addBoatChangeListener(listener); + getTeam().addNationalityChangeListener(listener); + } + + @Override + public void removeCompetitorChangeListener(CompetitorChangeListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + getBoat().removeCompetitorChangeListener(listener); + getTeam().removeNationalityChangeListener(listener); + } + + private Iterable getListeners() { + synchronized (listeners) { + return new HashSet(listeners); + } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicBoat.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicBoat.java index 0b4e8f1e278..b3131fc9e4c 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicBoat.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicBoat.java @@ -1,7 +1,13 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.Boat; +import com.sap.sailing.domain.base.BoatChangeListener; public interface DynamicBoat extends Boat { void setSailId(String newSailId); + + void addBoatChangeListener(BoatChangeListener listener); + + void removeCompetitorChangeListener(BoatChangeListener listener); + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicPerson.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicPerson.java index d5d13c3b45c..231192564e6 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicPerson.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicPerson.java @@ -1,8 +1,13 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.Nationality; +import com.sap.sailing.domain.base.NationalityChangeListener; import com.sap.sailing.domain.base.Person; public interface DynamicPerson extends Person { void setNationality(Nationality newNationality); + + void addNationalityChangeListener(NationalityChangeListener listener); + + void removeNationalityChangeListener(NationalityChangeListener listener); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicTeam.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicTeam.java index 807b159842a..fb0e14ba7a2 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicTeam.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicTeam.java @@ -1,8 +1,13 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.Nationality; +import com.sap.sailing.domain.base.NationalityChangeListener; import com.sap.sailing.domain.base.Team; public interface DynamicTeam extends Team { void setNationality(Nationality newNationality); + + void addNationalityChangeListener(NationalityChangeListener listener); + + void removeNationalityChangeListener(NationalityChangeListener listener); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java index 323f6fa263a..540f06566b8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java @@ -1,12 +1,18 @@ package com.sap.sailing.domain.base.impl; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.net.URL; +import java.util.Collections; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.Venue; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sse.common.Util; -public class EventBaseImpl implements EventBase { +public abstract class EventBaseImpl implements EventBase { private static final long serialVersionUID = -5749964088848611074L; private String name; @@ -15,15 +21,17 @@ public class EventBaseImpl implements EventBase { private final UUID id; private TimePoint startDate; private TimePoint endDate; + private ConcurrentLinkedQueue imageURLs; + private ConcurrentLinkedQueue videoURLs; - public EventBaseImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) { + protected EventBaseImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) { this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id); } /** * @param venue must not be null */ - public EventBaseImpl(String name, TimePoint startDate, TimePoint endDate, Venue venue, boolean isPublic, UUID id) { + protected EventBaseImpl(String name, TimePoint startDate, TimePoint endDate, Venue venue, boolean isPublic, UUID id) { assert venue != null; this.id = id; this.name = name; @@ -31,6 +39,18 @@ public class EventBaseImpl implements EventBase { this.endDate = endDate; this.venue = venue; this.isPublic = isPublic; + this.imageURLs = new ConcurrentLinkedQueue(); + this.videoURLs = new ConcurrentLinkedQueue(); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + if (imageURLs == null) { + imageURLs = new ConcurrentLinkedQueue(); + } + if (videoURLs == null) { + videoURLs = new ConcurrentLinkedQueue(); + } } @Override @@ -88,4 +108,50 @@ public class EventBaseImpl implements EventBase { this.endDate = endDate; } + @Override + public Iterable getImageURLs() { + return Collections.unmodifiableCollection(imageURLs); + } + + @Override + public void addImageURL(URL imageURL) { + if (!imageURLs.contains(imageURL)) { + imageURLs.add(imageURL); + } + } + + @Override + public void removeImageURL(URL imageURL) { + imageURLs.remove(imageURL); + } + + @Override + public void setImageURLs(Iterable imageURLs) { + this.imageURLs.clear(); + Util.addAll(imageURLs, this.imageURLs); + } + + @Override + public Iterable getVideoURLs() { + return Collections.unmodifiableCollection(videoURLs); + } + + @Override + public void addVideoURL(URL videoURL) { + if (!videoURLs.contains(videoURL)) { + videoURLs.add(videoURL); + } + } + + @Override + public void removeVideoURL(URL videoURL) { + videoURLs.remove(videoURL); + } + + @Override + public void setVideoURLs(Iterable videoURLs) { + this.videoURLs.clear(); + Util.addAll(videoURLs, this.videoURLs); + } + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/LeaderboardGroupBaseImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/LeaderboardGroupBaseImpl.java new file mode 100755 index 00000000000..59c1fd5e78e --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/LeaderboardGroupBaseImpl.java @@ -0,0 +1,54 @@ +package com.sap.sailing.domain.base.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.UUID; + +import com.sap.sailing.domain.base.LeaderboardGroupBase; + +public abstract class LeaderboardGroupBaseImpl implements LeaderboardGroupBase { + private static final long serialVersionUID = 5769435569603360651L; + + private UUID id; + private String name; + private String description; + + public LeaderboardGroupBaseImpl(UUID id, String name, String description) { + this.id = id; + this.name = name; + this.description = description; + } + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + if (id == null) { + id = UUID.randomUUID(); + } + } + + @Override + public UUID getId() { + return id; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public void setDescriptiom(String description) { + this.description = description; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/PersonImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/PersonImpl.java index 48115fd27f7..c42d1373194 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/PersonImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/PersonImpl.java @@ -1,24 +1,37 @@ package com.sap.sailing.domain.base.impl; +import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputStream; import java.util.Date; +import java.util.HashSet; +import java.util.Set; import com.sap.sailing.domain.base.Nationality; +import com.sap.sailing.domain.base.NationalityChangeListener; import com.sap.sailing.domain.common.impl.NamedImpl; +import com.sap.sse.common.Util; public class PersonImpl extends NamedImpl implements DynamicPerson { private static final long serialVersionUID = -2104903799224233508L; private Nationality nationality; private final Date dateOfBirth; private final String description; + private transient Set listeners; public PersonImpl(String name, Nationality nationality, Date dateOfBirth, String description) { super(name); this.nationality = nationality; this.dateOfBirth = dateOfBirth; this.description = description; + this.listeners = new HashSet(); } + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + listeners = new HashSet(); + } + @Override public InputStream getImage() { throw new UnsupportedOperationException("Image for person not yet implemented"); @@ -31,7 +44,13 @@ public class PersonImpl extends NamedImpl implements DynamicPerson { @Override public void setNationality(Nationality newNationality) { - this.nationality = newNationality; + final Nationality oldNationality = this.nationality; + if (!Util.equalsWithNull(oldNationality, newNationality)) { + this.nationality = newNationality; + for (NationalityChangeListener listener : getListeners()) { + listener.nationalityChanged(this, oldNationality, newNationality); + } + } } @Override @@ -44,4 +63,23 @@ public class PersonImpl extends NamedImpl implements DynamicPerson { return description; } + @Override + public void addNationalityChangeListener(NationalityChangeListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + @Override + public void removeNationalityChangeListener(NationalityChangeListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + private Iterable getListeners() { + synchronized (listeners) { + return new HashSet(listeners); + } + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java index 03a0c1f1858..21833028f20 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java @@ -312,5 +312,15 @@ public class SharedDomainFactoryImpl implements SharedDomainFactory { } return matcher; } + + @Override + public Mark getExistingMarkById(Serializable id) { + return markCache.get(id); + } + + @Override + public Mark getExistingMarkByIdAsString(String toStringRepresentationOfID) { + return markCache.get(markIdCache.get(toStringRepresentationOfID)); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SidelineImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SidelineImpl.java index bf910797daa..0921f2cde1d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SidelineImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SidelineImpl.java @@ -7,7 +7,7 @@ import java.util.List; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.common.impl.NamedImpl; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sse.common.Util; public class SidelineImpl extends NamedImpl implements Sideline { private static final long serialVersionUID = -8145721464971358691L; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java new file mode 100755 index 00000000000..5874331e7e1 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java @@ -0,0 +1,37 @@ +package com.sap.sailing.domain.base.impl; + +import java.util.UUID; + +import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.base.LeaderboardGroupBase; +import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.common.TimePoint; + +/** + * A simplified implementation of the {@link EventBase} interface which maintains an immutable collection of + * {@link LeaderboardGroupBase} objects to implement the {@link #getLeaderboardGroups()} method. + * + * @author Axel Uhl (D043530) + * + */ +public class StrippedEventImpl extends EventBaseImpl { + private static final long serialVersionUID = 5608501747499933988L; + private final Iterable leaderboardGroups; + + public StrippedEventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, + boolean isPublic, UUID id, Iterable leaderboardGroups) { + super(name, startDate, endDate, venueName, isPublic, id); + this.leaderboardGroups = leaderboardGroups; + } + + public StrippedEventImpl(String name, TimePoint startDate, TimePoint endDate, Venue venue, + boolean isPublic, UUID id, Iterable leaderboardGroups) { + super(name, startDate, endDate, venue, isPublic, id); + this.leaderboardGroups = leaderboardGroups; + } + + @Override + public Iterable getLeaderboardGroups() { + return leaderboardGroups; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedLeaderboardGroupImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedLeaderboardGroupImpl.java new file mode 100755 index 00000000000..7df0a3d23a8 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedLeaderboardGroupImpl.java @@ -0,0 +1,18 @@ +package com.sap.sailing.domain.base.impl; + +import java.util.UUID; + +public class StrippedLeaderboardGroupImpl extends LeaderboardGroupBaseImpl { + private static final long serialVersionUID = 7263952130620919924L; + private final boolean hasOverallLeaderboard; + + public StrippedLeaderboardGroupImpl(UUID id, String name, String description, boolean hasOverallLeaderboard) { + super(id, name, description); + this.hasOverallLeaderboard = hasOverallLeaderboard; + } + + @Override + public boolean hasOverallLeaderboard() { + return hasOverallLeaderboard; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java index d9332f79763..c5238230ded 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java @@ -1,18 +1,80 @@ package com.sap.sailing.domain.base.impl; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.HashSet; +import java.util.Set; + import com.sap.sailing.domain.base.Nationality; +import com.sap.sailing.domain.base.NationalityChangeListener; import com.sap.sailing.domain.base.Person; +import com.sap.sailing.domain.base.WithNationality; import com.sap.sailing.domain.common.impl.NamedImpl; +import com.sap.sse.common.Util; public class TeamImpl extends NamedImpl implements DynamicTeam { private static final long serialVersionUID = 4646922280429210183L; private final Iterable sailors; - private final Person coach; + private final DynamicPerson coach; + private final NationalityChangeListener personNationalityChangeForwarder; + private transient Set listeners; - public TeamImpl(String name, Iterable sailors, Person coach) { + public TeamImpl(String name, Iterable sailors, DynamicPerson coach) { super(name); + listeners = new HashSet(); this.sailors = sailors; this.coach = coach; + this.personNationalityChangeForwarder = new NationalityChangeListener() { + private static final long serialVersionUID = -6024941913105410444L; + @Override + public void nationalityChanged(WithNationality what, Nationality oldNationality, Nationality newNationality) { + if (what == getNationalityDonor()) { + for (NationalityChangeListener listener : getNationalityChangeListeners()) { + listener.nationalityChanged(TeamImpl.this, oldNationality, newNationality); + } + } + } + }; + setTransitiveListeners(); + } + + /** + * The listeners on {@link DynamicPerson} are transient. Our {@link #personNationalityChangeForwarder} needs to + * be restored after deserialization. + */ + private void setTransitiveListeners() { + if (this.sailors != null) { + for (DynamicPerson sailor : this.sailors) { + sailor.addNationalityChangeListener(personNationalityChangeForwarder); + } + } + if (this.coach != null) { + this.coach.addNationalityChangeListener(personNationalityChangeForwarder); + } + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + listeners = new HashSet(); + setTransitiveListeners(); + } + + public void addNationalityChangeListener(NationalityChangeListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + public void removeNationalityChangeListener(NationalityChangeListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + private Iterable getNationalityChangeListeners() { + synchronized (listeners) { + return new HashSet(listeners); + } } @Override @@ -21,28 +83,41 @@ public class TeamImpl extends NamedImpl implements DynamicTeam { } @Override - public Person getCoach() { + public DynamicPerson getCoach() { return coach; } @Override public Nationality getNationality() { + return getNationalityDonor() == null ? null : getNationalityDonor().getNationality(); + } + + private WithNationality getNationalityDonor() { for (Person sailor : getSailors()) { if (sailor.getNationality() != null) { - return sailor.getNationality(); + return sailor; } } if (getCoach() != null) { - return getCoach().getNationality(); + return getCoach(); } return null; } @Override public void setNationality(Nationality newNationality) { - for (Person sailor : getSailors()) { - ((DynamicPerson) sailor).setNationality(newNationality); + Nationality oldNationality = getNationality(); + if (!Util.equalsWithNull(oldNationality, newNationality)) { + for (Person sailor : getSailors()) { + ((DynamicPerson) sailor).setNationality(newNationality); + } + if (getCoach() != null) { + getCoach().setNationality(newNationality); + } + for (NationalityChangeListener listener : getNationalityChangeListeners()) { + listener.nationalityChanged(this, oldNationality, newNationality); + } } } - + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/VenueImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/VenueImpl.java index 68f879a9991..d33ee69e283 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/VenueImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/VenueImpl.java @@ -1,11 +1,15 @@ package com.sap.sailing.domain.base.impl; +import java.io.IOException; +import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.util.impl.LockUtil; +import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; public class VenueImpl implements Venue { private static final long serialVersionUID = 6854152040737643290L; @@ -16,25 +20,54 @@ public class VenueImpl implements Venue { * "NATO alphabet" (Alpha, Bravo, Charlie, ...). */ private final List courseAreas; + + private NamedReentrantReadWriteLock courseAreasLock; public VenueImpl(String name) { this.name = name; courseAreas = new ArrayList(); + courseAreasLock = createCourseAreasLock(name); + } + + private NamedReentrantReadWriteLock createCourseAreasLock(String name) { + return new NamedReentrantReadWriteLock("Course Areas for venue "+name, /* fair */ false); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + if (courseAreasLock == null) { + courseAreasLock = createCourseAreasLock(getName()); + } } @Override public Iterable getCourseAreas() { - return Collections.unmodifiableList(courseAreas); + LockUtil.lockForRead(courseAreasLock); + try { + return Collections.unmodifiableList(courseAreas); + } finally { + LockUtil.unlockAfterRead(courseAreasLock); + } } @Override public void addCourseArea(CourseArea courseArea) { - courseAreas.add(courseArea); + LockUtil.lockForWrite(courseAreasLock); + try { + courseAreas.add(courseArea); + } finally { + LockUtil.unlockAfterWrite(courseAreasLock); + } } @Override public void removeCourseArea(CourseArea courseArea) { - courseAreas.remove(courseArea); + LockUtil.lockForWrite(courseAreasLock); + try { + courseAreas.remove(courseArea); + } finally { + LockUtil.unlockAfterWrite(courseAreasLock); + } } @Override diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java index a90347df508..0aca85fadd6 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.base.racegroup; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sse.common.Named; /** * A "race". diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java index 620d8903c58..7ab4d858219 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java @@ -4,8 +4,8 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.configuration.RegattaConfiguration; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; +import com.sap.sse.common.Named; /** * A {@link RaceGroup} is an abstract representation of a set of races managed in fleets and series. diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/CompetitorResults.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/CompetitorResults.java index 03cbde6c23b..31d63a75e82 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/CompetitorResults.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/CompetitorResults.java @@ -4,8 +4,8 @@ import java.io.Serializable; import java.util.List; import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; -public interface CompetitorResults extends List> { +public interface CompetitorResults extends List> { } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java index f364f7cc475..41a28e44349 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java @@ -1,106 +1,135 @@ -package com.sap.sailing.domain.racelog; - -import java.util.HashSet; -import java.util.UUID; - -import com.sap.sailing.domain.common.WithID; -import com.sap.sailing.domain.racelog.impl.RaceLogEventComparator; -import com.sap.sailing.domain.tracking.Track; - -/** - * Special kind of {@link Track} for recording {@link RaceLogEvent}s. - * - *

- * Keeps track of the {@link RaceLogEvent}'s pass and returns only the events of the current pass on - * {@link RaceLog#getFixes()}. Use {@link RaceLog#getRawFixes()} to receive all events in a {@link RaceLog}. - *

- * - *

- * Implementations should use the {@link RaceLogEventComparator} for sorting its content. - *

- */ -public interface RaceLog extends Track, WithID { - - public static final int DefaultPassId = 0; - - /** - * Gets the current pass id. - * - * @return the pass id. - */ - int getCurrentPassId(); - - /** - * Adds a {@link RaceLogEvent} to the {@link RaceLog}. - * - * @param event - * {@link RaceLogEvent} to be added. - * @return true if the element was added, false otherwise. - */ - boolean add(RaceLogEvent event); - - /** - * Add a {@link RaceLogEventVisitor} as a listener for additions. - */ - void addListener(RaceLogEventVisitor listener); - - /** - * Remove a listener. - */ - void removeListener(RaceLogEventVisitor listener); - - /** - * Removes all listeners - * @return - */ - HashSet removeAllListeners(); - - /** - * Checks if the race log is empty. - */ - boolean isEmpty(); - - Iterable getRawFixesDescending(); - - Iterable getFixesDescending(); - - void addAllListeners(HashSet listeners); - - Iterable getAllListeners(); - - /** - * Adds an event to this race log and returns {@link RaceLog#getEventsToDeliver(UUID)} - * (excluding the new event) - */ - Iterable add(RaceLogEvent event, UUID clientId); - - /** - * Returns a superset of all race log events that were added to this race log but not yet returned to - * the client with ID clientId by this method. In general, the list returned is not a true - * superset but equals exactly those events not yet delivered to the client. However, if the server - * was re-started since the client last called this method, and since the underlying data structures - * are not durably stored, the entire set of all race log events would be delivered to the client once. - */ - Iterable getEventsToDeliver(UUID clientId); - - /** - * Returns all {@link #getRawFixes() raw fixes} and marks them as delivered to the client identified by clientId - * so that when that ID appears in a subsequent call to {@link #add(RaceLogEvent, UUID)}, the fixes returned by this call - * are already considered delivered to the client identified by clientId. - */ - Iterable getRawFixes(UUID clientId); - - /** - * Like {@link #add(RaceLogEvent)}, only that no events are triggered. Use this method only when loading a race log, - * e.g., from a replication or master data import or when loading from the database. - * - * @return true if the event was actually added which is the case if there was no equal event contained - * in this race log yet - */ - boolean load(RaceLogEvent event); - - /** - * Merges all events from the other race log into this. - */ - void merge(RaceLog other); -} +package com.sap.sailing.domain.racelog; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.NavigableSet; +import java.util.UUID; + +import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; +import com.sap.sailing.domain.racelog.impl.RaceLogEventComparator; +import com.sap.sailing.domain.tracking.Track; + +/** + * Special kind of {@link Track} for recording {@link RaceLogEvent}s. + * + *

+ * Keeps track of the {@link RaceLogEvent}'s pass and returns only the events of the current pass on + * {@link RaceLog#getFixes()}. Use {@link RaceLog#getRawFixes()} to receive all events in a {@link RaceLog}. + *

+ * + *

+ * Implementations should use the {@link RaceLogEventComparator} for sorting its content. + *

+ */ +public interface RaceLog extends Track, WithID { + + public static final int DefaultPassId = 0; + + /** + * Gets the current pass id. + * + * @return the pass id. + */ + int getCurrentPassId(); + + /** + * Adds a {@link RaceLogEvent} to the {@link RaceLog}. + * + * @param event + * {@link RaceLogEvent} to be added. + * @return true if the element was added, false otherwise. + */ + boolean add(RaceLogEvent event); + + /** + * Add a {@link RaceLogEventVisitor} as a listener for additions. + */ + void addListener(RaceLogEventVisitor listener); + + /** + * Remove a listener. + */ + void removeListener(RaceLogEventVisitor listener); + + /** + * Removes all listeners + * @return + */ + HashSet removeAllListeners(); + + /** + * Checks if the race log is empty. + */ + boolean isEmpty(); + + Iterable getRawFixesDescending(); + + Iterable getFixesDescending(); + + void addAllListeners(HashSet listeners); + + Iterable getAllListeners(); + + /** + * Adds an event to this race log and returns {@link RaceLog#getEventsToDeliver(UUID)} + * (excluding the new event) + */ + Iterable add(RaceLogEvent event, UUID clientId); + + /** + * Returns a superset of all race log events that were added to this race log but not yet returned to + * the client with ID clientId by this method. In general, the list returned is not a true + * superset but equals exactly those events not yet delivered to the client. However, if the server + * was re-started since the client last called this method, and since the underlying data structures + * are not durably stored, the entire set of all race log events would be delivered to the client once. + */ + Iterable getEventsToDeliver(UUID clientId); + + /** + * Returns all {@link #getRawFixes() raw fixes} and marks them as delivered to the client identified by clientId + * so that when that ID appears in a subsequent call to {@link #add(RaceLogEvent, UUID)}, the fixes returned by this call + * are already considered delivered to the client identified by clientId. + */ + Iterable getRawFixes(UUID clientId); + + /** + * Like {@link #add(RaceLogEvent)}, only that no events are triggered. Use this method only when loading a race log, + * e.g., from a replication or master data import or when loading from the database. + * + * @return true if the event was actually added which is the case if there was no equal event contained + * in this race log yet + */ + boolean load(RaceLogEvent event); + + /** + * Search for the event by its {@link RaceLogEvent#getId() id}. + * Caller needs to hold the read lock. + */ + RaceLogEvent getEventById(Serializable id); + + /** + * Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}. + * @return + */ + NavigableSet getUnrevokedEvents(); + + /** + * Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}. + * @return + */ + NavigableSet getUnrevokedEventsDescending(); + + /** + * Inserts a {@link RevokeEvent} for {@code toRevoke}, if latter is revokable, exists in the racelog + * and has not yet been revoked. + * + * @param author The author for the {@code RevokeEvent}. + */ + RevokeEvent revokeEvent(RaceLogEventAuthor author, RaceLogEvent toRevoke) throws NotRevokableException; + + /** + * Merges all events from the other race log into this. + */ + void merge(RaceLog other); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventAuthor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventAuthor.java index c69dd7a2eb6..07f9cda357e 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventAuthor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventAuthor.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.racelog; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; /** * Multiple devices may concurrently post to a single {@link RaceLog}. In this case it is important to understand diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java index 7cf001ce9a5..26ec9195352 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java @@ -3,13 +3,23 @@ package com.sap.sailing.domain.racelog; import java.io.Serializable; import java.util.List; +import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.impl.RaceLogEventFactoryImpl; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; import com.sap.sailing.domain.tracking.Wind; public interface RaceLogEventFactory { @@ -88,4 +98,23 @@ public interface RaceLogEventFactory { RaceLogWindFixEvent createWindFixEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, List competitors, int passId, Wind wind); + DeviceCompetitorMappingEvent createDeviceCompetitorMappingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + DeviceIdentifier device, Competitor mappedTo, int passId, TimePoint from, TimePoint to); + + DeviceMarkMappingEvent createDeviceMarkMappingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + DeviceIdentifier device, Mark mappedTo, int passId, TimePoint from, TimePoint to); + + DenoteForTrackingEvent createDenoteForTrackingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, + String raceName, BoatClass boatClass, Serializable raceId); + + StartTrackingEvent createStartTrackingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId); + + RevokeEvent createRevokeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Serializable revokedEventId); + + RegisterCompetitorEvent createRegisterCompetitorEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Competitor competitor); + + DefineMarkEvent createDefineMarkEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Mark mark); + + CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, + Serializable deviceMappingEventId, TimePoint closingTimePoint); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java index b277143e39c..9c73db4e60e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java @@ -3,13 +3,23 @@ package com.sap.sailing.domain.racelog; import java.io.Serializable; import java.util.List; +import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.impl.RaceLogEventRestoreFactoryImpl; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; import com.sap.sailing.domain.tracking.Wind; public interface RaceLogEventRestoreFactory extends RaceLogEventFactory { @@ -56,4 +66,28 @@ public interface RaceLogEventRestoreFactory extends RaceLogEventFactory { RaceLogWindFixEvent createWindFixEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, List competitors, Integer passId, Wind wind); + + DeviceCompetitorMappingEvent createDeviceCompetitorMappingEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, DeviceIdentifier device, Competitor mappedTo, int passId, TimePoint from, TimePoint to); + + DeviceMarkMappingEvent createDeviceMarkMappingEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, DeviceIdentifier device, Mark mappedTo, int passId, TimePoint from, TimePoint to); + + DenoteForTrackingEvent createDenoteForTrackingEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int passId, String raceName, BoatClass boatClass, Serializable raceId); + + StartTrackingEvent createStartTrackingEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int passId); + + RevokeEvent createRevokeEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int passId, Serializable revokedEventId); + + RegisterCompetitorEvent createRegisterCompetitorEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int passId, Competitor competitor); + + DefineMarkEvent createDefineMarkEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int passId, Mark mark); + + CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int passId, Serializable deviceMappingEventId, TimePoint closingTimePoint); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventVisitor.java index 45f58b5f642..21f4c7f52c4 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventVisitor.java @@ -1,5 +1,13 @@ package com.sap.sailing.domain.racelog; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; + public interface RaceLogEventVisitor { public void visit(RaceLogFlagEvent event); @@ -27,4 +35,20 @@ public interface RaceLogEventVisitor { public void visit(RaceLogProtestStartTimeEvent event); public void visit(RaceLogWindFixEvent event); + + public void visit(DeviceCompetitorMappingEvent event); + + public void visit(DeviceMarkMappingEvent event); + + public void visit(DenoteForTrackingEvent event); + + public void visit(StartTrackingEvent event); + + public void visit(RevokeEvent event); + + public void visit(RegisterCompetitorEvent event); + + public void visit(DefineMarkEvent event); + + public void visit(CloseOpenEndedDeviceMappingEvent event); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogGateLineOpeningTimeEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogGateLineOpeningTimeEvent.java index 74d202cb0fa..40f0a0ccf39 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogGateLineOpeningTimeEvent.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogGateLineOpeningTimeEvent.java @@ -1,11 +1,11 @@ package com.sap.sailing.domain.racelog; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public interface RaceLogGateLineOpeningTimeEvent extends RaceLogEvent { - public class GateLineOpeningTimes extends Pair { + public class GateLineOpeningTimes extends Util.Pair { private static final long serialVersionUID = -3262303047719105275L; public GateLineOpeningTimes(long gateLaunchStopTime, long golfDownTime) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/Revokable.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/Revokable.java new file mode 100644 index 00000000000..e9671fb7abf --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/Revokable.java @@ -0,0 +1,18 @@ +package com.sap.sailing.domain.racelog; + + +/** + * Any {@link RaceLogEvent} marked as {@code Revokable} can be revoked by a {@link RevokeEvent}. + * This means that if a {@code RevokeEvent} {@code r} which revokes an already existing event {@code e} + * is added to the {@code RaceLog}, then the revoked event {@code e} will subsequently not appear in + * the event-iterators of the {@code RaceLog} ({@link RaceLog#getUnrevokedEvents()}, + * {@link RaceLog#getUnrevokedEventsDescending()}). + *

+ * The event {@code r} can only successufully revoke {@code e}, if {@code r}'s priority is higher + * (author prio, timepoint), and if {@code e} is of an event type that implements {@code Revokable}. + * @author Fredrik Teschke + * + */ +public interface Revokable extends RaceLogEvent { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RevokeEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RevokeEvent.java new file mode 100644 index 00000000000..23a87ff845a --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RevokeEvent.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.racelog; + +import java.io.Serializable; + +/** + * Revokes the event referenced by {@link #getRevokedEventId()}. + * A {@link RevokeEvent} cannot itself be revoked. Instead, e.g. the original event should be resubmitted. + * @author Fredrik Teschke + * @see Revokable + * + */ +public interface RevokeEvent extends RaceLogEvent { + Serializable getRevokedEventId(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/LastPublishedCourseDesignFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/LastPublishedCourseDesignFinder.java index c700b59c17a..9ee61de7fba 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/LastPublishedCourseDesignFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/LastPublishedCourseDesignFinder.java @@ -4,7 +4,6 @@ import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.racelog.RaceLogEvent; -import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; public class LastPublishedCourseDesignFinder extends RaceLogAnalyzer { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RRS26StartModeFlagFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RRS26StartModeFlagFinder.java index 9c04c01f70f..9ad54b69cf1 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RRS26StartModeFlagFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RRS26StartModeFlagFinder.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.racelog.analyzing.impl; -import java.util.List; import java.util.Arrays; +import java.util.List; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RacingProcedureType; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RaceStatusAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RaceStatusAnalyzer.java index 1d296cbd3cd..2722d93643b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RaceStatusAnalyzer.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RaceStatusAnalyzer.java @@ -8,7 +8,9 @@ import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.impl.BaseRaceLogEventVisitor; +import com.sap.sailing.domain.racelog.impl.RaceLogRaceStatusEventComparator; import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; +import com.sap.sailing.util.impl.ArrayListNavigableSet; public class RaceStatusAnalyzer extends RaceLogAnalyzer { @@ -38,13 +40,17 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer { @Override protected RaceLogRaceStatus performAnalysis() { - for (RaceLogEvent event : getPassEventsDescending()) { - if (event instanceof RaceLogRaceStatusEvent) { - RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; - statusEvent.accept(eventDispatcher); - return eventDispatcher.nextStatus; + ArrayListNavigableSet statusEvents = new ArrayListNavigableSet(RaceLogRaceStatusEventComparator.INSTANCE); + for(RaceLogEvent event: getPassEvents()) { + if(event instanceof RaceLogRaceStatusEvent) { + statusEvents.add((RaceLogRaceStatusEvent) event); } } + for (RaceLogRaceStatusEvent event : statusEvents.descendingSet()) { + RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; + statusEvent.accept(eventDispatcher); + return eventDispatcher.nextStatus; + } return RaceLogRaceStatus.UNSCHEDULED; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java index bcd55c81e54..16bca0b04ec 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java @@ -14,6 +14,14 @@ import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; /** * Base class implementing {@link RaceLogEventVisitor}. Every method is implemented as a no-op. @@ -84,5 +92,44 @@ public abstract class BaseRaceLogEventVisitor implements RaceLogEventVisitor { public void visit(RaceLogWindFixEvent event) { } + + @Override + public void visit(DeviceCompetitorMappingEvent event) { + + } + + @Override + public void visit(DeviceMarkMappingEvent event) { + + } + + @Override + public void visit(DenoteForTrackingEvent event) { + + } + + @Override + public void visit(StartTrackingEvent event) { + + } + + @Override + public void visit(RevokeEvent event) { + + } + + @Override + public void visit(RegisterCompetitorEvent event) { + } + + @Override + public void visit(DefineMarkEvent event) { + + } + + @Override + public void visit(CloseOpenEndedDeviceMappingEvent event) { + + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/CompetitorResultsImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/CompetitorResultsImpl.java index 3eed3b472d8..66c8326d61c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/CompetitorResultsImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/CompetitorResultsImpl.java @@ -4,10 +4,10 @@ import java.io.Serializable; import java.util.ArrayList; import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.racelog.CompetitorResults; +import com.sap.sse.common.Util; -public class CompetitorResultsImpl extends ArrayList> implements CompetitorResults { +public class CompetitorResultsImpl extends ArrayList> implements CompetitorResults { private static final long serialVersionUID = 4928351242700897387L; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/NoAddingRaceLogWrapper.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/NoAddingRaceLogWrapper.java index 4a29b026ce7..e4c8d47e797 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/NoAddingRaceLogWrapper.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/NoAddingRaceLogWrapper.java @@ -4,15 +4,19 @@ import java.io.Serializable; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.NavigableSet; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import com.sap.sailing.domain.common.Duration; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.RevokeEvent; /** * Wrapper for a {@link RaceLog} which will ignore all calls trying to add an {@link RaceLogEvent}. All other @@ -195,12 +199,17 @@ public class NoAddingRaceLogWrapper implements RaceLog { public Iterable getEventsToDeliver(UUID clientId) { return innerRaceLog.getEventsToDeliver(clientId); } - + @Override public Iterable getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive) { return innerRaceLog.getFixes(from, fromInclusive, to, toInclusive); } + @Override + public NavigableSet getUnrevokedEvents() { + return innerRaceLog.getUnrevokedEvents(); + } + @Override public Duration getAverageIntervalBetweenFixes() { return innerRaceLog.getAverageIntervalBetweenFixes(); @@ -211,8 +220,22 @@ public class NoAddingRaceLogWrapper implements RaceLog { return innerRaceLog.getAverageIntervalBetweenRawFixes(); } + @Override + public NavigableSet getUnrevokedEventsDescending() { + return innerRaceLog.getUnrevokedEventsDescending(); + } + + public RaceLogEvent getEventById(Serializable id) { + return innerRaceLog.getEventById(id); + } + @Override public void merge(RaceLog other) { innerRaceLog.merge(other); } + + @Override + public RevokeEvent revokeEvent(RaceLogEventAuthor author, RaceLogEvent toRevoke) throws NotRevokableException { + return innerRaceLog.revokeEvent(author, toRevoke); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java index 59e8d6fbd44..d3e57ea8001 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java @@ -15,6 +15,14 @@ import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; public class RaceLogChangedVisitor implements RaceLogEventVisitor { @@ -83,4 +91,43 @@ public class RaceLogChangedVisitor implements RaceLogEventVisitor { listener.eventAdded(event); } + @Override + public void visit(DeviceCompetitorMappingEvent event) { + listener.eventAdded(event); + } + + @Override + public void visit(DeviceMarkMappingEvent event) { + listener.eventAdded(event); + } + + @Override + public void visit(DenoteForTrackingEvent event) { + listener.eventAdded(event); + } + + @Override + public void visit(StartTrackingEvent event) { + listener.eventAdded(event); + } + + @Override + public void visit(RevokeEvent event) { + listener.eventAdded(event); + } + + @Override + public void visit(RegisterCompetitorEvent event) { + listener.eventAdded(event); + } + + @Override + public void visit(DefineMarkEvent event) { + listener.eventAdded(event); + } + + @Override + public void visit(CloseOpenEndedDeviceMappingEvent event) { + listener.eventAdded(event); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventAuthorImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventAuthorImpl.java index 5bdfe31a1de..5eace2388eb 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventAuthorImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventAuthorImpl.java @@ -23,7 +23,9 @@ public class RaceLogEventAuthorImpl extends NamedImpl implements RaceLogEventAut @Override public int compareTo(RaceLogEventAuthor other) { - return new Integer(other.getPriority()).compareTo(getPriority()); + /** as '0' has a higher priority than '1' the compare method ranks a '0' priority higher than a '1' priority */ + Integer result = new Integer(getPriority()).compareTo(other.getPriority()); + return -result; } @Override diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java index 6ca7b073a19..6838ddef26b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java @@ -1,203 +1,271 @@ -package com.sap.sailing.domain.racelog.impl; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.racelog.Flags; -import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -import com.sap.sailing.domain.common.racelog.RacingProcedureType; -import com.sap.sailing.domain.racelog.CompetitorResults; -import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogEventAuthor; -import com.sap.sailing.domain.racelog.RaceLogEventFactory; -import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; -import com.sap.sailing.domain.racelog.RaceLogFlagEvent; -import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; -import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent; -import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent; -import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; -import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; -import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; -import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; -import com.sap.sailing.domain.tracking.Wind; - -public class RaceLogEventFactoryImpl implements RaceLogEventFactory { - - @Override - public RaceLogFlagEvent createFlagEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, - List involvedBoats, int passId, Flags upperFlag, Flags lowerFlag, boolean isDisplayed) { - return new RaceLogFlagEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, involvedBoats, passId, - upperFlag, lowerFlag, isDisplayed); - } - - @Override - public RaceLogFlagEvent createFlagEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Flags upperFlag, - Flags lowerFlag, boolean isDisplayed) { - return createFlagEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, upperFlag, - lowerFlag, isDisplayed); - } - - @Override - public RaceLogStartTimeEvent createStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - Serializable id, List involvedBoats, int passId, TimePoint startTime) { - return new RaceLogStartTimeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, involvedBoats, - passId, startTime); - } - - @Override - public RaceLogStartTimeEvent createStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, TimePoint startTime) { - return createStartTimeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, startTime); - } - - @Override - public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - Serializable id, List competitors, int passId, RaceLogRaceStatus nextStatus) { - return new RaceLogRaceStatusEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, - passId, nextStatus); - } - - @Override - public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, RaceLogRaceStatus nextStatus) { - return createRaceStatusEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, nextStatus); - } - - @Override - public RaceLogPassChangeEvent createPassChangeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - Serializable id, List competitors, int passId) { - return new RaceLogPassChangeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId); - } - - @Override - public RaceLogPassChangeEvent createPassChangeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId) { - return createPassChangeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId); - } - - @Override - public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - Serializable id, List competitors, int passId, Serializable courseAreaId) { - return new RaceLogCourseAreaChangeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, - passId, courseAreaId); - } - - @Override - public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - int passId, Serializable courseAreaId) { - return createCourseAreaChangedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), - passId, courseAreaId); - } - - @Override - public RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - Serializable id, List competitors, int passId, CourseBase courseData) { - return new RaceLogCourseDesignChangedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, - passId, courseData); - } - - @Override - public RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - int passId, CourseBase courseData) { - return createCourseDesignChangedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), - passId, courseData); - } - - @Override - public RaceLogFinishPositioningListChangedEventImpl createFinishPositioningListChangedEvent(TimePoint logicalTimePoint, - RaceLogEventAuthor author, Serializable id, List competitors, - int passId, CompetitorResults positionedCompetitors) { - return new RaceLogFinishPositioningListChangedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, - id, competitors, passId, positionedCompetitors); - } - - @Override - public RaceLogFinishPositioningListChangedEventImpl createFinishPositioningListChangedEvent(TimePoint logicalTimePoint, - RaceLogEventAuthor author, int passId, CompetitorResults positionedCompetitors) { - return createFinishPositioningListChangedEvent(logicalTimePoint, author, UUID.randomUUID(), - new ArrayList(), passId, positionedCompetitors); - } - - @Override - public RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint logicalTimePoint, - RaceLogEventAuthor author, Serializable id, List competitors, int passId, CompetitorResults positionedCompetitors) { - return new RaceLogFinishPositioningConfirmedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, - competitors, passId, positionedCompetitors); - } - - @Override - public RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - int passId, CompetitorResults positionedCompetitors) { - return createFinishPositioningConfirmedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, positionedCompetitors); - } - - @Override - public RaceLogPathfinderEvent createPathfinderEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - Serializable id, List competitors, int passId, String pathfinderId) { - return new RaceLogPathfinderEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, - passId, pathfinderId); - } - - @Override - public RaceLogPathfinderEvent createPathfinderEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, String pathfinderId) { - return createPathfinderEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, pathfinderId); - } - - @Override - public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - Serializable id, List competitors, int passId, Long gateLaunchStopTime, Long golfDownTime) { - return new RaceLogGateLineOpeningTimeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, - passId, gateLaunchStopTime, golfDownTime); - } - - @Override - public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - int passId, Long gateLaunchStopTime, Long golfDownTime) { - return createGateLineOpeningTimeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), - passId, gateLaunchStopTime, golfDownTime); - } - - @Override - public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - Serializable id, List competitors, int passId, RacingProcedureType type) { - return new RaceLogStartProcedureChangedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, - competitors, passId, type); - } - - @Override - public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - int passId, RacingProcedureType type) { - return createStartProcedureChangedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, type); - } - - @Override - public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - Serializable id, List competitors, int passId, TimePoint protestStartTime) { - return new RaceLogProtestStartTimeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, - competitors, passId, protestStartTime); - } - - @Override - public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, - int passId, TimePoint protestStartTime) { - return createProtestStartTimeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), - passId, protestStartTime); - } - - @Override - public RaceLogWindFixEvent createWindFixEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Wind wind) { - return createWindFixEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, wind); - } - - @Override - public RaceLogWindFixEvent createWindFixEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, - List competitors, int passId, Wind wind) { - return new RaceLogWindFixEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId, wind); - } - -} +package com.sap.sailing.domain.racelog.impl; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.CompetitorResults; +import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; +import com.sap.sailing.domain.racelog.RaceLogFlagEvent; +import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent; +import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent; +import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.events.CloseOpenEndedDeviceMappingEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.DefineMarkEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.DenoteForTrackingEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.DeviceCompetitorMappingEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.DeviceMarkMappingEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.RegisterCompetitorEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.RevokeEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.StartTrackingEventImpl; +import com.sap.sailing.domain.tracking.Wind; + +public class RaceLogEventFactoryImpl implements RaceLogEventFactory { + + @Override + public RaceLogFlagEvent createFlagEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, + List involvedBoats, int passId, Flags upperFlag, Flags lowerFlag, boolean isDisplayed) { + return new RaceLogFlagEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, involvedBoats, passId, + upperFlag, lowerFlag, isDisplayed); + } + + @Override + public RaceLogFlagEvent createFlagEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Flags upperFlag, + Flags lowerFlag, boolean isDisplayed) { + return createFlagEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, upperFlag, + lowerFlag, isDisplayed); + } + + @Override + public RaceLogStartTimeEvent createStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List involvedBoats, int passId, TimePoint startTime) { + return new RaceLogStartTimeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, involvedBoats, + passId, startTime); + } + + @Override + public RaceLogStartTimeEvent createStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, TimePoint startTime) { + return createStartTimeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, startTime); + } + + @Override + public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, RaceLogRaceStatus nextStatus) { + return new RaceLogRaceStatusEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, nextStatus); + } + + @Override + public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, RaceLogRaceStatus nextStatus) { + return createRaceStatusEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, nextStatus); + } + + @Override + public RaceLogPassChangeEvent createPassChangeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId) { + return new RaceLogPassChangeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId); + } + + @Override + public RaceLogPassChangeEvent createPassChangeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId) { + return createPassChangeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId); + } + + @Override + public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, Serializable courseAreaId) { + return new RaceLogCourseAreaChangeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, courseAreaId); + } + + @Override + public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, Serializable courseAreaId) { + return createCourseAreaChangedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), + passId, courseAreaId); + } + + @Override + public RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, CourseBase courseData) { + return new RaceLogCourseDesignChangedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, courseData); + } + + @Override + public RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, CourseBase courseData) { + return createCourseDesignChangedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), + passId, courseData); + } + + @Override + public RaceLogFinishPositioningListChangedEventImpl createFinishPositioningListChangedEvent(TimePoint logicalTimePoint, + RaceLogEventAuthor author, Serializable id, List competitors, + int passId, CompetitorResults positionedCompetitors) { + return new RaceLogFinishPositioningListChangedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, + id, competitors, passId, positionedCompetitors); + } + + @Override + public RaceLogFinishPositioningListChangedEventImpl createFinishPositioningListChangedEvent(TimePoint logicalTimePoint, + RaceLogEventAuthor author, int passId, CompetitorResults positionedCompetitors) { + return createFinishPositioningListChangedEvent(logicalTimePoint, author, UUID.randomUUID(), + new ArrayList(), passId, positionedCompetitors); + } + + @Override + public RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint logicalTimePoint, + RaceLogEventAuthor author, Serializable id, List competitors, int passId, CompetitorResults positionedCompetitors) { + return new RaceLogFinishPositioningConfirmedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, + competitors, passId, positionedCompetitors); + } + + @Override + public RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, CompetitorResults positionedCompetitors) { + return createFinishPositioningConfirmedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, positionedCompetitors); + } + + @Override + public RaceLogPathfinderEvent createPathfinderEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, String pathfinderId) { + return new RaceLogPathfinderEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, pathfinderId); + } + + @Override + public RaceLogPathfinderEvent createPathfinderEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, String pathfinderId) { + return createPathfinderEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, pathfinderId); + } + + @Override + public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, Long gateLaunchStopTime, Long golfDownTime) { + return new RaceLogGateLineOpeningTimeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, gateLaunchStopTime, golfDownTime); + } + + @Override + public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, Long gateLaunchStopTime, Long golfDownTime) { + return createGateLineOpeningTimeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), + passId, gateLaunchStopTime, golfDownTime); + } + + @Override + public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, RacingProcedureType type) { + return new RaceLogStartProcedureChangedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, + competitors, passId, type); + } + + @Override + public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, RacingProcedureType type) { + return createStartProcedureChangedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, type); + } + + @Override + public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, TimePoint protestStartTime) { + return new RaceLogProtestStartTimeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, + competitors, passId, protestStartTime); + } + + @Override + public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, TimePoint protestStartTime) { + return createProtestStartTimeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), + passId, protestStartTime); + } + + @Override + public RaceLogWindFixEvent createWindFixEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Wind wind) { + return createWindFixEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, wind); + } + + @Override + public RaceLogWindFixEvent createWindFixEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, + List competitors, int passId, Wind wind) { + return new RaceLogWindFixEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId, wind); + } + + @Override + public DeviceCompetitorMappingEvent createDeviceCompetitorMappingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + DeviceIdentifier device, Competitor mappedTo, int passId, TimePoint from, TimePoint to) { + return new DeviceCompetitorMappingEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, UUID.randomUUID(), + passId, mappedTo, device, from, to); + } + + @Override + public DeviceMarkMappingEvent createDeviceMarkMappingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + DeviceIdentifier device, Mark mappedTo, int passId, TimePoint from, TimePoint to) { + return new DeviceMarkMappingEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, UUID.randomUUID(), + passId, mappedTo, device, from, to); + } + + @Override + public DenoteForTrackingEvent createDenoteForTrackingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, + String raceName, BoatClass boatClass, Serializable raceId) { + return new DenoteForTrackingEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, UUID.randomUUID(), passId, raceName, + boatClass, raceId); + } + + @Override + public StartTrackingEvent createStartTrackingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId) { + return new StartTrackingEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, UUID.randomUUID(), passId); + } + + @Override + public RevokeEvent createRevokeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Serializable revokedEventId) { + return new RevokeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, UUID.randomUUID(), passId, revokedEventId); + } + + @Override + public RegisterCompetitorEvent createRegisterCompetitorEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, Competitor competitor) { + return new RegisterCompetitorEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, UUID.randomUUID(), passId, competitor); + } + + @Override + public DefineMarkEvent createDefineMarkEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, Mark mark) { + return new DefineMarkEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, UUID.randomUUID(), passId, mark); + } + + @Override + public CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint logicalTimePoint, + RaceLogEventAuthor author, int passId, Serializable deviceMappingEventId, TimePoint closingTimePoint) { + return new CloseOpenEndedDeviceMappingEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, + UUID.randomUUID(), passId, deviceMappingEventId, closingTimePoint); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java index cebfa95dd57..0f61f469175 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java @@ -3,8 +3,10 @@ package com.sap.sailing.domain.racelog.impl; import java.io.Serializable; import java.util.List; +import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; @@ -25,6 +27,23 @@ import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.events.CloseOpenEndedDeviceMappingEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.DefineMarkEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.DenoteForTrackingEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.DeviceCompetitorMappingEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.DeviceMarkMappingEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.RegisterCompetitorEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.RevokeEventImpl; +import com.sap.sailing.domain.racelog.tracking.events.StartTrackingEventImpl; import com.sap.sailing.domain.tracking.Wind; public class RaceLogEventRestoreFactoryImpl extends RaceLogEventFactoryImpl implements RaceLogEventRestoreFactory { @@ -109,5 +128,54 @@ public class RaceLogEventRestoreFactoryImpl extends RaceLogEventFactoryImpl impl return new RaceLogWindFixEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, wind); } + @Override + public DeviceCompetitorMappingEvent createDeviceCompetitorMappingEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, DeviceIdentifier device, Competitor mappedTo, int passId, TimePoint from, TimePoint to) { + return new DeviceCompetitorMappingEventImpl(createdAt, author, logicalTimePoint, pId, passId, mappedTo, device, from, to); + } + @Override + public DeviceMarkMappingEvent createDeviceMarkMappingEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, DeviceIdentifier device, Mark mappedTo, int passId, TimePoint from, + TimePoint to) { + return new DeviceMarkMappingEventImpl(createdAt, author, logicalTimePoint, pId, passId, mappedTo, device, from, to); + } + + @Override + public DenoteForTrackingEvent createDenoteForTrackingEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int passId, String raceName, BoatClass boatClass, Serializable raceId) { + return new DenoteForTrackingEventImpl(createdAt, author, logicalTimePoint, pId, passId, raceName, boatClass, raceId); + } + + @Override + public StartTrackingEvent createStartTrackingEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int passId) { + return new StartTrackingEventImpl(createdAt, author, logicalTimePoint, pId, passId); + } + + @Override + public RevokeEvent createRevokeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int passId, Serializable revokedEventId) { + return new RevokeEventImpl(createdAt, author, logicalTimePoint, pId, passId, revokedEventId); + } + + @Override + public RegisterCompetitorEvent createRegisterCompetitorEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int passId, Competitor competitor) { + return new RegisterCompetitorEventImpl(createdAt, author, logicalTimePoint, pId, passId, competitor); + } + + @Override + public DefineMarkEvent createDefineMarkEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int passId, Mark mark) { + return new DefineMarkEventImpl(createdAt, author, logicalTimePoint, pId, passId, mark); + } + + @Override + public CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint createdAt, + RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable pId, int passId, + Serializable deviceMappingEventId, TimePoint closingTimePoint) { + return new CloseOpenEndedDeviceMappingEventImpl(createdAt, author, logicalTimePoint, pId, passId, + deviceMappingEventId, closingTimePoint); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java index 6e8d605cc8b..0a4a8f17e23 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java @@ -1,326 +1,438 @@ -package com.sap.sailing.domain.racelog.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.Serializable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.NavigableSet; -import java.util.Set; -import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.sap.sailing.domain.base.Timed; -import com.sap.sailing.domain.racelog.RaceLog; -import com.sap.sailing.domain.racelog.RaceLogEvent; -import com.sap.sailing.domain.racelog.RaceLogEventVisitor; -import com.sap.sailing.domain.tracking.Track; -import com.sap.sailing.domain.tracking.impl.PartialNavigableSetView; -import com.sap.sailing.domain.tracking.impl.TrackImpl; -import com.sap.sailing.util.impl.ArrayListNavigableSet; - -/** - * {@link Track} implementation for {@link RaceLogEvent}s. - * - *

- * "Fix" validity is decided based on the {@link #getCurrentPassId() current pass}. The validity is not cached. - *

- * - *

- * {@link TrackImpl#getDummyFix(com.sap.sailing.domain.common.TimePoint)} is not overridden, see - * {@link RaceLogEventComparator} for sorting when interface methods like - * {@link Track#getFirstFixAfter(com.sap.sailing.domain.common.TimePoint)} are used. - *

- * - */ -public class RaceLogImpl extends TrackImpl implements RaceLog { - private static final long serialVersionUID = -176745401321893502L; - private static final String DefaultLockName = RaceLogImpl.class.getName() + ".lock"; - private final static Logger logger = Logger.getLogger(RaceLogImpl.class.getName()); - - /** - * Clients can use the {@link #add(RaceLogEvent, UUID)} method - */ - private transient Map> eventsDeliveredToClient = new HashMap>(); - - private final Serializable id; - private transient Set listeners; - private int currentPassId; - - /** - * Initializes a new {@link RaceLogImpl} with the default lock name. - */ - public RaceLogImpl(Serializable identifier) { - this(DefaultLockName, identifier); - } - - /** - * Initializes a new {@link RaceLogImpl}. - * - * @param nameForReadWriteLock - * name of lock. - */ - public RaceLogImpl(String nameForReadWriteLock, Serializable identifier) { - super(new ArrayListNavigableSet(RaceLogEventComparator.INSTANCE), nameForReadWriteLock); - - this.listeners = new HashSet(); - this.currentPassId = DefaultPassId; - this.id = identifier; - } - - @Override - public Serializable getId() { - return this.id; - } - - @Override - public int getCurrentPassId() { - return currentPassId; - } - - /** - * Sets a new active pass id. Ignored if new and current are equal. - * - * @param newPassId - * to be set. - */ - public void setCurrentPassId(int newPassId) { - if (newPassId != this.currentPassId) { - logger.finer(String.format("Changing pass id to %d", newPassId)); - this.currentPassId = newPassId; - } - } - - @Override - public boolean add(RaceLogEvent event) { - boolean isAdded = false; - lockForWrite(); - try { - isAdded = getInternalRawFixes().add(event); - } finally { - unlockAfterWrite(); - } - if (isAdded) { - logger.finer(String.format("%s (%s) was added to log %s.", event, event.getClass().getName(), getId())); - // FIXME with out-of-order delivery would destroy currentPassId; need to check at least the createdAt time - // point - setCurrentPassId(Math.max(event.getPassId(), this.currentPassId)); - notifyListenersAboutReceive(event); - } else { - logger.warning(String.format("%s (%s) was not added to race log %s. Ignoring", event, event.getClass().getName(), getId())); - } - return isAdded; - } - - @Override - public boolean load(RaceLogEvent event) { - boolean isAdded = false; - lockForWrite(); - try { - isAdded = getInternalRawFixes().add(event); - } finally { - unlockAfterWrite(); - } - if (isAdded) { - logger.finer(String.format("%s (%s) was loaded into log.", event, event.getClass().getName())); - setCurrentPassId(Math.max(event.getPassId(), this.currentPassId)); - } else { - logger.warning(String - .format("%s (%s) was not loaded into log. Ignoring", event, event.getClass().getName())); - } - return isAdded; - } - - @Override - public Iterable add(RaceLogEvent event, UUID clientId) { - boolean isAdded = false; - lockForWrite(); - try { - isAdded = getInternalRawFixes().add(event); - } finally { - unlockAfterWrite(); - } - if (isAdded) { - logger.finer(String.format("%s (%s) was added to log.", event, event.getClass().getName())); - // FIXME with out-of-order delivery would destroy currentPassId; need to check at least the createdAt time - // point - setCurrentPassId(Math.max(event.getPassId(), this.currentPassId)); - notifyListenersAboutReceive(event); - } else { - logger.warning(String.format("%s (%s) was not added to log. Ignoring", event, event.getClass().getName())); - } - return getEventsToDeliver(clientId, event); - } - - @Override - public Iterable getEventsToDeliver(UUID clientId) { - return getEventsToDeliver(clientId, null); - } - - protected Iterable getEventsToDeliver(UUID clientId, RaceLogEvent suppressedEvent) { - final LinkedHashSet stillToDeliverToClient; - lockForRead(); - try { - stillToDeliverToClient = new LinkedHashSet(getInternalRawFixes()); - } finally { - unlockAfterRead(); - } - stillToDeliverToClient.remove(suppressedEvent); - Set deliveredToClient = eventsDeliveredToClient.get(clientId); - if (deliveredToClient != null) { - stillToDeliverToClient.removeAll(deliveredToClient); - } else { - deliveredToClient = new HashSet(); - eventsDeliveredToClient.put(clientId, deliveredToClient); - } - deliveredToClient.addAll(stillToDeliverToClient); - deliveredToClient.add(suppressedEvent); - return stillToDeliverToClient; - } - - protected void notifyListenersAboutReceive(RaceLogEvent event) { - Set workingListeners = new HashSet(); - synchronized (listeners) { - workingListeners.addAll(listeners); - } - for (RaceLogEventVisitor listener : workingListeners) { - try { - event.accept(listener); - } catch (Throwable t) { - logger.log(Level.SEVERE, "RaceLogEventVisitor " + listener + " threw exception " + t.getMessage()); - logger.throwing(RaceLogImpl.class.getName(), "notifyListenersAboutReceive(RaceLogEvent)", t); - t.printStackTrace(); - } - } - } - - @Override - public boolean isEmpty() { - return getFirstRawFix() == null; - } - - @Override - public void addListener(RaceLogEventVisitor listener) { - synchronized (listeners) { - listeners.add(listener); - } - } - - @Override - public void removeListener(RaceLogEventVisitor listener) { - synchronized (listeners) { - listeners.remove(listener); - } - } - - @Override - protected NavigableSet getInternalFixes() { - return new PartialNavigableSetView(super.getInternalFixes()) { - @Override - protected boolean isValid(RaceLogEvent e) { - return e.getPassId() == getCurrentPassId(); - } - }; - } - - /** - * When deserializing, needs to initialize empty set of listeners. - */ - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - listeners = new HashSet(); - eventsDeliveredToClient = new HashMap>(); - } - - @Override - public Iterable getRawFixesDescending() { - return getRawFixes().descendingSet(); - } - - @Override - public Iterable getFixesDescending() { - return getFixes().descendingSet(); - } - - @Override - public HashSet removeAllListeners() { - synchronized (listeners) { - HashSet clonedListeners = new HashSet(listeners); - listeners = new HashSet(); - return clonedListeners; - } - } - - @Override - public void addAllListeners(HashSet listeners) { - synchronized (listeners) { - this.listeners.addAll(listeners); - } - } - - @Override - public Iterable getAllListeners() { - return this.listeners; - } - - @Override - public Iterable getRawFixes(UUID clientId) { - assertReadLock(); - NavigableSet result = getRawFixes(); - Set edtc = eventsDeliveredToClient.get(clientId); - if (edtc == null) { - edtc = new HashSet(); - eventsDeliveredToClient.put(clientId, edtc); - } - edtc.addAll(result); - return result; - } - - @Override - public void merge(RaceLog other) { - lockForWrite(); - other.lockForRead(); - try { - RaceLogEventComparator comparator = RaceLogEventComparator.INSTANCE; - Iterator thisIter = getRawFixes().iterator(); - Iterator otherIter = other.getRawFixes().iterator(); - RaceLogEvent thisEvent = null; - RaceLogEvent otherEvent = null; - while (otherIter.hasNext() || otherEvent != null) { - if (thisEvent == null && thisIter.hasNext()) { - thisEvent = thisIter.next(); - } - if (otherEvent == null) { - otherEvent = otherIter.next(); - } - if (thisEvent == null) { - // All events of this race log have been consumed; simply keep adding the events - // from the other race log to this race log. - // otherEvent has to be non-null because if thisIter didn't have a next, otherIter must have had a next - add(otherEvent); - otherEvent = null; // "consumed" otherEvent; try to grab next if a next element exists in otherIter - } else { - final int comparison = comparator.compare(thisEvent, otherEvent); - if (comparison < 0) { - thisEvent = null; // skip the "lesser" race log event on this race log - } else if (comparison == 0) { - // the race log event from the other log is already contained in this log; skip both - thisEvent = null; - otherEvent = null; - } else { - // comparison > 0; we skipped on this race log until we found a "greater" event on this race log; insert otherEvent - add(otherEvent); - otherEvent = null; // "consumed" - } - } - } - } finally { - other.unlockAfterRead(); - unlockAfterWrite(); - } - } -} +package com.sap.sailing.domain.racelog.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.Revokable; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.tracking.Track; +import com.sap.sailing.domain.tracking.impl.PartialNavigableSetView; +import com.sap.sailing.domain.tracking.impl.TrackImpl; +import com.sap.sailing.util.impl.ArrayListNavigableSet; + +/** + * {@link Track} implementation for {@link RaceLogEvent}s. + * + *

+ * "Fix" validity is decided based on the {@link #getCurrentPassId() current pass}. The validity is not cached. + *

+ * + *

+ * {@link TrackImpl#getDummyFix(com.sap.sailing.domain.common.TimePoint)} is not overridden, see + * {@link RaceLogEventComparator} for sorting when interface methods like + * {@link Track#getFirstFixAfter(com.sap.sailing.domain.common.TimePoint)} are used. + *

+ * + */ +public class RaceLogImpl extends TrackImpl implements RaceLog { + private static final long serialVersionUID = -176745401321893502L; + private static final String DefaultLockName = RaceLogImpl.class.getName() + ".lock"; + private final static Logger logger = Logger.getLogger(RaceLogImpl.class.getName()); + private Set revokedEventIds = new HashSet(); + + /** + * Clients can use the {@link #add(RaceLogEvent, UUID)} method + */ + private transient Map> eventsDeliveredToClient = new HashMap>(); + + private Map eventsById = new HashMap(); + + private final Serializable id; + private transient Set listeners; + private int currentPassId; + + /** + * Initializes a new {@link RaceLogImpl} with the default lock name. + */ + public RaceLogImpl(Serializable identifier) { + this(DefaultLockName, identifier); + } + + /** + * Initializes a new {@link RaceLogImpl}. + * + * @param nameForReadWriteLock + * name of lock. + */ + public RaceLogImpl(String nameForReadWriteLock, Serializable identifier) { + super(new ArrayListNavigableSet(RaceLogEventComparator.INSTANCE), nameForReadWriteLock); + + this.listeners = new HashSet(); + this.currentPassId = DefaultPassId; + this.id = identifier; + } + + @Override + public Serializable getId() { + return this.id; + } + + @Override + public int getCurrentPassId() { + return currentPassId; + } + + /** + * Sets a new active pass id. Ignored if new and current are equal. + * + * @param newPassId + * to be set. + */ + public void setCurrentPassId(int newPassId) { + if (newPassId != this.currentPassId) { + logger.finer(String.format("Changing pass id to %d", newPassId)); + this.currentPassId = newPassId; + } + } + + @Override + public boolean add(RaceLogEvent event) { + boolean isAdded = false; + lockForWrite(); + try { + isAdded = getInternalRawFixes().add(event); + } finally { + unlockAfterWrite(); + } + if (isAdded) { + logger.finer(String.format("%s (%s) was added to log %s.", event, event.getClass().getName(), getId())); + // FIXME with out-of-order delivery would destroy currentPassId; need to check at least the createdAt time + // point + setCurrentPassId(Math.max(event.getPassId(), this.currentPassId)); + revokeIfNecessary(event); + eventsById.put(event.getId(), event); + notifyListenersAboutReceive(event); + } else { + logger.fine(String.format("%s (%s) was not added to race log %s because it already existed there.", event, event.getClass().getName(), getId())); + } + return isAdded; + } + + @Override + public boolean load(RaceLogEvent event) { + boolean isAdded = false; + lockForWrite(); + try { + isAdded = getInternalRawFixes().add(event); + } finally { + unlockAfterWrite(); + } + if (isAdded) { + logger.finer(String.format("%s (%s) was loaded into log.", event, event.getClass().getName())); + setCurrentPassId(Math.max(event.getPassId(), this.currentPassId)); + revokeIfNecessary(event); + eventsById.put(event.getId(), event); + } else { + logger.fine(String + .format("%s (%s) was not loaded into log because it already existed there.", event, event.getClass().getName())); + } + return isAdded; + } + + private void revokeIfNecessary(RaceLogEvent newEvent) { + if (newEvent instanceof RevokeEvent) { + RevokeEvent revokeEvent = (RevokeEvent) newEvent; + try { + checkIfSuccessfullyRevokes(revokeEvent); + + lockForWrite(); + revokedEventIds.add(revokeEvent.getRevokedEventId()); + unlockAfterWrite(); + } catch (NotRevokableException e) { + logger.log(Level.WARNING, e.getMessage()); + } + } + } + + private void checkIfSuccessfullyRevokes(RevokeEvent revokeEvent) throws NotRevokableException { + lockForRead(); + RaceLogEvent revokedEvent = getEventById(revokeEvent.getRevokedEventId()); + unlockAfterRead(); + + if (revokedEvent == null) { + throw new NotRevokableException("RevokeEvent added, that refers to non-existent event to be revoked"); + } + + if (! (revokedEvent instanceof Revokable)) { + throw new NotRevokableException("RevokeEvent trying to revoke non-revokable event"); + } + + if (getInternalRawFixes().comparator().compare(revokeEvent, revokedEvent) <= 0) { + throw new NotRevokableException("RevokeEvent does not have sufficient priority"); + } + } + + @Override + public Iterable add(RaceLogEvent event, UUID clientId) { + boolean isAdded = false; + lockForWrite(); + try { + isAdded = getInternalRawFixes().add(event); + } finally { + unlockAfterWrite(); + } + if (isAdded) { + logger.finer(String.format("%s (%s) was added to log.", event, event.getClass().getName())); + // FIXME with out-of-order delivery would destroy currentPassId; need to check at least the createdAt time + // point + setCurrentPassId(Math.max(event.getPassId(), this.currentPassId)); + revokeIfNecessary(event); + notifyListenersAboutReceive(event); + } else { + logger.fine(String.format("%s (%s) was not added to log because it already eists there.", event, event.getClass().getName())); + } + return getEventsToDeliver(clientId, event); + } + + @Override + public Iterable getEventsToDeliver(UUID clientId) { + return getEventsToDeliver(clientId, null); + } + + protected Iterable getEventsToDeliver(UUID clientId, RaceLogEvent suppressedEvent) { + final LinkedHashSet stillToDeliverToClient; + lockForRead(); + try { + stillToDeliverToClient = new LinkedHashSet(getInternalRawFixes()); + } finally { + unlockAfterRead(); + } + stillToDeliverToClient.remove(suppressedEvent); + Set deliveredToClient = eventsDeliveredToClient.get(clientId); + if (deliveredToClient != null) { + stillToDeliverToClient.removeAll(deliveredToClient); + } else { + deliveredToClient = new HashSet(); + eventsDeliveredToClient.put(clientId, deliveredToClient); + } + deliveredToClient.addAll(stillToDeliverToClient); + deliveredToClient.add(suppressedEvent); + return stillToDeliverToClient; + } + + protected void notifyListenersAboutReceive(RaceLogEvent event) { + Set workingListeners = new HashSet(); + synchronized (listeners) { + workingListeners.addAll(listeners); + } + for (RaceLogEventVisitor listener : workingListeners) { + try { + event.accept(listener); + } catch (Throwable t) { + logger.log(Level.SEVERE, "RaceLogEventVisitor " + listener + " threw exception " + t.getMessage()); + logger.throwing(RaceLogImpl.class.getName(), "notifyListenersAboutReceive(RaceLogEvent)", t); + t.printStackTrace(); + } + } + } + + @Override + public boolean isEmpty() { + return getFirstRawFix() == null; + } + + @Override + public void addListener(RaceLogEventVisitor listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + @Override + public void removeListener(RaceLogEventVisitor listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + @Override + protected NavigableSet getInternalFixes() { + return new PartialNavigableSetView(super.getInternalFixes()) { + @Override + protected boolean isValid(RaceLogEvent e) { + return e.getPassId() == getCurrentPassId(); + } + }; + } + + /** + * When deserializing, needs to initialize empty set of listeners. Furthermore, as a migration effort, when the + * {@link #eventsById} field was introduced, old clients get null as its value when deserializing which + * leads to NPEs later on. However, since the map is redundant to the contents of the fixes collection, + * it can be reconstructed here. + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + listeners = new HashSet(); + eventsDeliveredToClient = new HashMap>(); + if (eventsById == null) { + eventsById = new HashMap(); + lockForRead(); + try { + for (RaceLogEvent event : getRawFixes()) { + eventsById.put(event.getId(), event); + } + } finally { + unlockAfterRead(); + } + } + if (revokedEventIds == null) { + revokedEventIds = new HashSet(); + lockForRead(); + try { + for (RaceLogEvent event : getRawFixes()) { + if (event instanceof RevokeEvent) { + revokedEventIds.add(event.getId()); + } + } + } finally { + unlockAfterRead(); + } + } + } + + @Override + public Iterable getRawFixesDescending() { + return getRawFixes().descendingSet(); + } + + @Override + public Iterable getFixesDescending() { + return getFixes().descendingSet(); + } + + @Override + public HashSet removeAllListeners() { + synchronized (listeners) { + HashSet clonedListeners = new HashSet(listeners); + listeners = new HashSet(); + return clonedListeners; + } + } + + @Override + public void addAllListeners(HashSet listeners) { + synchronized (listeners) { + this.listeners.addAll(listeners); + } + } + + @Override + public Iterable getAllListeners() { + return this.listeners; + } + + @Override + public Iterable getRawFixes(UUID clientId) { + assertReadLock(); + NavigableSet result = getRawFixes(); + Set edtc = eventsDeliveredToClient.get(clientId); + if (edtc == null) { + edtc = new HashSet(); + eventsDeliveredToClient.put(clientId, edtc); + } + edtc.addAll(result); + return result; + } + + @Override + public RaceLogEvent getEventById(Serializable id) { + assertReadLock(); + return eventsById.get(id); + } + + @Override + public NavigableSet getUnrevokedEvents() { + return new PartialNavigableSetView(super.getInternalFixes()) { + @Override + protected boolean isValid(RaceLogEvent e) { + return ! (e instanceof RevokeEvent) && ! revokedEventIds.contains(e.getId()); + } + }; + } + + @Override + public NavigableSet getUnrevokedEventsDescending() { + return new PartialNavigableSetView(super.getInternalFixes().descendingSet()) { + @Override + protected boolean isValid(RaceLogEvent e) { + return ! (e instanceof RevokeEvent) && ! revokedEventIds.contains(e.getId()); + } + }; + } + + @Override + public void merge(RaceLog other) { + lockForWrite(); + other.lockForRead(); + try { + RaceLogEventComparator comparator = RaceLogEventComparator.INSTANCE; + Iterator thisIter = getRawFixes().iterator(); + Iterator otherIter = other.getRawFixes().iterator(); + RaceLogEvent thisEvent = null; + RaceLogEvent otherEvent = null; + while (otherIter.hasNext() || otherEvent != null) { + if (thisEvent == null && thisIter.hasNext()) { + thisEvent = thisIter.next(); + } + if (otherEvent == null) { + otherEvent = otherIter.next(); + } + if (thisEvent == null) { + // All events of this race log have been consumed; simply keep adding the events + // from the other race log to this race log. + // otherEvent has to be non-null because if thisIter didn't have a next, otherIter must have had a next + add(otherEvent); + otherEvent = null; // "consumed" otherEvent; try to grab next if a next element exists in otherIter + } else { + final int comparison = comparator.compare(thisEvent, otherEvent); + if (comparison < 0) { + thisEvent = null; // skip the "lesser" race log event on this race log + } else if (comparison == 0) { + // the race log event from the other log is already contained in this log; skip both + thisEvent = null; + otherEvent = null; + } else { + // comparison > 0; we skipped on this race log until we found a "greater" event on this race log; insert otherEvent + add(otherEvent); + otherEvent = null; // "consumed" + } + } + } + } finally { + other.unlockAfterRead(); + unlockAfterWrite(); + } + } + + @Override + public RevokeEvent revokeEvent(RaceLogEventAuthor author, RaceLogEvent toRevoke) throws NotRevokableException { + if (toRevoke == null) { + throw new NotRevokableException("Received null as event to revoke"); + } + RevokeEvent revokeEvent = RaceLogEventFactory.INSTANCE.createRevokeEvent(MillisecondsTimePoint.now(), author, + getCurrentPassId(), toRevoke.getId()); + checkIfSuccessfullyRevokes(revokeEvent); + add(revokeEvent); + return revokeEvent; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogRaceStatusEventComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogRaceStatusEventComparator.java new file mode 100644 index 00000000000..2685400c6e4 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogRaceStatusEventComparator.java @@ -0,0 +1,49 @@ +package com.sap.sailing.domain.racelog.impl; + +import java.io.Serializable; +import java.util.Comparator; + +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; + +/** + * Comparator sorting by pass, then by state order number, then by {@link RaceLogEventAuthor}, then by {@link RaceLogEvent#getCreatedAt()} timestamp. + */ +public enum RaceLogRaceStatusEventComparator implements Comparator, Serializable { + INSTANCE; + + private RaceLogRaceStatusEventComparator() { + } + + @Override + public int compare(RaceLogRaceStatusEvent e1, RaceLogRaceStatusEvent e2) { + int result = comparePasses(e1, e2); + return result == 0 ? compareRaceStatus(e1, e2) : result; + } + + private int comparePasses(RaceLogRaceStatusEvent e1, RaceLogRaceStatusEvent e2) { + return e1.getPassId() - e2.getPassId(); + } + + private int compareRaceStatus(RaceLogRaceStatusEvent e1, RaceLogRaceStatusEvent e2) { + int result = e1.getNextStatus().getOrderNumber() - e2.getNextStatus().getOrderNumber(); + + return result == 0 ? compareAuthorPriorities(e1, e2) : result; + } + + private int compareAuthorPriorities(RaceLogRaceStatusEvent e1, RaceLogRaceStatusEvent e2) { + int result = e1.getAuthor().compareTo(e2.getAuthor()); + return result == 0 ? compareCreatedAtTimes(e1, e2) : result; + } + + private int compareCreatedAtTimes(RaceLogRaceStatusEvent e1, RaceLogRaceStatusEvent e2) { + int result = e1.getCreatedAt().compareTo(e2.getCreatedAt()); + return result == 0 ? compareIds(e1, e2) : result; + } + + private int compareIds(RaceLogRaceStatusEvent e1, RaceLogRaceStatusEvent e2) { + return e1.getId().toString().compareTo(e2.getId().toString()); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java index f1158dbda02..dcd929c3e60 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java @@ -1,8 +1,8 @@ package com.sap.sailing.domain.racelog.state.impl; import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.base.configuration.ConfigurationLoader; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.racelog.Flags; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java index 7d9e70c1ef9..75133ffeb0b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java @@ -5,7 +5,6 @@ import com.sap.sailing.domain.base.configuration.ConfigurationLoader; import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.CompetitorResults; @@ -33,6 +32,7 @@ import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureFacto import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; import com.sap.sailing.domain.racelog.state.racingprocedure.impl.ReadonlyRacingProcedureFactory; import com.sap.sailing.domain.tracking.Wind; +import com.sap.sse.common.Util; /** * Implementation of {@link ReadonlyRaceState}. Use the static factory methods to instantiate your race state. diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java index 0afde8fdd14..730c043ec69 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java @@ -5,7 +5,6 @@ import java.util.Collection; import com.sap.sailing.domain.base.configuration.procedures.GateStartConfiguration; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.racelog.FlagPole; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RacingProcedureType; @@ -28,6 +27,7 @@ import com.sap.sailing.domain.racelog.state.racingprocedure.gate.GateStartRacing import com.sap.sailing.domain.racelog.state.racingprocedure.impl.BaseRacingProcedure; import com.sap.sailing.domain.racelog.state.racingprocedure.impl.NoMorePrerequisite; import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedureChangedListeners; +import com.sap.sse.common.Util; public class GateStartRacingProcedureImpl extends BaseRacingProcedure implements GateStartRacingProcedure { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedureChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedureChangedListener.java index a3d0ef987e1..fa4e7e612ac 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedureChangedListener.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedureChangedListener.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.racelog.state.racingprocedure.impl; -import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; public abstract class BaseRacingProcedureChangedListener implements RacingProcedureChangedListener { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureFactoryImpl.java index 448b5fc96a5..f5e34bb8571 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureFactoryImpl.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.racelog.state.racingprocedure.impl; -import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.base.configuration.ConfigurationLoader; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEventAuthor; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/CloseOpenEndedDeviceMappingEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/CloseOpenEndedDeviceMappingEvent.java new file mode 100644 index 00000000000..02bb1b851f3 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/CloseOpenEndedDeviceMappingEvent.java @@ -0,0 +1,26 @@ +package com.sap.sailing.domain.racelog.tracking; + +import java.io.Serializable; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.Revokable; + +/** + * Closes the time range of a {@link DeviceMappingEvent}. This means that the {@link #getClosingTimePoint() timepoint} provided + * by this event is substituted for the missing timepoint in the {@code DeviceMappingEvent} it refers to. + * @author Fredrik Teschke + * + */ +public interface CloseOpenEndedDeviceMappingEvent extends RaceLogEvent, Revokable { + /** + * Returns the timepoint that shall substitute the missing one in the corresponding {@link DeviceMappingEvent}. + */ + TimePoint getClosingTimePoint(); + + /** + * Returns the {@code id} of the event for which this event provides the closing timepoint. + * @return + */ + Serializable getDeviceMappingEventId(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DefineMarkEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DefineMarkEvent.java new file mode 100644 index 00000000000..8434c919775 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DefineMarkEvent.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.Revokable; + +/** + * Defines a mark, that can then be used to build a course. + * @author Fredrik Teschke + * + */ +public interface DefineMarkEvent extends RaceLogEvent, Revokable { + Mark getMark(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DenoteForTrackingEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DenoteForTrackingEvent.java new file mode 100644 index 00000000000..afdf81449c7 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DenoteForTrackingEvent.java @@ -0,0 +1,23 @@ +package com.sap.sailing.domain.racelog.tracking; + +import java.io.Serializable; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.Revokable; + +/** + * The existence of this event in a {@link RaceLog} indicates that the necessary data + * for creating this race will be accumulated in this RaceLog. Once it is present, the + * actual {@link TrackedRace} can then be created from this information, and fixes for + * competitors and marks that arrive on the server, for which a corresponding + * {@link DeviceMappingEvent} exists will be added to the race. + * @author Fredrik Teschke + * + */ +public interface DenoteForTrackingEvent extends RaceLogEvent, Revokable { + String getRaceName(); + BoatClass getBoatClass(); + Serializable getRaceId(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceCompetitorMappingEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceCompetitorMappingEvent.java new file mode 100644 index 00000000000..a6b87fe3126 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceCompetitorMappingEvent.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.base.Competitor; + +public interface DeviceCompetitorMappingEvent extends DeviceMappingEvent { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceIdentifier.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceIdentifier.java new file mode 100644 index 00000000000..fe2290bdfda --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceIdentifier.java @@ -0,0 +1,60 @@ +package com.sap.sailing.domain.racelog.tracking; + +import java.io.Serializable; + +import com.sap.sailing.domain.racelog.RaceLog; + +/** + * Identifies any kind of tracking device. Should be implemented accordingly for different tracking + * adapters, e.g. for smartphones, Igtimi trackers etc.

+ * + * Device identifiers are integrated in such a way, that a third party wishing to use their own + * types of device identifiers can do so without touching the existing code base. Instead, handlers + * for serializing device identifiers can be registered via the OSGi service registry. When adding a + * new device identifier, the following handlers have to be implemented and registered: + *

    + *
  • {@code DeviceIdentifierMongoHandler}
  • + *
+ * The following handlers are optional: + *
    + *
  • {@code DeviceIdentifierJsonHandler}: + * Enables managing such identifiers from a smartphone or tablet through the {@link RaceLog}.
  • + *
  • {@code DeviceIdentifierStringSerializationHandler}: + * Enables device identifiers to be created and managed in the admin console.
  • + *
+ *

+ * + * Refer to {@link SmartphoneUUIDIdentifier} and the related handlers as an implementation example. + * An example on how to register the handlers in the OSGi service registry is given in + * {@link DeviceIdentifierSerializationHandler}.

+ * + * The handler is chosen based on the {@code identifier type} of the device identifier. This identifier + * type should be unique, consider using a fully qualified class name.

+ * + * The {@link #getStringRepresentation() string representation} of a device identifier is used to generate + * a {@link PlaceHolderDeviceIdentifier} in case an appropriate handler is missing.

+ * + * {@link Object#equals(Object)} and {@link Object#hashCode()} should be implemented so that multiple + * deserializations of same device identifier (conceptually) result in objects that are be identified + * as being equal through these two methods. + * @author Fredrik Teschke + * + */ +public interface DeviceIdentifier extends Serializable { + /** + * The returned {@link String} is used to look up corresponding services for serialization + * and persistence. + * + * The reason for this design choice is that in future, third parties could easily write their own adapter and only + * have to register new OSGi service, and not touch the SAP Sailing Analytics code. + */ + String getIdentifierType(); + + /** + * Create a string representation, that can identify this device. + * The returned values should be unique for this identifier within its {@link #getIdentifierType() type}, + * but need not include the {@link #getIdentifierType() type} itself in the representation. + */ + String getStringRepresentation(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceIdentifierSerializationHandler.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceIdentifierSerializationHandler.java new file mode 100644 index 00000000000..fcad12efeaf --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceIdentifierSerializationHandler.java @@ -0,0 +1,41 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sse.common.Util; + +/** + * Base interface for handlers of serialization and deserialization of {@link DeviceIdentifier}s. + * Additional information is passed around to enable fallback mechanisms (see {@link PlaceHolderDeviceIdentifier}) + * in case no appropriate handler for a {@code type} is registered.

+ * + * Handlers that implement child-interfaces (e.g. {@link DeviceIdentifierStringSerializationHandler} have to be + * registered in the OSGi service registry for the respective child-interface.Also, the device identifier type has to be + * added as a property to that service registration, as in this example:

+ * + *

+ * Dictionary properties = new Hashtable();
+ * properties.put(TypeBasedServiceFinder.TYPE, type);
+ * context.registerService(DeviceIdentifierMongoHandler.class, new SmartphoneUUIDMongoHandler(), properties)}.
+ * 

+ * + * @author Fredrik Teschke + * + */ +public interface DeviceIdentifierSerializationHandler { + /** + * Serialize the identifier. Also returns additional information, in case this is a fallback + * serializer, that actually returns some generic serializiation (e.g. {@link PlaceHolderDeviceIdentifier}). + * + * @param deviceIdentifier + * @return A pair: <{@code device type}, {@code serialized identifier}> + */ + Util.Pair serialize(DeviceIdentifier deviceIdentifier) throws TransformationException; + + /** + * Deserialize the identifier. In case this is a fallback deserializer (e.g. for {@link PlaceHolderDeviceIdentifier}), + * additional information such as {@code type} and {@code stringRepresentation} is passed in.

+ * The handler should check whether all necessary information is contained (specifically, if the {@code serialized} + * object is not {@code null}, and otherwise throw an {@link TransformationException}. + */ + DeviceIdentifier deserialize(T serialized, String type, String stringRepresentation) throws TransformationException; +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceIdentifierStringSerializationHandler.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceIdentifierStringSerializationHandler.java new file mode 100644 index 00000000000..ab9b33eeebf --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceIdentifierStringSerializationHandler.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.racelog.tracking; + + +/** + * Handle serialization to a {@code String}, for passing the identifier to the GWT frontend. + * Implementing this is not mandatory - if it were, there would be no need to have separate Mongo, JSON and other + * serialiazation handlers. + * + * This way, at least any device identifier that can be serialized to a string can also be exposed to the GWT frontend + * (can both be displayed and especially be created there!). + * + * @author Fredrik Teschke + * + */ +public interface DeviceIdentifierStringSerializationHandler extends DeviceIdentifierSerializationHandler { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceMapping.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceMapping.java new file mode 100644 index 00000000000..875057c55ba --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceMapping.java @@ -0,0 +1,24 @@ +package com.sap.sailing.domain.racelog.tracking; + +import java.io.Serializable; +import java.util.List; + +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.common.TimeRange; +import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceMappingFinder; + +/** + * @see DeviceMappingFinder {@code DeviceMappings} are derived from {@code DeviceMappingEvents}. The {@link Timed} interface + * is to be implemented as providing the {@link TimeRange#from()} time point as the answer to {@link Timed#getTimePoint()}. + * + * @author Fredrik Teschke + */ +public interface DeviceMapping extends DeviceWithTimeRange { + ItemType getMappedTo(); + /** + * List the ids of those race log events from which this mapping was generated. + * In the simplest case this is a single event. + */ + List getOriginalRaceLogEventIds(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceMappingEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceMappingEvent.java new file mode 100644 index 00000000000..853712e28f1 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceMappingEvent.java @@ -0,0 +1,38 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.Revokable; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DefinedMarkFinder; + +/** + * Event type for mapping {@link DeviceIdentifier devices} to {@code items} ({@link Competitor competitors} or {@link Mark marks}). + *

+ * Not both {@link #getFrom()} and {@link #getTo()} may be {@code null} at the same time (which would be a mapping for "all times", + * as the semantics of somehow later limiting this range would become unclear + *

+ * If one end of the time range enclosed by {@link #getFrom()} and {@link #getTo()} is open, this can be closed + * by a {@link CloseOpenEndedDeviceMappingEvent}. + * + * @see DefinedMarkFinder for rules by which {@link DeviceMapping}s are derived from these events. + * @author Fredrik Teschke + */ +public interface DeviceMappingEvent extends RaceLogEvent, Revokable { + ItemType getMappedTo(); + DeviceIdentifier getDevice(); + + /** + * The {@code return} value may be {@code null}, symbolizing an open range. + * In that case the device should be mapped to the item for any timepoint before that specified by {@link #getTo()}. + */ + TimePoint getFrom(); + + /** + * The {@code return} value may be {@code null}, symbolizing an open range. + * In that case the device should be mapped to the item for any timepoint after that specified by {@link #getFrom()}. + */ + TimePoint getTo(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceMarkMappingEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceMarkMappingEvent.java new file mode 100644 index 00000000000..6160fe62756 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceMarkMappingEvent.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.base.Mark; + +public interface DeviceMarkMappingEvent extends DeviceMappingEvent { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceWithTimeRange.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceWithTimeRange.java new file mode 100755 index 00000000000..56a6e5c0bef --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/DeviceWithTimeRange.java @@ -0,0 +1,10 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.common.TimeRange; + +public interface DeviceWithTimeRange extends Timed { + DeviceIdentifier getDevice(); + + TimeRange getTimeRange(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/PlaceHolderDeviceIdentifier.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/PlaceHolderDeviceIdentifier.java new file mode 100644 index 00000000000..e3c6a87e05a --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/PlaceHolderDeviceIdentifier.java @@ -0,0 +1,67 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.racelog.RaceLog; + + + +/** + * If the proper identifier cannot be resolved (with an approriate {@link DeviceIdentifierSerializationHandler}), for + * example because the OSGi context is not available on the smartphone receiving the {@link DevicieIdentifier} via its + * {@link RaceLog}, then this class can be used as a placeholder, that will at least offer the {@link #type} and + * {@link #stringRepresentation string representation} of the original identifier. + * + * @author Fredrik Teschke + * + */ +public class PlaceHolderDeviceIdentifier implements DeviceIdentifier { + private static final long serialVersionUID = 8642469389180406246L; + + private final String type; + private final String stringRepresentation; + + public PlaceHolderDeviceIdentifier(String type, String stringRepresentation) { + this.type = type; + this.stringRepresentation = stringRepresentation; + } + + @Override + public String getIdentifierType() { + return type; + } + + @Override + public String getStringRepresentation() { + return stringRepresentation; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((stringRepresentation == null) ? 0 : stringRepresentation.hashCode()); + result = prime * result + ((type == null) ? 0 : type.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; + PlaceHolderDeviceIdentifier other = (PlaceHolderDeviceIdentifier) obj; + if (stringRepresentation == null) { + if (other.stringRepresentation != null) + return false; + } else if (!stringRepresentation.equals(other.stringRepresentation)) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/RegisterCompetitorEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/RegisterCompetitorEvent.java new file mode 100644 index 00000000000..06470d41647 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/RegisterCompetitorEvent.java @@ -0,0 +1,21 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.Revokable; + +/** + * Register a competitor for that race using the {@link RaceLog} in racelog-tracked races. + * + * A dummy {@link Competitor} implementation with only an {@link Competitor#getId() id} may be used, + * if the competitor is known to already exist on the server, as it is + * {@link IsManagedBySharedDomainFactory#resolve(com.sap.sailing.domain.base.SharedDomainFactory) resolved} + * on arrival. + * @author Fredrik Teschke + * + */ +public interface RegisterCompetitorEvent extends RaceLogEvent, Revokable { + Competitor getCompetitor(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/SmartphoneUUIDIdentifier.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/SmartphoneUUIDIdentifier.java new file mode 100644 index 00000000000..7b2d16103d6 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/SmartphoneUUIDIdentifier.java @@ -0,0 +1,10 @@ +package com.sap.sailing.domain.racelog.tracking; + +import java.util.UUID; + + +public interface SmartphoneUUIDIdentifier extends DeviceIdentifier { + String TYPE = "smartphoneUUID"; + + UUID getUUID(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/SmartphoneUUIDSerializationHandler.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/SmartphoneUUIDSerializationHandler.java new file mode 100644 index 00000000000..f96ef1e702c --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/SmartphoneUUIDSerializationHandler.java @@ -0,0 +1,27 @@ +package com.sap.sailing.domain.racelog.tracking; + +import java.util.UUID; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.impl.SmartphoneUUIDIdentifierImpl; +import com.sap.sse.common.Util; + +public class SmartphoneUUIDSerializationHandler { + private SmartphoneUUIDIdentifier castIdentifier(DeviceIdentifier identifier) throws TransformationException { + if (!(identifier instanceof SmartphoneUUIDIdentifier)) + throw new TransformationException("Expected a SmartphoneUUIDIdentifier, got instead: " + identifier); + return (SmartphoneUUIDIdentifier) identifier; + } + + public Util.Pair serialize(DeviceIdentifier deviceIdentifier) throws TransformationException { + return new Util.Pair(castIdentifier(deviceIdentifier).getUUID().toString(), SmartphoneUUIDIdentifier.TYPE); + } + + public DeviceIdentifier deserialize(String input, String type, String stringRep) throws TransformationException { + try { + return new SmartphoneUUIDIdentifierImpl(UUID.fromString(stringRep)); + } catch (IllegalArgumentException e) { + throw new TransformationException("Invalid string representation of smartphone UUID", e); + } + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/StartTrackingEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/StartTrackingEvent.java new file mode 100644 index 00000000000..de7c0c166ac --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/StartTrackingEvent.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.Revokable; +import com.sap.sailing.domain.racelog.tracking.events.DeviceCompetitorMappingEventImpl; + +/** + * Closes the phase during which {@link DeviceCompetitorMappingEventImpl}s actually result + * in competitors being added, and indicates that the server should create a {@link TrackedRace} + * using the information in this {@link RaceLog}. + * + * @author Fredrik Teschke + * + */ +public interface StartTrackingEvent extends RaceLogEvent, Revokable { +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/AllEventsOfTypeFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/AllEventsOfTypeFinder.java new file mode 100644 index 00000000000..1edf0fca849 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/AllEventsOfTypeFinder.java @@ -0,0 +1,37 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import java.util.ArrayList; +import java.util.List; + +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; + +/** + * Finds all events in the racelog which is an {@code instanceof} {@link #ofType}. + * @author Fredrik Teschke + * + */ +public class AllEventsOfTypeFinder extends RaceLogAnalyzer> { + private final Class ofType; + private final boolean onlyUnrevoked; + + public AllEventsOfTypeFinder(RaceLog raceLog, boolean onlyUnrevoked, Class ofType) { + super(raceLog); + this.ofType = ofType; + this.onlyUnrevoked = onlyUnrevoked; + } + + @Override + protected List performAnalysis() { + List result = new ArrayList(); + Iterable set = onlyUnrevoked ? raceLog.getUnrevokedEventsDescending() : getAllEventsDescending(); + for (RaceLogEvent event : set) { + if (ofType.isAssignableFrom(event.getClass())) { + result.add(event); + } + } + return result; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DefinedMarkFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DefinedMarkFinder.java new file mode 100644 index 00000000000..5bd0b68e005 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DefinedMarkFinder.java @@ -0,0 +1,30 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; + +public class DefinedMarkFinder extends RaceLogAnalyzer> { + public DefinedMarkFinder(RaceLog raceLog) { + super(raceLog); + } + + @Override + protected Collection performAnalysis() { + List< Mark> result = new ArrayList(); + for (RaceLogEvent event : raceLog.getUnrevokedEvents()) { + if (event instanceof DefineMarkEvent) { + DefineMarkEvent dME = (DefineMarkEvent) event; + result.add(dME.getMark()); + } + } + return result; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DeviceCompetitorMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DeviceCompetitorMappingFinder.java new file mode 100644 index 00000000000..6e177a3e5fc --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DeviceCompetitorMappingFinder.java @@ -0,0 +1,18 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMappingEvent; + +public class DeviceCompetitorMappingFinder extends DeviceMappingFinder { + + public DeviceCompetitorMappingFinder(RaceLog raceLog) { + super(raceLog); + } + + @Override + protected boolean isValidMapping(DeviceMappingEvent mapping) { + return mapping instanceof DeviceCompetitorMappingEvent; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DeviceMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DeviceMappingFinder.java new file mode 100644 index 00000000000..80b5b6c6d61 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DeviceMappingFinder.java @@ -0,0 +1,101 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimeRange; +import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.common.impl.TimeRangeImpl; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMapping; +import com.sap.sailing.domain.racelog.tracking.DeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.impl.DeviceMappingImpl; + +/** + * Extracts the {@link DeviceMapping}s with the appropriate {@link TimeRange}s from the {@link RaceLog}. + * Based on the {@link DeviceMappingEvent}s found in the {@code RaceLog} for the tracked {@code items} + * ({@code Competitor}s and {@code Marks}), the actual mappings are created. + *

+ * Mappings can be defined with an open-ended time range (e.g. track this from now on), which should then be + * closed by a corresponding {@link CloseOpenEndedDeviceMappingEvent}. + *

+ * The process of removing conflicts between mappings to ensure that only one mapping was active for + * one tracked item has been removed. Instead, every {@code DeviceMappingEvent} now results directly in one + * {@code DeviceMapping}, the only changes being introduced by closing open time ranges in cases where an + * according {@code CloseOpenEndedDeviceMappingEvent} exists. + */ +public class DeviceMappingFinder extends RaceLogAnalyzer>>> { + public DeviceMappingFinder(RaceLog raceLog) { + super(raceLog); + } + + protected boolean isValidMapping(DeviceMappingEvent mapping) { + return mapping instanceof DeviceMappingEvent; + } + + protected DeviceMapping getMapping(DeviceIdentifier device, ItemT item, + TimePoint from, TimePoint to, Serializable originalEventId) { + return new DeviceMappingImpl(item, device, new TimeRangeImpl(from, to), + Collections.singletonList(originalEventId)); + } + + private List getItemSet(Map> map, T item) { + List list = map.get(item); + if (list == null) { + list = new ArrayList(); + map.put(item, list); + } + return list; + } + @Override + protected Map>> performAnalysis() { + Map>> events = new HashMap>>(); + Map>> mappings = new HashMap>>(); + Map closingEvents = new HashMap(); + + for (RaceLogEvent e : raceLog.getUnrevokedEvents()) { + if (e instanceof DeviceMappingEvent && isValidMapping(((DeviceMappingEvent) e))) { + @SuppressWarnings("unchecked") + DeviceMappingEvent mappingEvent = (DeviceMappingEvent) e; + getItemSet(events, mappingEvent.getMappedTo()).add(mappingEvent); + } else if (e instanceof CloseOpenEndedDeviceMappingEvent) { + //a higher priority closing events for the same mapping event overwrites the lower priority one + CloseOpenEndedDeviceMappingEvent closingEvent = (CloseOpenEndedDeviceMappingEvent) e; + closingEvents.put(closingEvent.getDeviceMappingEventId(), closingEvent); + } + } + + for (ItemT item : events.keySet()) { + mappings.put(item, closeOpenRanges(events.get(item), item, closingEvents)); + } + + return mappings; + } + + private List> closeOpenRanges(List> events, ItemT item, + Map closingEvents) { + List> result = new ArrayList>(); + + for (DeviceMappingEvent event : events) { + TimePoint from = event.getFrom(); + TimePoint to = event.getTo(); + TimePoint closingTimePoint = closingEvents.containsKey(event.getId()) ? + closingEvents.get(event.getId()).getClosingTimePoint() : null; + if (from == null) from = closingTimePoint; + if (to == null) to = closingTimePoint; + + result.add(getMapping(event.getDevice(), item, from, to, event.getId())); + } + + return result; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DeviceMarkMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DeviceMarkMappingFinder.java new file mode 100644 index 00000000000..9832673f295 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/DeviceMarkMappingFinder.java @@ -0,0 +1,18 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.tracking.DeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; + +public class DeviceMarkMappingFinder extends DeviceMappingFinder { + + public DeviceMarkMappingFinder(RaceLog raceLog) { + super(raceLog); + } + + @Override + protected boolean isValidMapping(DeviceMappingEvent mapping) { + return mapping instanceof DeviceMarkMappingEvent; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/LastEventOfTypeFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/LastEventOfTypeFinder.java new file mode 100644 index 00000000000..16546f027c8 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/LastEventOfTypeFinder.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; + +/** + * Finds the most recent event in the racelog which is an {@code instanceof} {@link #ofType}. + * @author Fredrik Teschke + * + */ +public class LastEventOfTypeFinder extends RaceLogAnalyzer { + private final Class ofType; + private final boolean onlyUnrevoked; + + public LastEventOfTypeFinder(RaceLog raceLog, boolean onlyUnrevoked, Class ofType) { + super(raceLog); + this.ofType = ofType; + this.onlyUnrevoked = onlyUnrevoked; + } + + @Override + protected RaceLogEvent performAnalysis() { + Iterable set = onlyUnrevoked ? raceLog.getUnrevokedEventsDescending() : getAllEventsDescending(); + for (RaceLogEvent event : set) { + if (ofType.isAssignableFrom(event.getClass())) { + return event; + } + } + return null; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/LastPingMappingEventFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/LastPingMappingEventFinder.java new file mode 100644 index 00000000000..f88720758be --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/LastPingMappingEventFinder.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; +import com.sap.sailing.domain.racelog.tracking.DeviceMappingEvent; + +public class LastPingMappingEventFinder extends RaceLogAnalyzer> { + private final WithID item; + + public LastPingMappingEventFinder(RaceLog raceLog, WithID forItem) { + super(raceLog); + this.item = forItem; + } + + @Override + protected DeviceMappingEvent performAnalysis() { + for (RaceLogEvent e : raceLog.getUnrevokedEventsDescending()) { + if (e instanceof DeviceMappingEvent) { + DeviceMappingEvent mappingEvent = (DeviceMappingEvent) e; + if (item.equals(mappingEvent.getMappedTo()) && mappingEvent.getFrom() != null && + mappingEvent.getTo() != null && mappingEvent.getFrom().equals(mappingEvent.getTo())) { + @SuppressWarnings("unchecked") + DeviceMappingEvent cast = (DeviceMappingEvent) mappingEvent; + return cast; + } + } + } + return null; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/OpenEndedDeviceMappingCloser.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/OpenEndedDeviceMappingCloser.java new file mode 100644 index 00000000000..48ab1c6cabc --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/OpenEndedDeviceMappingCloser.java @@ -0,0 +1,49 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMapping; +import com.sap.sailing.domain.racelog.tracking.DeviceMappingEvent; + +/** + * Generates events that close the events that are responsible for the {@link #mapping} + * that is passed to the constructor. + * @author Fredrik Teschke + * + */ +public class OpenEndedDeviceMappingCloser extends RaceLogAnalyzer> { + private final DeviceMapping mapping; + private final RaceLogEventAuthor author; + private final TimePoint closingTimePoint; + + public OpenEndedDeviceMappingCloser(RaceLog raceLog, DeviceMapping mapping, RaceLogEventAuthor author, + TimePoint closingTimePoint) { + super(raceLog); + this.mapping = mapping; + this.author = author; + this.closingTimePoint = closingTimePoint; + } + + @Override + protected List performAnalysis() { + List result = new ArrayList(); + + for (Serializable eventId : mapping.getOriginalRaceLogEventIds()) { + DeviceMappingEvent event = (DeviceMappingEvent) raceLog.getEventById(eventId); + if (event.getFrom() == null || event.getTo() == null) { + result.add(RaceLogEventFactory.INSTANCE.createCloseOpenEndedDeviceMappingEvent( + MillisecondsTimePoint.now(), author, raceLog.getCurrentPassId(), event.getId(), closingTimePoint)); + } + } + return result; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/RaceInformationFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/RaceInformationFinder.java new file mode 100644 index 00000000000..a21e518d268 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/RaceInformationFinder.java @@ -0,0 +1,23 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; + +public class RaceInformationFinder extends RaceLogAnalyzer { + public RaceInformationFinder(RaceLog raceLog) { + super(raceLog); + } + + @Override + protected DenoteForTrackingEvent performAnalysis() { + for (RaceLogEvent event : getAllEvents()) { + if (event instanceof DenoteForTrackingEvent) { + return (DenoteForTrackingEvent) event; + } + } + + return null; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/RaceLogTrackingStateAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/RaceLogTrackingStateAnalyzer.java new file mode 100644 index 00000000000..5a4f72d4ffb --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/RaceLogTrackingStateAnalyzer.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; + +public class RaceLogTrackingStateAnalyzer extends RaceLogAnalyzer { + + public RaceLogTrackingStateAnalyzer(RaceLog raceLog) { + super(raceLog); + } + + @Override + protected RaceLogTrackingState performAnalysis() { + for (RaceLogEvent event : raceLog.getUnrevokedEventsDescending()) { + if (event instanceof StartTrackingEvent) { + return RaceLogTrackingState.TRACKING; + } else if (event instanceof DenoteForTrackingEvent) { + return RaceLogTrackingState.AWAITING_RACE_DEFINITION; + } + } + return RaceLogTrackingState.NOT_A_RACELOG_TRACKED_RACE; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/RegisteredCompetitorsAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/RegisteredCompetitorsAnalyzer.java new file mode 100644 index 00000000000..476588d7026 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/analyzing/impl/RegisteredCompetitorsAnalyzer.java @@ -0,0 +1,37 @@ +package com.sap.sailing.domain.racelog.tracking.analyzing.impl; + +import java.util.HashSet; +import java.util.Set; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; + +/** + * Uses the {@link RegisterCompetitorEvent}s in the {@link RaceLog} + * to determine the competitors registered for this event. + * @author Fredrik Teschke + * + */ +public class RegisteredCompetitorsAnalyzer extends RaceLogAnalyzer> { + + public RegisteredCompetitorsAnalyzer(RaceLog raceLog) { + super(raceLog); + } + + @Override + protected Set performAnalysis() { + Set result = new HashSet(); + + for (RaceLogEvent event : getRaceLog().getUnrevokedEvents()) { + if (event instanceof RegisterCompetitorEvent) { + result.add(((RegisterCompetitorEvent) event).getCompetitor()); + } + } + + return result; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/AbstractDeviceMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/AbstractDeviceMappingEventImpl.java new file mode 100644 index 00000000000..4c4320912a8 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/AbstractDeviceMappingEventImpl.java @@ -0,0 +1,50 @@ +package com.sap.sailing.domain.racelog.tracking.events; + +import java.io.Serializable; +import java.util.Collections; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMappingEvent; + +public abstract class AbstractDeviceMappingEventImpl extends RaceLogEventImpl implements +DeviceMappingEvent { + private static final long serialVersionUID = -8439653251231710356L; + + private final ItemType mappedTo; + private final DeviceIdentifier device; + private final TimePoint from; + private final TimePoint to; + + public AbstractDeviceMappingEventImpl(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int pPassId, ItemType mappedTo, + DeviceIdentifier device, TimePoint from, TimePoint to) { + super(createdAt, author, logicalTimePoint, pId, Collections.emptyList(), pPassId); + this.mappedTo = mappedTo; + this.device = device; + this.from = from; + this.to = to; + } + + public ItemType getMappedTo() { + return mappedTo; + } + public DeviceIdentifier getDevice() { + return device; + } + public TimePoint getFrom() { + return from; + } + public TimePoint getTo() { + return to; + } + + @Override + public String getShortInfo() { + return "device: " + device + ", mapped to: " + mappedTo + ", from: " + from + ", to: " + to; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/CloseOpenEndedDeviceMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/CloseOpenEndedDeviceMappingEventImpl.java new file mode 100644 index 00000000000..6ae55f525c7 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/CloseOpenEndedDeviceMappingEventImpl.java @@ -0,0 +1,42 @@ +package com.sap.sailing.domain.racelog.tracking.events; + +import java.io.Serializable; +import java.util.Collections; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; + +public class CloseOpenEndedDeviceMappingEventImpl extends RaceLogEventImpl implements CloseOpenEndedDeviceMappingEvent { + private static final long serialVersionUID = -2401732623610224918L; + + private final Serializable deviceMappingEventId; + private final TimePoint closingTimePoint; + + public CloseOpenEndedDeviceMappingEventImpl(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int pPassId, + Serializable deviceMappingEventId, TimePoint closingTimePoint) { + super(createdAt, author, logicalTimePoint, pId, Collections.emptyList(), pPassId); + this.deviceMappingEventId = deviceMappingEventId; + this.closingTimePoint = closingTimePoint; + } + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } + + @Override + public Serializable getDeviceMappingEventId() { + return deviceMappingEventId; + } + + @Override + public TimePoint getClosingTimePoint() { + return closingTimePoint; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DefineMarkEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DefineMarkEventImpl.java new file mode 100644 index 00000000000..5bed57f7fef --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DefineMarkEventImpl.java @@ -0,0 +1,35 @@ +package com.sap.sailing.domain.racelog.tracking.events; + +import java.io.Serializable; +import java.util.Collections; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; + +public class DefineMarkEventImpl extends RaceLogEventImpl implements DefineMarkEvent { + private static final long serialVersionUID = 277007856878002208L; + + private final Mark mark; + + public DefineMarkEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int pPassId, Mark mark) { + super(createdAt, author, logicalTimePoint, pId, Collections.emptyList(), pPassId); + this.mark = mark; + } + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } + + @Override + public Mark getMark() { + return mark; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DenoteForTrackingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DenoteForTrackingEventImpl.java new file mode 100644 index 00000000000..c3600530b63 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DenoteForTrackingEventImpl.java @@ -0,0 +1,51 @@ +package com.sap.sailing.domain.racelog.tracking.events; + +import java.io.Serializable; +import java.util.Collections; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; + +public class DenoteForTrackingEventImpl extends RaceLogEventImpl implements +DenoteForTrackingEvent { + private static final long serialVersionUID = 6937741283401976385L; + + private final String raceName; + private final BoatClass boatClass; + private final Serializable raceId; + + public DenoteForTrackingEventImpl(TimePoint createdAt, + RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int pPassId, + String raceName, BoatClass boatClass, Serializable raceId) { + super(createdAt, author, logicalTimePoint, pId, Collections.emptyList(), pPassId); + this.raceName = raceName; + this.boatClass = boatClass; + this.raceId = raceId; + } + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } + + @Override + public String getRaceName() { + return raceName; + } + + @Override + public BoatClass getBoatClass() { + return boatClass; + } + + @Override + public Serializable getRaceId() { + return raceId; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DeviceCompetitorMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DeviceCompetitorMappingEventImpl.java new file mode 100644 index 00000000000..9338ba836c5 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DeviceCompetitorMappingEventImpl.java @@ -0,0 +1,25 @@ +package com.sap.sailing.domain.racelog.tracking.events; + +import java.io.Serializable; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; + +public class DeviceCompetitorMappingEventImpl extends AbstractDeviceMappingEventImpl +implements DeviceCompetitorMappingEvent { + public DeviceCompetitorMappingEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int pPassId, Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) { + super(createdAt, author, logicalTimePoint, pId, pPassId, mappedTo, device, from, to); + } + + private static final long serialVersionUID = -1494030544804758753L; + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DeviceMarkMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DeviceMarkMappingEventImpl.java new file mode 100644 index 00000000000..539eff2d742 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/DeviceMarkMappingEventImpl.java @@ -0,0 +1,25 @@ +package com.sap.sailing.domain.racelog.tracking.events; + +import java.io.Serializable; + +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; + +public class DeviceMarkMappingEventImpl extends AbstractDeviceMappingEventImpl +implements DeviceMarkMappingEvent { + public DeviceMarkMappingEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int pPassId, Mark mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) { + super(createdAt, author, logicalTimePoint, pId, pPassId, mappedTo, device, from, to); + } + + private static final long serialVersionUID = -1494030544804758753L; + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RegisterCompetitorEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RegisterCompetitorEventImpl.java new file mode 100644 index 00000000000..3db0e10a290 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RegisterCompetitorEventImpl.java @@ -0,0 +1,30 @@ +package com.sap.sailing.domain.racelog.tracking.events; + +import java.io.Serializable; +import java.util.Collections; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; + +public class RegisterCompetitorEventImpl extends RaceLogEventImpl implements RegisterCompetitorEvent { + private static final long serialVersionUID = -30864810737555657L; + + public RegisterCompetitorEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int pPassId, Competitor competitor) { + super(createdAt, author, logicalTimePoint, pId, Collections.singletonList(competitor), pPassId); + } + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } + + @Override + public Competitor getCompetitor() { + return getInvolvedBoats().get(0); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RevokeEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RevokeEventImpl.java new file mode 100644 index 00000000000..01c45e24e86 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RevokeEventImpl.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.racelog.tracking.events; + +import java.io.Serializable; +import java.util.Collections; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; + +public class RevokeEventImpl extends RaceLogEventImpl implements RevokeEvent { + private static final long serialVersionUID = -30864810737555657L; + + private final Serializable revokedEventId; + + public RevokeEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, int pPassId, Serializable revokedEventId) { + super(createdAt, author, logicalTimePoint, pId, Collections.emptyList(), pPassId); + this.revokedEventId = revokedEventId; + } + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } + + @Override + public Serializable getRevokedEventId() { + return revokedEventId; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/StartTrackingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/StartTrackingEventImpl.java new file mode 100644 index 00000000000..6cebf36d276 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/StartTrackingEventImpl.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.racelog.tracking.events; + +import java.io.Serializable; +import java.util.Collections; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; + +public class StartTrackingEventImpl extends RaceLogEventImpl implements +StartTrackingEvent { + private static final long serialVersionUID = 6017954368580125221L; + + public StartTrackingEventImpl(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, int pPassId) { + super(createdAt, author, logicalTimePoint, pId, Collections.emptyList(), pPassId); + } + + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/impl/DeviceMappingImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/impl/DeviceMappingImpl.java new file mode 100644 index 00000000000..2ffbab79491 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/impl/DeviceMappingImpl.java @@ -0,0 +1,73 @@ +package com.sap.sailing.domain.racelog.tracking.impl; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimeRange; +import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.common.impl.TimeRangeImpl; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceMapping; +import com.sap.sailing.domain.racelog.tracking.DeviceMappingEvent; + +public class DeviceMappingImpl implements DeviceMapping { + private static final long serialVersionUID = -4602948402371623654L; + + private final ItemType mappedTo; + private final DeviceIdentifier device; + private final TimeRange timeRange; + private final List originalRaceLogEventIds = new ArrayList(); + + public DeviceMappingImpl(ItemType mappedTo, DeviceIdentifier device, TimeRange timeRange) { + this.mappedTo = mappedTo; + this.device = device; + this.timeRange = timeRange; + } + + public DeviceMappingImpl(ItemType mappedTo, DeviceIdentifier device, TimeRange timeRange, Serializable originalRaceLogEventId) { + this(mappedTo, device, timeRange); + if (originalRaceLogEventId != null) originalRaceLogEventIds.add(originalRaceLogEventId); + } + + public DeviceMappingImpl(ItemType mappedTo, DeviceIdentifier device, TimeRange timeRange, List originalRaceLogEventIds) { + this(mappedTo, device, timeRange); + if (originalRaceLogEventIds != null) this.originalRaceLogEventIds.addAll(originalRaceLogEventIds); + } + + @Override + public DeviceIdentifier getDevice() { + return device; + } + + @Override + public TimeRange getTimeRange() { + return timeRange; + } + + @Override + public ItemType getMappedTo() { + return mappedTo; + } + + @Override + public TimePoint getTimePoint() { + return timeRange.from(); + } + + public static DeviceMapping convertToDeviceMapping(DeviceMappingEvent event) { + return new DeviceMappingImpl(event.getMappedTo(), event.getDevice(), new TimeRangeImpl(event.getFrom(), event.getTo()), + event.getId()); + } + + @Override + public String toString() { + return "Device Mapping - device: " + device + ", mapped to: " + mappedTo + ", time range: " + timeRange; + } + + @Override + public List getOriginalRaceLogEventIds() { + return originalRaceLogEventIds; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/impl/PlaceHolderDeviceIdentifierSerializationHandler.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/impl/PlaceHolderDeviceIdentifierSerializationHandler.java new file mode 100644 index 00000000000..469c94f8ac0 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/impl/PlaceHolderDeviceIdentifierSerializationHandler.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.racelog.tracking.impl; + +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.PlaceHolderDeviceIdentifier; +import com.sap.sse.common.Util; + +/** + * Can be used as the last option, if no other service is available when trying to load an identifier + * from, or save it to the database. + * The handler itself doesn't do much, but relies on the {@code type} and {@code stringRepresentation} + * to be saved and loaded. This way, the {@code stringRepresentation} is always available, even if the + * appropriate persistence service was available when writing, and is now unavailable when loading. + * + * @author Fredrik Teschke + * + */ +public class PlaceHolderDeviceIdentifierSerializationHandler { + + public DeviceIdentifier deserialize(String object, String type, String stringRepresentation) throws TransformationException { + return new PlaceHolderDeviceIdentifier(type, stringRepresentation); + } + + public Util.Pair serialize(DeviceIdentifier object) throws TransformationException { + return new Util.Pair(object.getIdentifierType(), object.getStringRepresentation()); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/impl/SmartphoneUUIDIdentifierImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/impl/SmartphoneUUIDIdentifierImpl.java new file mode 100644 index 00000000000..27bedb74bf7 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/impl/SmartphoneUUIDIdentifierImpl.java @@ -0,0 +1,48 @@ +package com.sap.sailing.domain.racelog.tracking.impl; + +import java.util.UUID; + +import com.sap.sailing.domain.racelog.tracking.SmartphoneUUIDIdentifier; + + +public class SmartphoneUUIDIdentifierImpl implements SmartphoneUUIDIdentifier { + private static final long serialVersionUID = -1747763242191049428L; + + private final UUID uuid; + + public SmartphoneUUIDIdentifierImpl(UUID uuid) { + this.uuid = uuid; + } + + public UUID getUUID() { + return uuid; + } + + @Override + public String getIdentifierType() { + return TYPE; + } + + @Override + public String toString() { + return "UUID " + uuid; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SmartphoneUUIDIdentifier) { + return ((SmartphoneUUIDIdentifier) obj).getUUID().equals(uuid); + } + return false; + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } + + @Override + public String getStringRepresentation() { + return uuid.toString(); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java index 4ab66c047bd..62cd5b9c7ae 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java @@ -1,147 +1,147 @@ -package com.sap.sailing.domain.tracking; - -import java.io.Serializable; -import java.util.Iterator; -import java.util.concurrent.locks.ReadWriteLock; - -import com.sap.sailing.domain.base.Timed; -import com.sap.sailing.domain.common.Duration; -import com.sap.sailing.domain.common.TimePoint; - -/** - * A track records {@link Timed} items for an object of type ItemType. It allows clients to ask for a value - * close to a given {@link TimePoint}. The track manages a time-based set of raw fixes. An implementation may have an - * understanding of how to eliminate outliers. For example, if a track implementation knows it's tracking boats, it may - * consider fixes that the boat cannot possibly have reached due to its speed and direction change limitations as - * outliers. The set of fixes with outliers filtered out can be obtained using {@link #getFixes} whereas - * {@link #getRawFixes()} returns the unfiltered, raw fixes. If an implementation has no idea what an outlier is, - * both methods will return the same fix sequence.

- * - * With tracks, concurrency is an important issue. Threads may want to modify a track while other threads may want to - * read from it. Several methods such as {@link #getLastFixAtOrBefore(TimePoint)} return a single fix and can manage - * concurrency internally. However, those methods returning a collection of fixes, such as {@link #getFixes()} or an - * iterator over a collection of fixes, such as {@link #getFixesIterator(TimePoint, boolean)}, need special treatment. - * Until we internalize such iterations (see bug 824, http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=824), - * callers need to manage a read lock which is part of a {@link ReadWriteLock} managed by this track. Callers do so - * by calling {@link #lockForRead} and {@link #unlockAfterRead}. - * - * @author Axel Uhl (d043530) - */ -public interface Track extends Serializable { - /** - * Locks this track for reading by the calling thread. If the thread already holds the lock for this track, - * the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a finally - * block to release the lock under all possible circumstances. Failure to do so will inevitably lead to - * deadlocks! - */ - void lockForRead(); - - /** - * Decrements the hold count for this track's read lock for the calling thread. If it goes to zero, the lock will be - * released and other readers or a writer can obtain the lock. Make sure to call this method in a - * finally block for each {@link #lockForRead()} invocation. - */ - void unlockAfterRead(); - - /** - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - * - * @return the smoothened fixes - */ - Iterable getFixes(); - - /** - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - * - * @return The smoothened fixes between from and to. - */ - Iterable getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive); - - /** - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterable getRawFixes(); - - FixType getLastFixAtOrBefore(TimePoint timePoint); - - FixType getLastFixBefore(TimePoint timePoint); - - FixType getLastRawFixAtOrBefore(TimePoint timePoint); - - FixType getFirstFixAtOrAfter(TimePoint timePoint); - - FixType getFirstRawFixAtOrAfter(TimePoint timePoint); - - FixType getLastRawFixBefore(TimePoint timePoint); - - FixType getFirstRawFixAfter(TimePoint timePoint); - - FixType getFirstFixAfter(TimePoint timePoint); - - /** - * The first fix in this track or null if the track is empty. The fix returned may - * be an outlier that is not returned by calls operating on the smoothened version of the track. - */ - FixType getFirstRawFix(); - - /** - * The last fix in this track or null if the track is empty. The fix returned may - * be an outlier that is not returned by calls operating on the smoothened version of the track. - */ - FixType getLastRawFix(); - - /** - * Returns an iterator starting at the first fix after startingAt (or "at or after" in case - * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see - * also {@link #getFixes()}, without any smoothening or dampening applied. - * - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterator getFixesIterator(TimePoint startingAt, boolean inclusive); - - /** - * Returns an iterator starting at the first raw fix after startingAt (or "at or after" in case - * inclusive is true). The fixes returned by the iterator are the raw fixes (see also - * {@link #getRawFixes()}, without any smoothening or dampening applied. - * - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive); - - /** - * Returns a descending iterator starting at the first fix before startingAt (or "at or before" in case - * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see - * also {@link #getFixes()}, without any smoothening or dampening applied. - * - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterator getFixesDescendingIterator(TimePoint startingAt, boolean inclusive); - - /** - * Returns a descending iterator starting at the first raw fix before startingAt (or "at or before" in case - * inclusive is true). The fixes returned by the iterator are the raw fixes (see also - * {@link #getRawFixes()}, without any smoothening or dampening applied. - * - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive); - - /** - * @return the average duration between two fixes (outliers removed) in this track or null if there is not - * more than one fix in the track - */ - Duration getAverageIntervalBetweenFixes(); - - /** - * @return the average duration between two fixes (outliers not removed) in this track or null if there is not - * more than one raw fix in the track - */ - Duration getAverageIntervalBetweenRawFixes(); -} +package com.sap.sailing.domain.tracking; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.concurrent.locks.ReadWriteLock; + +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.common.Duration; +import com.sap.sailing.domain.common.TimePoint; + +/** + * A track records {@link Timed} items for an object of type ItemType. It allows clients to ask for a value + * close to a given {@link TimePoint}. The track manages a time-based set of raw fixes. An implementation may have an + * understanding of how to eliminate outliers. For example, if a track implementation knows it's tracking boats, it may + * consider fixes that the boat cannot possibly have reached due to its speed and direction change limitations as + * outliers. The set of fixes with outliers filtered out can be obtained using {@link #getFixes} whereas + * {@link #getRawFixes()} returns the unfiltered, raw fixes. If an implementation has no idea what an outlier is, + * both methods will return the same fix sequence.

+ * + * With tracks, concurrency is an important issue. Threads may want to modify a track while other threads may want to + * read from it. Several methods such as {@link #getLastFixAtOrBefore(TimePoint)} return a single fix and can manage + * concurrency internally. However, those methods returning a collection of fixes, such as {@link #getFixes()} or an + * iterator over a collection of fixes, such as {@link #getFixesIterator(TimePoint, boolean)}, need special treatment. + * Until we internalize such iterations (see bug 824, http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=824), + * callers need to manage a read lock which is part of a {@link ReadWriteLock} managed by this track. Callers do so + * by calling {@link #lockForRead} and {@link #unlockAfterRead}. + * + * @author Axel Uhl (d043530) + */ +public interface Track extends Serializable { + /** + * Locks this track for reading by the calling thread. If the thread already holds the lock for this track, + * the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a finally + * block to release the lock under all possible circumstances. Failure to do so will inevitably lead to + * deadlocks! + */ + void lockForRead(); + + /** + * Decrements the hold count for this track's read lock for the calling thread. If it goes to zero, the lock will be + * released and other readers or a writer can obtain the lock. Make sure to call this method in a + * finally block for each {@link #lockForRead()} invocation. + */ + void unlockAfterRead(); + + /** + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + * + * @return the smoothened fixes + */ + Iterable getFixes(); + + /** + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + * + * @return The smoothened fixes between from and to. + */ + Iterable getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive); + + /** + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterable getRawFixes(); + + FixType getLastFixAtOrBefore(TimePoint timePoint); + + FixType getLastFixBefore(TimePoint timePoint); + + FixType getLastRawFixAtOrBefore(TimePoint timePoint); + + FixType getFirstFixAtOrAfter(TimePoint timePoint); + + FixType getFirstRawFixAtOrAfter(TimePoint timePoint); + + FixType getLastRawFixBefore(TimePoint timePoint); + + FixType getFirstRawFixAfter(TimePoint timePoint); + + FixType getFirstFixAfter(TimePoint timePoint); + + /** + * The first fix in this track or null if the track is empty. The fix returned may + * be an outlier that is not returned by calls operating on the smoothened version of the track. + */ + FixType getFirstRawFix(); + + /** + * The last fix in this track or null if the track is empty. The fix returned may + * be an outlier that is not returned by calls operating on the smoothened version of the track. + */ + FixType getLastRawFix(); + + /** + * Returns an iterator starting at the first fix after startingAt (or "at or after" in case + * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see + * also {@link #getFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getFixesIterator(TimePoint startingAt, boolean inclusive); + + /** + * Returns an iterator starting at the first raw fix after startingAt (or "at or after" in case + * inclusive is true). The fixes returned by the iterator are the raw fixes (see also + * {@link #getRawFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive); + + /** + * Returns a descending iterator starting at the first fix before startingAt (or "at or before" in case + * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see + * also {@link #getFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getFixesDescendingIterator(TimePoint startingAt, boolean inclusive); + + /** + * Returns a descending iterator starting at the first raw fix before startingAt (or "at or before" in case + * inclusive is true). The fixes returned by the iterator are the raw fixes (see also + * {@link #getRawFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive); + + /** + * @return the average duration between two fixes (outliers removed) in this track or null if there is not + * more than one fix in the track + */ + Duration getAverageIntervalBetweenFixes(); + + /** + * @return the average duration between two fixes (outliers not removed) in this track or null if there is not + * more than one raw fix in the track + */ + Duration getAverageIntervalBetweenRawFixes(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java index f8d21861adf..152e4071625 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java @@ -1,341 +1,348 @@ -package com.sap.sailing.domain.tracking.impl; - -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.NavigableSet; - -import com.sap.sailing.domain.base.Timed; -import com.sap.sailing.domain.common.Duration; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.tracking.Track; -import com.sap.sailing.util.impl.ArrayListNavigableSet; -import com.sap.sailing.util.impl.LockUtil; -import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; -import com.sap.sailing.util.impl.UnmodifiableNavigableSet; - -public class TrackImpl implements Track { - private static final long serialVersionUID = -4075853657857657528L; - /** - * The fixes, ordered by their time points - */ - private final ArrayListNavigableSet fixes; - - private final NamedReentrantReadWriteLock readWriteLock; - - protected static class DummyTimed implements Timed { - private static final long serialVersionUID = 6047311973718918856L; - private final TimePoint timePoint; - public DummyTimed(TimePoint timePoint) { - super(); - this.timePoint = timePoint; - } - @Override - public TimePoint getTimePoint() { - return timePoint; - } - @Override - public String toString() { - return timePoint.toString(); - } - } - - public TrackImpl(String nameForReadWriteLock) { - this(new ArrayListNavigableSet(TimedComparator.INSTANCE), nameForReadWriteLock); - } - - protected TrackImpl(ArrayListNavigableSet fixes, String nameForReadWriteLock) { - this.readWriteLock = new NamedReentrantReadWriteLock(nameForReadWriteLock, /* fair */ false); - this.fixes = fixes; - } - - /** - * Synchronize the serialization such that no fixes are added while serializing - */ - private void writeObject(ObjectOutputStream s) throws IOException { - lockForRead(); - try { - s.defaultWriteObject(); - } finally { - unlockAfterRead(); - } - } - - @Override - public void lockForRead() { - LockUtil.lockForRead(readWriteLock); - } - - @Override - public void unlockAfterRead() { - LockUtil.unlockAfterRead(readWriteLock); - } - - protected void lockForWrite() { - LockUtil.lockForWrite(readWriteLock); - } - - protected void unlockAfterWrite() { - LockUtil.unlockAfterWrite(readWriteLock); - } - - /** - * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and {@link #unlockAfterRead()} - * to avoid {@link ConcurrentModificationException}s. Should they modify the structure returned, they have to use - * {@link #lockForWrite()} and {@link #unlockAfterWrite()}, respectively. - */ - protected NavigableSet getInternalRawFixes() { - @SuppressWarnings("unchecked") - NavigableSet result = (NavigableSet) fixes; - return result; - } - - /** - * asserts that the calling thread holds at least one of read and write lock - */ - protected void assertReadLock() { - if (readWriteLock.getReadHoldCount() < 1 && readWriteLock.getWriteHoldCount() < 1) { - throw new IllegalStateException("Caller must obtain read lock using lockForRead() before calling this method"); - } - } - - protected void assertWriteLock() { - if (readWriteLock.getWriteHoldCount() < 1) { - throw new IllegalStateException("Caller must obtain write lock using lockForWrite() before calling this method"); - } - } - - /** - * Callers that want to iterate over the collection returned need to synchronize on this object to - * avoid {@link ConcurrentModificationException}s. - * - * @return the smoothened fixes; this implementation simply delegates to {@link #getInternalRawFixes()} because for - * only {@link Timed} fixes we can't know how to remove outliers. Subclasses that constrain the - * FixType may provide smoothening implementations. - */ - protected NavigableSet getInternalFixes() { - NavigableSet result = getInternalRawFixes(); - return result; - } - - /** - * Iterates the fixes with outliers getting skipped, in the order of their time points. - * Relies on {@link #getInternalFixes()} to void the track view from outliers. - */ - @Override - public NavigableSet getFixes() { - assertReadLock(); - return new UnmodifiableNavigableSet(getInternalFixes()); - } - - @Override - public Iterable getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive) { - return getFixes().subSet(getDummyFix(from), fromInclusive, getDummyFix(to), toInclusive); - } - - /** - * Iterates over the raw sequence of fixes, all potential outliers included - */ - @Override - public NavigableSet getRawFixes() { - assertReadLock(); - return new UnmodifiableNavigableSet(getInternalRawFixes()); - } - - @Override - public FixType getLastFixAtOrBefore(TimePoint timePoint) { - lockForRead(); - try { - return (FixType) getInternalFixes().floor(getDummyFix(timePoint)); - } finally { - unlockAfterRead(); - } - } - - @Override - public FixType getLastFixBefore(TimePoint timePoint) { - lockForRead(); - try { - return (FixType) getInternalFixes().lower(getDummyFix(timePoint)); - } finally { - unlockAfterRead(); - } - } - - @Override - public FixType getLastRawFixAtOrBefore(TimePoint timePoint) { - lockForRead(); - try { - return (FixType) getInternalRawFixes().floor(getDummyFix(timePoint)); - } finally { - unlockAfterRead(); - } - } - - @Override - public FixType getFirstRawFixAtOrAfter(TimePoint timePoint) { - lockForRead(); - try { - return (FixType) getInternalRawFixes().ceiling(getDummyFix(timePoint)); - } finally { - unlockAfterRead(); - } - } - - @Override - public FixType getFirstFixAtOrAfter(TimePoint timePoint) { - lockForRead(); - try { - return (FixType) getInternalFixes().ceiling(getDummyFix(timePoint)); - } finally { - unlockAfterRead(); - } - } - - @Override - public FixType getLastRawFixBefore(TimePoint timePoint) { - lockForRead(); - try { - return (FixType) getInternalRawFixes().lower(getDummyFix(timePoint)); - } finally { - unlockAfterRead(); - } - } - - @Override - public FixType getFirstFixAfter(TimePoint timePoint) { - lockForRead(); - try { - return (FixType) getInternalFixes().higher(getDummyFix(timePoint)); - } finally { - unlockAfterRead(); - } - } - - @Override - public FixType getFirstRawFixAfter(TimePoint timePoint) { - lockForRead(); - try { - return (FixType) getInternalRawFixes().higher(getDummyFix(timePoint)); - } finally { - unlockAfterRead(); - } - } - - @Override - public FixType getFirstRawFix() { - lockForRead(); - try { - if (getInternalFixes().isEmpty()) { - return null; - } else { - return (FixType) getInternalFixes().first(); - } - } finally { - unlockAfterRead(); - } - } - - @Override - public FixType getLastRawFix() { - lockForRead(); - try { - if (getInternalRawFixes().isEmpty()) { - return null; - } else { - return (FixType) getInternalRawFixes().last(); - } - } finally { - unlockAfterRead(); - } - } - - @Override - public Iterator getFixesIterator(TimePoint startingAt, boolean inclusive) { - assertReadLock(); - Iterator result = (Iterator) getInternalFixes().tailSet( - getDummyFix(startingAt), inclusive).iterator(); - return result; - } - - @Override - public Iterator getFixesDescendingIterator(TimePoint startingAt, boolean inclusive) { - assertReadLock(); - Iterator result = (Iterator) getInternalFixes().headSet( - getDummyFix(startingAt), inclusive).descendingIterator(); - return result; - } - - /** - * Creates a dummy fix that conforms to FixType. This in particular means that subclasses - * instantiating FixType with a specific class need to redefine this method so as to return - * a dummy fix complying with their instantiation type used for FixType. Otherwise, a - * {@link ClassCastException} may result upon certain operations performed with the fix returned by - * this method. - */ - protected FixType getDummyFix(TimePoint timePoint) { - @SuppressWarnings("unchecked") - FixType result = (FixType) new DummyTimed(timePoint); - return result; - } - - @Override - public Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive) { - assertReadLock(); - Iterator result = (Iterator) getInternalRawFixes().tailSet( - getDummyFix(startingAt), inclusive).iterator(); - return result; - } - - @Override - public Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive) { - assertReadLock(); - Iterator result = (Iterator) getInternalRawFixes().headSet( - getDummyFix(startingAt), inclusive).descendingIterator(); - return result; - } - - protected boolean add(FixType fix) { - lockForWrite(); - try { - return getInternalRawFixes().add(fix); - } finally { - unlockAfterWrite(); - } - } - - @Override - public Duration getAverageIntervalBetweenFixes() { - lockForRead(); - try { - final Duration result; - final int size = getFixes().size(); - if (size > 1) { - result = getFixes().first().getTimePoint().until(getFixes().last().getTimePoint()).divide(size-1); - } else { - result = null; - } - return result; - } finally { - unlockAfterRead(); - } - } - - @Override - public Duration getAverageIntervalBetweenRawFixes() { - lockForRead(); - try { - final Duration result; - final int size = getRawFixes().size(); - if (size > 1) { - result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1); - } else { - result = null; - } - return result; - } finally { - unlockAfterRead(); - } - } -} +package com.sap.sailing.domain.tracking.impl; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.NavigableSet; + +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.common.Duration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.tracking.Track; +import com.sap.sailing.util.impl.ArrayListNavigableSet; +import com.sap.sailing.util.impl.LockUtil; +import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sailing.util.impl.UnmodifiableNavigableSet; + +public class TrackImpl implements Track { + private static final long serialVersionUID = -4075853657857657528L; + /** + * The fixes, ordered by their time points + */ + private final ArrayListNavigableSet fixes; + + private final NamedReentrantReadWriteLock readWriteLock; + + protected static class DummyTimed implements Timed { + private static final long serialVersionUID = 6047311973718918856L; + private final TimePoint timePoint; + public DummyTimed(TimePoint timePoint) { + super(); + this.timePoint = timePoint; + } + @Override + public TimePoint getTimePoint() { + return timePoint; + } + @Override + public String toString() { + return timePoint.toString(); + } + } + + public TrackImpl(String nameForReadWriteLock) { + this(new ArrayListNavigableSet(TimedComparator.INSTANCE), nameForReadWriteLock); + } + + protected TrackImpl(ArrayListNavigableSet fixes, String nameForReadWriteLock) { + this.readWriteLock = new NamedReentrantReadWriteLock(nameForReadWriteLock, /* fair */ false); + this.fixes = fixes; + } + + /** + * Synchronize the serialization such that no fixes are added while serializing + */ + private void writeObject(ObjectOutputStream s) throws IOException { + lockForRead(); + try { + s.defaultWriteObject(); + } finally { + unlockAfterRead(); + } + } + + @Override + public void lockForRead() { + LockUtil.lockForRead(readWriteLock); + } + + @Override + public void unlockAfterRead() { + LockUtil.unlockAfterRead(readWriteLock); + } + + protected void lockForWrite() { + LockUtil.lockForWrite(readWriteLock); + } + + protected void unlockAfterWrite() { + LockUtil.unlockAfterWrite(readWriteLock); + } + + /** + * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and {@link #unlockAfterRead()} + * to avoid {@link ConcurrentModificationException}s. Should they modify the structure returned, they have to use + * {@link #lockForWrite()} and {@link #unlockAfterWrite()}, respectively. + */ + protected NavigableSet getInternalRawFixes() { + @SuppressWarnings("unchecked") + NavigableSet result = (NavigableSet) fixes; + return result; + } + + /** + * asserts that the calling thread holds at least one of read and write lock + */ + protected void assertReadLock() { + if (readWriteLock.getReadHoldCount() < 1 && readWriteLock.getWriteHoldCount() < 1) { + throw new IllegalStateException("Caller must obtain read lock using lockForRead() before calling this method"); + } + } + + protected void assertWriteLock() { + if (readWriteLock.getWriteHoldCount() < 1) { + throw new IllegalStateException("Caller must obtain write lock using lockForWrite() before calling this method"); + } + } + + /** + * Callers that want to iterate over the collection returned need to synchronize on this object to + * avoid {@link ConcurrentModificationException}s. + * + * @return the smoothened fixes; this implementation simply delegates to {@link #getInternalRawFixes()} because for + * only {@link Timed} fixes we can't know how to remove outliers. Subclasses that constrain the + * FixType may provide smoothening implementations. + */ + protected NavigableSet getInternalFixes() { + NavigableSet result = getInternalRawFixes(); + return result; + } + + /** + * Iterates the fixes with outliers getting skipped, in the order of their time points. + * Relies on {@link #getInternalFixes()} to void the track view from outliers. + */ + @Override + public NavigableSet getFixes() { + assertReadLock(); + return new UnmodifiableNavigableSet(getInternalFixes()); + } + + @Override + public Iterable getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive) { + return getFixes().subSet(getDummyFix(from), fromInclusive, getDummyFix(to), toInclusive); + } + + /** + * Iterates over the raw sequence of fixes, all potential outliers included + */ + @Override + public NavigableSet getRawFixes() { + assertReadLock(); + return new UnmodifiableNavigableSet(getInternalRawFixes()); + } + + @Override + public FixType getLastFixAtOrBefore(TimePoint timePoint) { + lockForRead(); + try { + return (FixType) getInternalFixes().floor(getDummyFix(timePoint)); + } finally { + unlockAfterRead(); + } + } + + @Override + public FixType getLastFixBefore(TimePoint timePoint) { + lockForRead(); + try { + return (FixType) getInternalFixes().lower(getDummyFix(timePoint)); + } finally { + unlockAfterRead(); + } + } + + @Override + public FixType getLastRawFixAtOrBefore(TimePoint timePoint) { + lockForRead(); + try { + return (FixType) getInternalRawFixes().floor(getDummyFix(timePoint)); + } finally { + unlockAfterRead(); + } + } + + @Override + public FixType getFirstRawFixAtOrAfter(TimePoint timePoint) { + lockForRead(); + try { + return (FixType) getInternalRawFixes().ceiling(getDummyFix(timePoint)); + } finally { + unlockAfterRead(); + } + } + + @Override + public FixType getFirstFixAtOrAfter(TimePoint timePoint) { + lockForRead(); + try { + return (FixType) getInternalFixes().ceiling(getDummyFix(timePoint)); + } finally { + unlockAfterRead(); + } + } + + @Override + public FixType getLastRawFixBefore(TimePoint timePoint) { + lockForRead(); + try { + return (FixType) getInternalRawFixes().lower(getDummyFix(timePoint)); + } finally { + unlockAfterRead(); + } + } + + @Override + public FixType getFirstFixAfter(TimePoint timePoint) { + lockForRead(); + try { + return (FixType) getInternalFixes().higher(getDummyFix(timePoint)); + } finally { + unlockAfterRead(); + } + } + + @Override + public FixType getFirstRawFixAfter(TimePoint timePoint) { + lockForRead(); + try { + return (FixType) getInternalRawFixes().higher(getDummyFix(timePoint)); + } finally { + unlockAfterRead(); + } + } + + @Override + public FixType getFirstRawFix() { + lockForRead(); + try { + if (getInternalFixes().isEmpty()) { + return null; + } else { + return (FixType) getInternalFixes().first(); + } + } finally { + unlockAfterRead(); + } + } + + @Override + public FixType getLastRawFix() { + lockForRead(); + try { + if (getInternalRawFixes().isEmpty()) { + return null; + } else { + return (FixType) getInternalRawFixes().last(); + } + } finally { + unlockAfterRead(); + } + } + + @Override + public Iterator getFixesIterator(TimePoint startingAt, boolean inclusive) { + assertReadLock(); + Iterator result = (Iterator) getInternalFixes().tailSet( + getDummyFix(startingAt), inclusive).iterator(); + return result; + } + + @Override + public Iterator getFixesDescendingIterator(TimePoint startingAt, boolean inclusive) { + assertReadLock(); + Iterator result = (Iterator) getInternalFixes().headSet( + getDummyFix(startingAt), inclusive).descendingIterator(); + return result; + } + + /** + * Creates a dummy fix that conforms to FixType. This in particular means that subclasses + * instantiating FixType with a specific class need to redefine this method so as to return + * a dummy fix complying with their instantiation type used for FixType. Otherwise, a + * {@link ClassCastException} may result upon certain operations performed with the fix returned by + * this method. + */ + protected FixType getDummyFix(TimePoint timePoint) { + @SuppressWarnings("unchecked") + FixType result = (FixType) new DummyTimed(timePoint); + return result; + } + + @Override + public Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive) { + assertReadLock(); + Iterator result = (Iterator) getInternalRawFixes().tailSet( + getDummyFix(startingAt), inclusive).iterator(); + return result; + } + + @Override + public Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive) { + assertReadLock(); + Iterator result = (Iterator) getInternalRawFixes().headSet( + getDummyFix(startingAt), inclusive).descendingIterator(); + return result; + } + + protected boolean add(FixType fix) { + lockForWrite(); + try { + return addWithoutLocking(fix); + } finally { + unlockAfterWrite(); + } + } + + /** + * The caller must ensure to hold the write lock for this track when calling this methos + */ + protected boolean addWithoutLocking(FixType fix) { + return getInternalRawFixes().add(fix); + } + + @Override + public Duration getAverageIntervalBetweenFixes() { + lockForRead(); + try { + final Duration result; + final int size = getFixes().size(); + if (size > 1) { + result = getFixes().first().getTimePoint().until(getFixes().last().getTimePoint()).divide(size-1); + } else { + result = null; + } + return result; + } finally { + unlockAfterRead(); + } + } + + @Override + public Duration getAverageIntervalBetweenRawFixes() { + lockForRead(); + try { + final Duration result; + final int size = getRawFixes().size(); + if (size > 1) { + result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1); + } else { + result = null; + } + return result; + } finally { + unlockAfterRead(); + } + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/ConcurrentWeakHashMap.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/ConcurrentWeakHashMap.java new file mode 100755 index 00000000000..1e71a2f8cca --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/ConcurrentWeakHashMap.java @@ -0,0 +1,243 @@ +package com.sap.sailing.util.impl; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.sap.sse.common.Util; + +/** + * A map that is concurrency-safe as provided by the {@link ConcurrentHashMap} implementation, and referencing + * keys only weakly. Like {@link ConcurrentHashMap}, this map does not support null keys. + * + * @author Axel Uhl (D043530) + */ +public class ConcurrentWeakHashMap implements Map { + private final ConcurrentHashMap map; + + private final ReferenceQueue queue; + + /** + * A reference whose hash code is based on the hash code of the original referent, also after the + * reference has been enqueued. Equality is defined such that this reference is equal to itself which is + * helpful to remove the entries from the {@link ConcurrentHashMap}, as well as equal to any other reference + * of this type if their referents are equal. + * + * @author Axel Uhl (D043530) + * + */ + private class WeakReferenceThatIsEqualToItselfAndItsReferent extends WeakReference { + private final int hashCode; + + public WeakReferenceThatIsEqualToItselfAndItsReferent(K referent, ReferenceQueue q) { + super(referent, q); + hashCode = referent.hashCode(); + } + + @Override + public int hashCode() { + return hashCode; + } + + /** + * If other is this reference, it it considered equal. Otherwise, other is considered + * equal to this reference if the referent is still set and the referent equals other. + */ + @Override + public boolean equals(Object other) { + final boolean result; + if (other == this) { + result = true; + } else { + @SuppressWarnings("unchecked") // don't care about the generics here; important is that we can get at the referent + WeakReferenceThatIsEqualToItselfAndItsReferent otherAsRef = (WeakReferenceThatIsEqualToItselfAndItsReferent) other; + K referent = get(); + K otherReferent = otherAsRef.get(); + result = Util.equalsWithNull(referent, otherReferent); + } + return result; + } + } + + private class KeySet extends AbstractSet { + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return map.containsKey(o); + } + + @Override + public boolean remove(Object o) { + return map.remove(o) != null; + } + + @Override + public Iterator iterator() { + purgeStaleEntries(); + final Iterator> iterator = map.entrySet().iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public K next() { + return iterator.next().getKey().get(); + } + + @Override + public void remove() { + iterator.remove(); + } + }; + } + } + + private class EntrySet extends AbstractSet> { + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public Iterator> iterator() { + purgeStaleEntries(); + final Iterator> iterator = map.entrySet().iterator(); + return new Iterator>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Map.Entry next() { + final Map.Entry e = iterator.next(); + return new Map.Entry() { + @Override + public K getKey() { + return e.getKey().get(); + } + + @Override + public V getValue() { + return e.getValue(); + } + + @Override + public V setValue(V value) { + return e.setValue(value); + } + }; + } + + @Override + public void remove() { + iterator.remove(); + } + }; + } + } + + public ConcurrentWeakHashMap() { + map = new ConcurrentHashMap(); + queue = new ReferenceQueue(); + } + + private void purgeStaleEntries() { + Reference ref; + while ((ref=queue.poll()) != null) { + map.remove(ref); + } + } + + @Override + public int size() { + purgeStaleEntries(); + return map.size(); + } + + @Override + public boolean isEmpty() { + purgeStaleEntries(); + return map.isEmpty(); + } + + @SuppressWarnings("unchecked") // if the cast doesn't work then neither will the equals call, so false will result which is ok + @Override + public boolean containsKey(Object key) { + purgeStaleEntries(); + return map.containsKey(new WeakReferenceThatIsEqualToItselfAndItsReferent((K) key, /* queue */ null)); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @SuppressWarnings("unchecked") // if the cast doesn't work then neither will the equals call, so null will result which is ok + @Override + public V get(Object key) { + purgeStaleEntries(); + return map.get(new WeakReferenceThatIsEqualToItselfAndItsReferent((K) key, /* queue */ null)); + } + + @Override + public V put(K key, V value) { + return map.put(new WeakReferenceThatIsEqualToItselfAndItsReferent(key, queue), value); + } + + @SuppressWarnings("unchecked") // if the cast doesn't work then neither will the equals call, so null will result which is ok + @Override + public V remove(Object key) { + return map.remove(new WeakReferenceThatIsEqualToItselfAndItsReferent((K) key, /* queue */ null)); + } + + @Override + public void putAll(Map m) { + for (Map.Entry e : m.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set keySet() { + return new KeySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return new EntrySet(); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/LockUtil.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/LockUtil.java index f94ca339ae6..98deae3fc26 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/LockUtil.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/LockUtil.java @@ -1,9 +1,10 @@ package com.sap.sailing.util.impl; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; -import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; @@ -14,7 +15,7 @@ import java.util.logging.Logger; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sse.common.Util; /** * Supports lock management for {@link NamedReentrantReadWriteLock} which is a specialization of @@ -51,7 +52,7 @@ public class LockUtil { private static final int NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK = 5; private static final Logger logger = Logger.getLogger(Util.class.getName()); - private static final Map lastTimeWriteLockWasObtained = new WeakHashMap(); + private static final Map lastTimeWriteLockWasObtained = new ConcurrentWeakHashMap(); /** * Tells how many other threads propagated which held lock to the key thread. During propagation, a lock is @@ -63,20 +64,22 @@ public class LockUtil { * The thread-specific value maps are used as monitor objects whenever decisions about thread-specific lock counts * need to be made. */ - private static final Map> propagationCounts = new ConcurrentHashMap>(); + private static final Map> propagationCounts = new ConcurrentWeakHashMap>(); /** * Counts the "virtual" locks. A "virtual" lock is obtained if and only if at the time of calling * {@link #lockForRead(NamedReentrantReadWriteLock)} or {@link #lockForWrite(NamedReentrantReadWriteLock)} the - * respective lock has a positive {@link #propagationCounts propagation count} for the current thread. + * respective lock has a positive {@link #propagationCounts propagation count} for the current thread. Entries + * always have a positive integer value. {@link #decrement(Lock, Map)} removes entries whose integer count would + * go to 0. */ - private static final Map> virtualLockCounts = new ConcurrentHashMap>(); + private static final Map> virtualLockCounts = new ConcurrentWeakHashMap>(); /** * Redundant but easily accessible hold count per thread and lock. These are the actual lock hold counts as they are * recorded in the actual {@link NamedReentrantReadWriteLock} locks. */ - private static final Map> lockCounts = new ConcurrentHashMap>(); + private static final Map> lockCounts = new ConcurrentWeakHashMap>(); public static void lockForRead(NamedReentrantReadWriteLock lock) { acquireLockVirtuallyOrActually(lock, lock.readLock(), ReadOrWrite.READ); @@ -84,9 +87,7 @@ public class LockUtil { public static void lockForWrite(NamedReentrantReadWriteLock lock) { acquireLockVirtuallyOrActually(lock, lock.writeLock(), ReadOrWrite.WRITE); - synchronized (lastTimeWriteLockWasObtained) { - lastTimeWriteLockWasObtained.put(lock, MillisecondsTimePoint.now()); - } + lastTimeWriteLockWasObtained.put(lock, MillisecondsTimePoint.now()); } private static void acquireLockVirtuallyOrActually(NamedReentrantReadWriteLock lock, final Lock readOrWriteLock, final ReadOrWrite readOrWrite) { @@ -142,14 +143,9 @@ public class LockUtil { } public static void unlockAfterWrite(NamedReentrantReadWriteLock lock) { - Map currentThreadPropagationCounts = getCurrentThreadsPropagationCounts(); - synchronized (currentThreadPropagationCounts) { - unlockVirtuallyOrActually(lock, lock.writeLock()); - } + unlockVirtuallyOrActually(lock, lock.writeLock()); final TimePoint timePointWriteLockWasObtained; - synchronized (lastTimeWriteLockWasObtained) { - timePointWriteLockWasObtained = lastTimeWriteLockWasObtained.get(lock); - } + timePointWriteLockWasObtained = lastTimeWriteLockWasObtained.get(lock); if (timePointWriteLockWasObtained == null) { logger.info("Internal error: write lock " + lock.getName() + " to be unlocked but no time recorded for when it was last obtained.\n" @@ -355,9 +351,9 @@ public class LockUtil { } - private static String getStackTrace(Thread thread) { + private static String formatStackTrace(StackTraceElement[] stackTrace) { StringBuilder sb = new StringBuilder(); - for (StackTraceElement sf : thread.getStackTrace()) { + for (StackTraceElement sf : stackTrace) { sb.append(sf.toString()); sb.append('\n'); } @@ -365,7 +361,7 @@ public class LockUtil { } private static String getCurrentStackTrace() { - return getStackTrace(Thread.currentThread()); + return formatStackTrace(Thread.currentThread().getStackTrace()); } /** @@ -381,6 +377,14 @@ public class LockUtil { try { locked = lock.tryLock(NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK, TimeUnit.SECONDS); if (!locked) { + Thread writer = lockParent.getWriter(); + // capture the stack traces as quickly as possible to try to reflect the situation as it was when the lock couuldn't be obtained + StackTraceElement[] writerStackTrace = writer != null ? writer.getStackTrace() : null; + Map readerStackTraces = new HashMap(); + final List readers = lockParent.getReaders(); + for (Thread reader : readers) { + readerStackTraces.put(reader, reader.getStackTrace()); + } StringBuilder message = new StringBuilder(); message.append("Couldn't acquire lock "); message.append(lockDescriptionForTimeoutLogMessage); @@ -388,14 +392,15 @@ public class LockUtil { message.append(NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK); message.append("s in thread " + Thread.currentThread().getName() + " at "); message.append(getCurrentStackTrace()); - Thread writer = lockParent.getWriter(); if (writer != null) { message.append("\nThe current writer is:\n"); - appendThreadData(message, writer); + appendThreadData(message, writer, writerStackTrace); } - message.append("\nThe current readers are:\n"); - for (Thread reader : lockParent.getReaders()) { - appendThreadData(message, reader); + if (readers != null && !readers.isEmpty()) { + message.append("\nThe current readers are:\n"); + for (Thread reader : readers) { + appendThreadData(message, reader, readerStackTraces.get(reader)); + } } message.append("Trying again..."); logger.info(message.toString()); @@ -406,10 +411,10 @@ public class LockUtil { return locked; } - private static void appendThreadData(StringBuilder message, Thread writer) { + private static void appendThreadData(StringBuilder message, Thread writer, StackTraceElement[] stackTrace) { message.append(writer); message.append('\n'); - message.append(getStackTrace(writer)); + message.append(formatStackTrace(stackTrace)); message.append('\n'); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java index 1923731e032..584619fd706 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java @@ -9,7 +9,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantReadWriteLock; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implements Named { private static final long serialVersionUID = 2906084982209339774L; @@ -165,10 +165,18 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen return readLockWrapper; } + /** + * Contains the threads currently holding a read lock. Each thread is contained as many times as it + * successfully acquired the read lock re-entrantly. + */ public List getReaders() { return new ArrayList(readers); } + /** + * @return null if the write lock is not currently held by any thread; the thread holding the write + * lock otherwise. + */ public Thread getWriter() { return getOwner(); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.swisstimingadapter.test/META-INF/MANIFEST.MF index 42d23bb81e3..90d0a49ef40 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/META-INF/MANIFEST.MF @@ -17,7 +17,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.domain.tractracadapter, com.sap.sailing.domain.swisstimingadapter.persistence;bundle-version="1.0.0", org.junit4;bundle-version="4.8.2", - org.hamcrest.core;bundle-version="1.1.0" + org.hamcrest.core;bundle-version="1.1.0", + com.sap.sse.common Bundle-ClassPath: resources/, . Import-Package: com.sap.sailing.server diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/classes/services/MessageFileServiceImpl.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/classes/services/MessageFileServiceImpl.java index 748de273a20..4fdac1e0c57 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/classes/services/MessageFileServiceImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/classes/services/MessageFileServiceImpl.java @@ -23,9 +23,9 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.Mark; +import com.sap.sailing.domain.swisstimingadapter.Mark.MarkType; import com.sap.sailing.domain.swisstimingadapter.Race; import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage; -import com.sap.sailing.domain.swisstimingadapter.Mark.MarkType; import com.sap.sailing.domain.swisstimingadapter.classes.messages.CAMMessage; import com.sap.sailing.domain.swisstimingadapter.classes.messages.CCGMessage; import com.sap.sailing.domain.swisstimingadapter.classes.messages.ClockAtMarkElement; @@ -36,7 +36,7 @@ import com.sap.sailing.domain.swisstimingadapter.classes.messages.STLMessage; import com.sap.sailing.domain.swisstimingadapter.classes.messages.TMDMessage; import com.sap.sailing.domain.swisstimingadapter.classes.messages.TimingDataElement; import com.sap.sailing.domain.swisstimingadapter.classes.services.Exceptions.MessageScriptParsingException; -import com.sap.sailing.domain.swisstimingadapter.impl.CompetitorImpl; +import com.sap.sailing.domain.swisstimingadapter.impl.CompetitorWithoutID; import com.sap.sailing.domain.swisstimingadapter.impl.MarkImpl; import com.sap.sailing.domain.swisstimingadapter.impl.RaceImpl; import com.sap.sailing.domain.swisstimingadapter.impl.SailMasterMessageImpl; @@ -218,7 +218,7 @@ public class MessageFileServiceImpl implements MessageFileService { int count = Integer.valueOf(message.getSections()[2]); for (int i = 0; i < count; i++) { String[] competitorDetails = message.getSections()[3 + i].split(";"); - competitors.add(new CompetitorImpl(competitorDetails[0], competitorDetails[1], competitorDetails[2])); + competitors.add(new CompetitorWithoutID(competitorDetails[0], competitorDetails[1], competitorDetails[2])); } return new STLMessage(raceId, competitors); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryTest.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryTest.java index 99a9d34deac..f6b46ce87d1 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryTest.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryTest.java @@ -1,5 +1,9 @@ package com.sap.sailing.domain.swisstimingadapter.impl; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.assertThat; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -7,11 +11,6 @@ import org.junit.Test; import com.sap.sailing.domain.swisstimingadapter.RaceType; import com.sap.sailing.domain.swisstimingadapter.RaceType.OlympicRaceCode; -import static org.junit.Assert.*; - -import static org.hamcrest.core.Is.*; -import static org.hamcrest.core.IsNull.*; - public class DomainFactoryTest { private com.sap.sailing.domain.swisstimingadapter.impl.DomainFactoryImpl swissTimingDomainFactory; diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SailMasterConnectivityTest.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SailMasterConnectivityTest.java index d33087be848..31acbd4a0b0 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SailMasterConnectivityTest.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SailMasterConnectivityTest.java @@ -26,8 +26,6 @@ import org.junit.rules.Timeout; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.Course; import com.sap.sailing.domain.swisstimingadapter.Fix; @@ -41,6 +39,7 @@ import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage; import com.sap.sailing.domain.swisstimingadapter.StartList; import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; import com.sap.sailing.domain.swisstimingadapter.impl.RaceImpl; +import com.sap.sse.common.Util; public class SailMasterConnectivityTest { private static final Logger logger = Logger.getLogger(SailMasterConnectivityTest.class.getName()); @@ -61,12 +60,12 @@ public class SailMasterConnectivityTest { @Before public void setUp() throws InterruptedException, ParseException { startSailMasterDummy(); - Race race4702 = new RaceImpl("W4702", "470 Women Race 2"); - Race race4711 = new RaceImpl("4711", "A wonderful test race"); - Race race4712 = new RaceImpl("4712", "Not such a wonderful race"); - connector4702 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4702.getRaceID(), race4702.getDescription(), null /* boat class */); - connector4711 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4711.getRaceID(), race4711.getDescription(), null /* boat class */); - connector4712 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4712.getRaceID(), race4712.getDescription(), null /* boat class */); + Race race4702 = new RaceImpl("W4702", "R2", "470 Women Race 2"); + Race race4711 = new RaceImpl("4711", "R2", "A wonderful test race"); + Race race4712 = new RaceImpl("4712", "R2", "Not such a wonderful race"); + connector4702 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4702.getRaceID(), race4702.getRaceName(), race4702.getDescription(), null /* boat class */); + connector4711 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4711.getRaceID(), race4702.getRaceName(), race4711.getDescription(), null /* boat class */); + connector4712 = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("localhost", port, race4712.getRaceID(), race4702.getRaceName(), race4712.getDescription(), null /* boat class */); Thread.sleep(1000); // wait until all connectors are really connected to the SailMasterDummy } @@ -101,18 +100,19 @@ public class SailMasterConnectivityTest { final Collection fixesResult = new ArrayList(); connector4702.addSailMasterListener(new SailMasterListener() { @Override - public void receivedTimingData(String raceID, String boatID, List> markIndicesRanksAndTimesSinceStartInMilliseconds) {} + public void receivedTimingData(String raceID, String boatID, List> markIndicesRanksAndTimesSinceStartInMilliseconds) {} @Override public void receivedStartList(String raceID, StartList startList) {} @Override public void receivedCourseConfiguration(String raceID, Course course) {} @Override - public void receivedClockAtMark(String raceID, List> markIndicesTimePointsAndBoatIDs) {} + public void receivedClockAtMark(String raceID, List> markIndicesTimePointsAndBoatIDs) {} @Override public void receivedAvailableRaces(Iterable races) {} @Override public void storedDataProgress(String raceID, double progress) {} - + @Override + public void receivedWindData(String raceID, int zeroBasedMarkIndex, double windDirectionTrueDegrees, double windSpeedInKnots) {} @Override public void receivedRacePositionData(String raceID, RaceStatus status, TimePoint timePoint, TimePoint startTime, Long millisecondsSinceRaceStart, Integer nextMarkIndexForLeader, Distance distanceToNextMarkForLeaderInMeters, diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SimpleDomainFactoryTest.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SimpleDomainFactoryTest.java index b72affde3e8..95548182ac2 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SimpleDomainFactoryTest.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SimpleDomainFactoryTest.java @@ -16,7 +16,6 @@ import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.Course; @@ -30,6 +29,7 @@ import com.sap.sailing.domain.swisstimingadapter.impl.MarkImpl; import com.sap.sailing.domain.swisstimingadapter.impl.RaceImpl; import com.sap.sailing.domain.swisstimingadapter.impl.StartListImpl; import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sse.common.Util; import difflib.PatchFailedException; @@ -57,24 +57,26 @@ public class SimpleDomainFactoryTest { DomainFactory domainFactory = DomainFactory.INSTANCE; final String raceID = "SAX920103"; RaceDefinition raceDefinition = domainFactory.createRaceDefinition(domainFactory.getOrCreateDefaultRegatta( - EmptyRaceLogStore.INSTANCE, - raceID, null /* boat class */, - new RacingEventServiceImpl()), - new Race() { - @Override - public String getDescription() { - return null; - } + EmptyRaceLogStore.INSTANCE, raceID, null /* boat class */, new RacingEventServiceImpl()), new Race() { + @Override + public String getDescription() { + return null; + } - @Override - public String getRaceID() { - return raceID; - } + @Override + public String getRaceID() { + return raceID; + } - @Override - public BoatClass getBoatClass() { - return null; - } + @Override + public BoatClass getBoatClass() { + return null; + } + + @Override + public String getRaceName() { + return "The famous SAX920103 race"; + } }, new StartList() { @Override public String getRaceID() { @@ -106,7 +108,7 @@ public class SimpleDomainFactoryTest { DomainFactory domainFactory = DomainFactory.INSTANCE; Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, "TestEvent", /* boatClass */ null, new RacingEventServiceImpl(), com.sap.sailing.domain.base.DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null); - Race race = new RaceImpl("1234", "Race 1234"); + Race race = new RaceImpl("1234", "R1", "Race 1234"); Iterable competitors = Collections.emptyList(); StartList startList = new StartListImpl("1234", competitors); Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2"), /* markType */ null); @@ -131,7 +133,7 @@ public class SimpleDomainFactoryTest { DomainFactory domainFactory = DomainFactory.INSTANCE; Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, "TestEvent", /* boatClass */ null, new RacingEventServiceImpl(), com.sap.sailing.domain.base.DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null); - Race race = new RaceImpl("1234", "Race 1234"); + Race race = new RaceImpl("1234", "R1", "Race 1234"); Iterable competitors = Collections.emptyList(); StartList startList = new StartListImpl("1234", competitors); Mark mark1 = new MarkImpl("M1", 0, Arrays.asList("D1", "D2"), /* markType */ null); diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SwissTimingSailMasterLiveTest.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SwissTimingSailMasterLiveTest.java index 6fd62de9b7f..26ddf95ae79 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SwissTimingSailMasterLiveTest.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/SwissTimingSailMasterLiveTest.java @@ -21,9 +21,6 @@ import org.junit.Test; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.Course; import com.sap.sailing.domain.swisstimingadapter.Fix; @@ -34,6 +31,7 @@ import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector; import com.sap.sailing.domain.swisstimingadapter.SailMasterListener; import com.sap.sailing.domain.swisstimingadapter.StartList; import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; +import com.sap.sse.common.Util; @Ignore("This test doesn't work as long as the server doesn't play an actual race") public class SwissTimingSailMasterLiveTest implements SailMasterListener { @@ -42,7 +40,7 @@ public class SwissTimingSailMasterLiveTest implements SailMasterListener { @Before public void connect() throws InterruptedException, ParseException { - connector = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("gps.sportresult.com", 40300, "W4702", "Women 470 Race 2", null /* boat class*/); + connector = SwissTimingFactory.INSTANCE.getOrCreateSailMasterConnector("gps.sportresult.com", 40300, "W4702", "R2", "Women 470 Race 2", null /* boat class*/); } @After @@ -96,7 +94,7 @@ public class SwissTimingSailMasterLiveTest implements SailMasterListener { @Test public void testGetClockAtMark() throws UnknownHostException, IOException, InterruptedException, ParseException { Race race = connector.getRace(); - List> clockAtMark = connector.getClockAtMark(race.getRaceID()); + List> clockAtMark = connector.getClockAtMark(race.getRaceID()); assertFalse(clockAtMark.isEmpty()); } @@ -150,9 +148,9 @@ public class SwissTimingSailMasterLiveTest implements SailMasterListener { int numberOfMarks = Util.size(course.getMarks()); Iterable competitors = connector.getStartList(race.getRaceID()).getCompetitors(); for (Competitor competitor : competitors) { - Map> markPassingTimes = connector.getMarkPassingTimesInMillisecondsSinceRaceStart(race.getRaceID(), competitor.getBoatID()); + Map> markPassingTimes = connector.getMarkPassingTimesInMillisecondsSinceRaceStart(race.getRaceID(), competitor.getBoatID()); for (Integer markIndex : markPassingTimes.keySet()) { - Pair rankAndTime = markPassingTimes.get(markIndex); + com.sap.sse.common.Util.Pair rankAndTime = markPassingTimes.get(markIndex); for (int i=0; i> markIndicesRanksAndTimesSinceStartInMilliseconds) { + List> markIndicesRanksAndTimesSinceStartInMilliseconds) { } @Override public void receivedClockAtMark(String raceID, - List> markIndicesTimePointsAndBoatIDs) { + List> markIndicesTimePointsAndBoatIDs) { } @Override @@ -198,4 +196,9 @@ public class SwissTimingSailMasterLiveTest implements SailMasterListener { @Override public void storedDataProgress(String raceID, double progress) { } + + @Override + public void receivedWindData(String raceID, int zeroBasedMarkIndex, double windDirectionTrueDegrees, + double windSpeedInKnots) { + } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditCAM.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditCAM.java index 93df613d515..ac74434a56b 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditCAM.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditCAM.java @@ -10,16 +10,18 @@ */ package com.sap.sailing.domain.swisstimingadapter.test.ui; -import com.sap.sailing.domain.swisstimingadapter.classes.messages.CAMMessage; -import com.sap.sailing.domain.swisstimingadapter.classes.messages.ClockAtMarkElement; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; + import javax.swing.JOptionPane; +import com.sap.sailing.domain.swisstimingadapter.classes.messages.CAMMessage; +import com.sap.sailing.domain.swisstimingadapter.classes.messages.ClockAtMarkElement; + /** * * @author D056866 diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditRAC.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditRAC.java index f9ac33eda8f..9b4679e08ad 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditRAC.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditRAC.java @@ -149,7 +149,7 @@ public class EditRAC extends javax.swing.JDialog { private void jAddActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jAddActionPerformed // TODO add your handling code here: - races.add(new DescriptionRace(jRaceID.getText(), jRaceDesc.getText())); + races.add(new DescriptionRace(jRaceID.getText(), jRaceID.getText(), jRaceDesc.getText())); jRaceList.setListData(races.toArray(new Race[0])); }//GEN-LAST:event_jAddActionPerformed @@ -173,8 +173,8 @@ public class EditRAC extends javax.swing.JDialog { private class DescriptionRace extends RaceImpl { - public DescriptionRace(String raceId, String desc){ - super(raceId, desc); + public DescriptionRace(String raceId, String raceName, String desc){ + super(raceId, raceName, desc); } @Override diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditSTL.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditSTL.java index 76420dde43d..7eee29387a2 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditSTL.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditSTL.java @@ -10,10 +10,11 @@ */ package com.sap.sailing.domain.swisstimingadapter.test.ui; +import java.util.List; + import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.classes.messages.STLMessage; -import com.sap.sailing.domain.swisstimingadapter.impl.CompetitorImpl; -import java.util.List; +import com.sap.sailing.domain.swisstimingadapter.impl.CompetitorWithoutID; /** * @@ -107,11 +108,11 @@ public class EditSTL extends javax.swing.JDialog { }); jCompetitorList.setModel(new javax.swing.AbstractListModel() { - Competitor[] competitors = { new CompetitorImpl("Item 1", "DEU", "Item 1"), - new CompetitorImpl("Item 2", "DEU", "Item 2"), - new CompetitorImpl("Item 3", "DEU", "Item 3"), - new CompetitorImpl("Item 4", "DEU", "Item 4"), - new CompetitorImpl("Item 5", "DEU", "Item 5") }; + Competitor[] competitors = { new CompetitorWithoutID("Item 1", "DEU", "Item 1"), + new CompetitorWithoutID("Item 2", "DEU", "Item 2"), + new CompetitorWithoutID("Item 3", "DEU", "Item 3"), + new CompetitorWithoutID("Item 4", "DEU", "Item 4"), + new CompetitorWithoutID("Item 5", "DEU", "Item 5") }; public int getSize() { return competitors.length; } public Competitor getElementAt(int i) { return competitors[i]; } }); @@ -209,7 +210,7 @@ public class EditSTL extends javax.swing.JDialog { return stl; } - private class CompetitorWithDesc extends CompetitorImpl { + private class CompetitorWithDesc extends CompetitorWithoutID { public CompetitorWithDesc(String boatID, String NOC, String name){ super(boatID, NOC, name); diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/SwissTimingRaceEditor.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/SwissTimingRaceEditor.java index b74f7b8902c..fb3b82aad9b 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/SwissTimingRaceEditor.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/SwissTimingRaceEditor.java @@ -10,6 +10,17 @@ */ package com.sap.sailing.domain.swisstimingadapter.test.ui; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.filechooser.FileFilter; + import com.sap.sailing.domain.swisstimingadapter.classes.messages.ABSMessage; import com.sap.sailing.domain.swisstimingadapter.classes.messages.CAMMessage; import com.sap.sailing.domain.swisstimingadapter.classes.messages.CCGMessage; @@ -20,16 +31,6 @@ import com.sap.sailing.domain.swisstimingadapter.classes.messages.TMDMessage; import com.sap.sailing.domain.swisstimingadapter.classes.services.MessageFileService; import com.sap.sailing.domain.swisstimingadapter.classes.services.MessageFileServiceImpl; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; -import javax.swing.filechooser.FileFilter; - /** * * @author D056866 diff --git a/java/com.sap.sailing.domain.swisstimingadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.swisstimingadapter/META-INF/MANIFEST.MF index 4d2d250eeed..577a544907c 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.swisstimingadapter/META-INF/MANIFEST.MF @@ -9,7 +9,9 @@ Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sailing.domain, com.sap.sailing.udpconnector, com.googlecode.java-diff-utils;bundle-version="1.3.0", - com.sap.sailing.domain.common + com.sap.sailing.domain.common, + com.sap.sailing.xrr.resultimport, + com.sap.sse.common Export-Package: com.sap.sailing.domain.swisstimingadapter, com.sap.sailing.domain.swisstimingadapter.impl;x-friends:="com.sap.sailing.server.test" Import-Package: org.osgi.framework;version="1.7.0" diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/Competitor.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/Competitor.java index b63ec027a2f..48641dd6dbc 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/Competitor.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/Competitor.java @@ -1,9 +1,15 @@ package com.sap.sailing.domain.swisstimingadapter; +import java.util.List; + public interface Competitor { String getBoatID(); String getThreeLetterIOCCode(); + List getCrew(); + String getName(); + + String getID(); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/CrewMember.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/CrewMember.java new file mode 100644 index 00000000000..0406a38b8ff --- /dev/null +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/CrewMember.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.swisstimingadapter; + +public interface CrewMember { + public String getName(); + + public String getNationality(); + + public String getPosition(); +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java index 9906b5a0603..9ca4001ee67 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java @@ -34,12 +34,16 @@ public interface DomainFactory { Nationality getOrCreateNationality(String threeLetterIOCCode); - Competitor getOrCreateCompetitor(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, String raceId, BoatClass boatClass); - - Competitor getOrCreateCompetitor(String boatID, String threeLetterIOCCode, String name, String raceId, BoatClass boatClass); + Competitor createCompetitorWithoutID(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, String raceId, BoatClass boatClass); - Competitor getCompetitorByBoatIDAndRaceIDOrBoatClass(String boatID, String raceID, BoatClass boatClass); + Competitor createCompetitorWithID(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, BoatClass boatClass); + + Competitor createCompetitorWithoutID(String boatID, String threeLetterIOCCode, String name, String raceId, BoatClass boatClass); + String getCompetitorID(String boatID, RaceType raceType); + + String getCompetitorID(String boatID, BoatClass boatClass); + RaceDefinition createRaceDefinition(Regatta regatta, Race race, StartList startList, com.sap.sailing.domain.swisstimingadapter.Course course); com.sap.sailing.domain.base.Mark getOrCreateMark(String trackerID); @@ -54,8 +58,8 @@ public interface DomainFactory { RaceType getRaceTypeFromRaceID(String raceID); - RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, - String raceDescription, BoatClass boatClass, long delayToLiveInMillis, + RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, String raceName, + String raceDescription, BoatClass boatClass, StartList startList, long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore); ControlPoint getOrCreateControlPoint(Iterable devices); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/Race.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/Race.java index ea2d70610ab..a505eb1092a 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/Race.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/Race.java @@ -5,5 +5,6 @@ import com.sap.sailing.domain.base.BoatClass; public interface Race extends WithDescription { String getRaceID(); + String getRaceName(); BoatClass getBoatClass(); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterAdapter.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterAdapter.java index 746fd834564..9cc91e9000b 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterAdapter.java @@ -5,7 +5,7 @@ import java.util.List; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; public abstract class SailMasterAdapter implements SailMasterListener { @@ -17,12 +17,12 @@ public abstract class SailMasterAdapter implements SailMasterListener { @Override public void receivedTimingData(String raceID, String boatID, - List> markIndicesRanksAndTimesSinceStartInMilliseconds) { + List> markIndicesRanksAndTimesSinceStartInMilliseconds) { } @Override public void receivedClockAtMark(String raceID, - List> markIndicesTimePointsAndBoatIDs) { + List> markIndicesTimePointsAndBoatIDs) { } @Override @@ -41,4 +41,8 @@ public abstract class SailMasterAdapter implements SailMasterListener { public void storedDataProgress(String raceID, double progress) { } + @Override + public void receivedWindData(String raceID, int markIndex, double windDirectionTrueDegrees, double windSpeedInKnots) { + } + } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterConnector.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterConnector.java index 473c3beb6ff..a8ba1c6dfaa 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterConnector.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterConnector.java @@ -9,8 +9,7 @@ import java.util.Map; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; public interface SailMasterConnector { SailMasterMessage sendRequestAndGetResponse(MessageType messageType, String... args) throws UnknownHostException, IOException, InterruptedException; @@ -39,7 +38,7 @@ public interface SailMasterConnector { * boatID passed the mark. * @throws InterruptedException */ - Map> getMarkPassingTimesInMillisecondsSinceRaceStart(String raceID, String boatID) + Map> getMarkPassingTimesInMillisecondsSinceRaceStart(String raceID, String boatID) throws UnknownHostException, IOException, InterruptedException; /** @@ -48,7 +47,7 @@ public interface SailMasterConnector { */ void addSailMasterListener(SailMasterListener listener) throws UnknownHostException, IOException, InterruptedException; - void removeSailMasterListener(SailMasterListener listener); + void removeSailMasterListener(SailMasterListener listener) throws IOException; SailMasterMessage receiveMessage(MessageType type) throws InterruptedException; @@ -69,7 +68,7 @@ public interface SailMasterConnector { * @return the list of mark index / mark time / sail number triplets telling when which leader * first passed the mark with the respective index */ - List> getClockAtMark(String raceID) throws ParseException, UnknownHostException, IOException, InterruptedException; + List> getClockAtMark(String raceID) throws ParseException, UnknownHostException, IOException, InterruptedException; /** * Stops this connector's thread that is started upon creation and responsible for receiving spontaneous events @@ -78,4 +77,11 @@ public interface SailMasterConnector { void stop() throws IOException; boolean isStopped(); + + /** + * Some messages in the SwissTiming SailMaster protocol lack proper time stamp information. It is therefore + * necessary to keep track of time stamps received from other messages and use them as an approximation for + * the time point of messages received without explicit time stamp. + */ + TimePoint getLastRPDMessageTimePoint(); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java index 14535538728..e6cf48b01af 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SailMasterListener.java @@ -5,16 +5,16 @@ import java.util.List; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; public interface SailMasterListener { void receivedRacePositionData(String raceID, RaceStatus status, TimePoint timePoint, TimePoint startTime, Long millisecondsSinceRaceStart, Integer nextMarkIndexForLeader, Distance distanceToNextMarkForLeader, Collection fixes); - void receivedTimingData(String raceID, String boatID, List> markIndicesRanksAndTimesSinceStartInMilliseconds); + void receivedTimingData(String raceID, String boatID, List> markIndicesRanksAndTimesSinceStartInMilliseconds); - void receivedClockAtMark(String raceID, List> markIndicesTimePointsAndBoatIDs); + void receivedClockAtMark(String raceID, List> markIndicesTimePointsAndBoatIDs); void receivedStartList(String raceID, StartList startList); @@ -25,7 +25,8 @@ public interface SailMasterListener { /** * Announces the progress of data loaded from a local persistent store (as opposed to receiving it live from a * SailMaster instance). 0.0 means no progress so far, 1.0 means loading from persistent store completed. - * @param raceID TODO */ void storedDataProgress(String raceID, double progress); + + void receivedWindData(String raceID, int zeroBasedMarkIndex, double windDirectionTrueDegrees, double windSpeedInKnots); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java index 6b20bbc2d43..a07040a69b6 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java @@ -5,11 +5,14 @@ import java.net.UnknownHostException; import java.text.ParseException; import java.util.List; +import javax.xml.bind.JAXBException; + import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.tracking.RacesHandle; import com.sap.sailing.domain.tracking.TrackerManager; +import com.sap.sailing.xrr.resultimport.schema.RegattaResults; public interface SwissTimingAdapter { List getSwissTimingRaceRecords(String hostname, int port) throws InterruptedException, UnknownHostException, IOException, ParseException; @@ -23,10 +26,14 @@ public interface SwissTimingAdapter { * to add the races to. Otherwise, a default regatta as described above will be created and used. * @param raceDescription TODO */ - RacesHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceDescription, - BoatClass boatClass, String hostname, int port, RaceLogStore logStore, long timeoutInMilliseconds) + RacesHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription, + BoatClass boatClass, String hostname, int port, StartList startList, RaceLogStore logStore, long timeoutInMilliseconds) throws InterruptedException, UnknownHostException, IOException, ParseException, Exception; + StartList readStartListForRace(String raceId, RegattaResults regattaResults); + + RegattaResults readRegattaEntryListFromXrrUrl(String xrrEntryListUrl) throws IOException, JAXBException; + SwissTimingFactory getSwissTimingFactory(); com.sap.sailing.domain.swisstimingadapter.DomainFactory getSwissTimingDomainFactory(); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java index 2662e47b988..d05976732cb 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java @@ -1,66 +1,67 @@ -package com.sap.sailing.domain.swisstimingadapter; - -import java.io.IOException; -import java.net.UnknownHostException; -import java.text.ParseException; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.racelog.RaceLogStore; -import com.sap.sailing.domain.swisstimingadapter.impl.SwissTimingFactoryImpl; -import com.sap.sailing.domain.tracking.RaceTracker; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.domain.tracking.WindStore; - -public interface SwissTimingFactory { - SwissTimingFactory INSTANCE = new SwissTimingFactoryImpl(); - - SwissTimingMessageParser createMessageParser(); - - /** - * Obtains a connector to one or more SwissTiming Sail Master system(s). Such a connector uses a host name and port - * number to establish the connecting via TCP. The connector offers a number of explicit service request methods. - * Additionally, the connector can receive "spontaneous" events sent by the sail master system. Clients can register - * for those spontaneous events (see {@link SailMasterConnector#addSailMasterListener}). - *

- * - * When the connector is used with SailMaster instances hidden behind a "bridge" / firewall, no explicit requests - * are possible, and the connector has to rely solely on the events it receives. It may, though, load recorded - * race-specific messages through a {@link RaceSpecificMessageLoader} object. If a non-null - * {@link RaceSpecificMessageLoader} is provided, the connector will fetch the {@link #getRaces() list of races} - * from that loader. Additionally, the connector will use the loader upon each - * {@link SailMasterConnector#trackRace(String)} to load all messages recorded by the loader for the race requested - * so far. - *

- * - * Generally, the connector needs to be instructed for which races it shall handle events using calls to the - * {@link SailMasterConnector#trackRace} and {@link SailMasterConnector#stopTrackingRace} operations. - * {@link MessageType#isRaceSpecific() Race-specific messages} for other races are ignored and not forwarded to any - * listener. - */ - SailMasterConnector getOrCreateSailMasterConnector(String hostname, int port, String raceId, String raceDescription, BoatClass boatClass) - throws InterruptedException, ParseException; - - SailMasterConnector getOrCreateSailMasterLiveSimulatorConnector(String host, int port, String raceId, - String raceDescription, BoatClass boatClass) throws InterruptedException, ParseException; - - SailMasterTransceiver createSailMasterTransceiver(); - - SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, int port); - - SwissTimingRaceTracker createRaceTracker(String raceID, String raceDescription, BoatClass boatClass, String hostname, int port, - long delayToLiveInMillis, RaceLogStore raceLogStore, WindStore windStore, - DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, UnknownHostException, - IOException, ParseException; - - RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceDescription, BoatClass boatClass, String hostname, int port, long delayToLiveInMillis, - WindStore windStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException, InterruptedException, - IOException, ParseException; - - Race createRace(String raceId, String description, BoatClass boatClass); - - SailMasterMessage createMessage(String message); - - SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration(String string); - -} +package com.sap.sailing.domain.swisstimingadapter; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.text.ParseException; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.swisstimingadapter.impl.SwissTimingFactoryImpl; +import com.sap.sailing.domain.tracking.RaceTracker; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.WindStore; + +public interface SwissTimingFactory { + SwissTimingFactory INSTANCE = new SwissTimingFactoryImpl(); + + SwissTimingMessageParser createMessageParser(); + + /** + * Obtains a connector to one or more SwissTiming Sail Master system(s). Such a connector uses a host name and port + * number to establish the connecting via TCP. The connector offers a number of explicit service request methods. + * Additionally, the connector can receive "spontaneous" events sent by the sail master system. Clients can register + * for those spontaneous events (see {@link SailMasterConnector#addSailMasterListener}). + *

+ * + * When the connector is used with SailMaster instances hidden behind a "bridge" / firewall, no explicit requests + * are possible, and the connector has to rely solely on the events it receives. It may, though, load recorded + * race-specific messages through a {@link RaceSpecificMessageLoader} object. If a non-null + * {@link RaceSpecificMessageLoader} is provided, the connector will fetch the {@link #getRaces() list of races} + * from that loader. Additionally, the connector will use the loader upon each + * {@link SailMasterConnector#trackRace(String)} to load all messages recorded by the loader for the race requested + * so far. + *

+ * + * Generally, the connector needs to be instructed for which races it shall handle events using calls to the + * {@link SailMasterConnector#trackRace} and {@link SailMasterConnector#stopTrackingRace} operations. + * {@link MessageType#isRaceSpecific() Race-specific messages} for other races are ignored and not forwarded to any + * listener. + */ + SailMasterConnector getOrCreateSailMasterConnector(String hostname, int port, String raceId, String raceName, String raceDescription, BoatClass boatClass) + throws InterruptedException, ParseException; + + SailMasterConnector getOrCreateSailMasterLiveSimulatorConnector(String host, int port, String raceId, String raceName, + String raceDescription, BoatClass boatClass) throws InterruptedException, ParseException; + + SailMasterTransceiver createSailMasterTransceiver(); + + SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, int port); + + SwissTimingRaceTracker createRaceTracker(String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, + StartList startList, long delayToLiveInMillis, RaceLogStore raceLogStore, WindStore windStore, GPSFixStore gpsFixStore, + DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, UnknownHostException, + IOException, ParseException; + + RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, + StartList startList, long delayToLiveInMillis, WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException, InterruptedException, + IOException, ParseException; + + Race createRace(String raceId, String raceName, String description, BoatClass boatClass); + + SailMasterMessage createMessage(String message); + + SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration(String string); + +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingRaceTracker.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingRaceTracker.java index e1d5dce08d4..2da24a55968 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingRaceTracker.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingRaceTracker.java @@ -1,11 +1,15 @@ package com.sap.sailing.domain.swisstimingadapter; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.tracking.RaceTracker; +import com.sap.sse.common.Util; public interface SwissTimingRaceTracker extends RaceTracker { /** * The key for a SwissTiming race tracker is the triple of raceID, host name and port number. */ - Triple getID(); + Util.Triple getID(); + + Competitor getCompetitorByBoatIDAndRaceIDOrBoatClass(String boatID, String raceID, BoatClass boatClass); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CompetitorWithID.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CompetitorWithID.java new file mode 100644 index 00000000000..1b0d9499523 --- /dev/null +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CompetitorWithID.java @@ -0,0 +1,55 @@ +package com.sap.sailing.domain.swisstimingadapter.impl; + +import java.util.List; + +import com.sap.sailing.domain.swisstimingadapter.Competitor; +import com.sap.sailing.domain.swisstimingadapter.CrewMember; + +public class CompetitorWithID implements Competitor { + private final String boatID; + private final String threeLetterIOCCode; + private final String name; + private final String id; + + private List crew; + + public CompetitorWithID(String id, String boatID, String threeLetterIOCCode, String name, List crew) { + super(); + this.boatID = boatID; + this.threeLetterIOCCode = threeLetterIOCCode; + this.name = name; + this.id = id; + this.crew = crew; + } + + @Override + public String getBoatID() { + return boatID; + } + + @Override + public String getThreeLetterIOCCode() { + return threeLetterIOCCode; + } + + @Override + public String getName() { + return name; + } + + public String getID() { + return id; + } + + @Override + public String toString() { + return "CompetitorWithID [boatID=" + boatID + ", threeLetterIOCCode=" + + threeLetterIOCCode + ", name=" + name + ", id=" + id + "]"; + } + + @Override + public List getCrew() { + return crew; + } + +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CompetitorImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CompetitorWithoutID.java similarity index 56% rename from java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CompetitorImpl.java rename to java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CompetitorWithoutID.java index dd2290e1e3f..80b484c798f 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CompetitorImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CompetitorWithoutID.java @@ -1,13 +1,16 @@ package com.sap.sailing.domain.swisstimingadapter.impl; -import com.sap.sailing.domain.swisstimingadapter.Competitor; +import java.util.List; -public class CompetitorImpl implements Competitor { +import com.sap.sailing.domain.swisstimingadapter.Competitor; +import com.sap.sailing.domain.swisstimingadapter.CrewMember; + +public class CompetitorWithoutID implements Competitor { private final String boatID; private final String threeLetterIOCCode; private final String name; - public CompetitorImpl(String boatID, String threeLetterIOCCode, String name) { + public CompetitorWithoutID(String boatID, String threeLetterIOCCode, String name) { super(); this.boatID = boatID; this.threeLetterIOCCode = threeLetterIOCCode; @@ -29,13 +32,19 @@ public class CompetitorImpl implements Competitor { return name; } - @Override - public String toString() { - return name+"/"+boatID; - } - public String getID() { return null; } + @Override + public String toString() { + return "CompetitorWithoutID [boatID=" + boatID + ", threeLetterIOCCode=" + + threeLetterIOCCode + ", name=" + name + "]"; + } + + @Override + public List getCrew() { + return null; + } + } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CrewMemberImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CrewMemberImpl.java new file mode 100644 index 00000000000..edd06e5ca15 --- /dev/null +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/CrewMemberImpl.java @@ -0,0 +1,31 @@ +package com.sap.sailing.domain.swisstimingadapter.impl; + +import com.sap.sailing.domain.swisstimingadapter.CrewMember; + +public class CrewMemberImpl implements CrewMember { + private String name; + private String nationality; + private String position; + + public CrewMemberImpl(String name, String nationality, String position) { + super(); + this.name = name; + this.nationality = nationality; + this.position = position; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getNationality() { + return nationality; + } + + public String getPosition() { + return position; + } + +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index d0629b12d42..eba034fb674 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.swisstimingadapter.impl; import java.util.ArrayList; +import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -26,16 +27,14 @@ import com.sap.sailing.domain.base.impl.PersonImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.TeamImpl; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WithID; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.swisstimingadapter.Course; +import com.sap.sailing.domain.swisstimingadapter.CrewMember; import com.sap.sailing.domain.swisstimingadapter.DomainFactory; import com.sap.sailing.domain.swisstimingadapter.Fix; import com.sap.sailing.domain.swisstimingadapter.Mark; @@ -50,6 +49,8 @@ import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sse.common.Named; +import com.sap.sse.common.Util; import difflib.PatchFailedException; @@ -97,7 +98,8 @@ public class DomainFactoryImpl implements DomainFactory { } if (result == null) { BoatClass regattaBoatClass = boatClass != null ? boatClass : getRaceTypeFromRaceID(raceID).getBoatClass(); - result = new RegattaImpl(raceLogStore, raceID, regattaBoatClass, trackedRegattaRegistry, + Calendar calendar = Calendar.getInstance(); + result = new RegattaImpl(raceLogStore, "ST Regatta " + calendar.get(Calendar.YEAR)+" for race "+raceID, regattaBoatClass, trackedRegattaRegistry, getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), raceID, null); logger.info("Created regatta "+result.getName()+" ("+result.hashCode()+")"); raceIDToRegattaCache.put(raceID, result); @@ -105,28 +107,44 @@ public class DomainFactoryImpl implements DomainFactory { return result; } - private Competitor getCompetitorByBoatIDAndRaceType(String boatID, RaceType raceType) { - return baseDomainFactory.getExistingCompetitorById(getCompetitorID(boatID, raceType)); - } - - private Competitor getCompetitorByBoatIDAndBoatClass(String boatID, BoatClass boatClass) { - return baseDomainFactory.getExistingCompetitorById(getCompetitorID(boatID, boatClass)); + @Override + public Competitor createCompetitorWithID(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, BoatClass boatClass) { + CompetitorStore competitorStore = baseDomainFactory.getCompetitorStore(); + Competitor result = competitorStore.getExistingCompetitorByIdAsString(competitor.getID()); + if (result == null || competitorStore.isCompetitorToUpdateDuringGetOrCreate(result)) { + DynamicBoat boat = new BoatImpl(competitor.getName(), boatClass, competitor.getBoatID()); + List teamMembers = new ArrayList(); + for (CrewMember crewMember: competitor.getCrew()) { + DynamicPerson person = new PersonImpl(crewMember.getName().trim(), getOrCreateNationality(crewMember.getNationality()), + /* dateOfBirth */ null, crewMember.getPosition()); + teamMembers.add(person); + } + DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); + result = competitorStore.getOrCreateCompetitor(competitor.getID(), competitor.getName(), null /*displayColor*/, team, boat); + } + return result; } @Override - public Competitor getCompetitorByBoatIDAndRaceIDOrBoatClass(String boatID, String raceID, BoatClass boatClass) { - Competitor result = null; - if(boatClass != null) { - result = getCompetitorByBoatIDAndBoatClass(boatID, boatClass); - } else { - RaceType raceType = getRaceTypeFromRaceID(raceID); - if (raceType != null) { - result = getCompetitorByBoatIDAndRaceType(boatID, raceType); - } + public Competitor createCompetitorWithoutID(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, String raceId, BoatClass boatClass) { + Competitor result = null; + DynamicBoat boat = new BoatImpl(competitor.getName(), boatClass, competitor.getBoatID()); + List teamMembers = new ArrayList(); + for (String teamMemberName : competitor.getName().split("[-+&]")) { + teamMembers.add(new PersonImpl(teamMemberName.trim(), getOrCreateNationality(competitor.getThreeLetterIOCCode()), + /* dateOfBirth */ null, teamMemberName.trim())); } + DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); + result = baseDomainFactory.getCompetitorStore().getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), raceId, boatClass), + competitor.getName(), null /*displayColor*/, team, boat); return result; } + @Override + public Competitor createCompetitorWithoutID(String boatID, String threeLetterIOCCode, String name, String raceId, BoatClass boatClass) { + return createCompetitorWithoutID(new CompetitorWithoutID(boatID, threeLetterIOCCode, name), raceId, boatClass); + } + private String getCompetitorID(String boatID, String raceId, BoatClass boatClass) { String result = null; if(boatClass != null) { @@ -140,36 +158,15 @@ public class DomainFactoryImpl implements DomainFactory { return result; } - private String getCompetitorID(String boatID, RaceType raceType) { + @Override + public String getCompetitorID(String boatID, RaceType raceType) { return boatID + "/" + raceType.getRaceCode(); } - private String getCompetitorID(String boatID, BoatClass boatClass) { + @Override + public String getCompetitorID(String boatID, BoatClass boatClass) { return boatID + "/" + boatClass.getName(); } - - @Override - public Competitor getOrCreateCompetitor(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, String raceId, BoatClass boatClass) { - Competitor result = getCompetitorByBoatIDAndRaceIDOrBoatClass(competitor.getBoatID(), raceId, boatClass); - CompetitorStore competitorStore = baseDomainFactory.getCompetitorStore(); - if (result == null || competitorStore.isCompetitorToUpdateDuringGetOrCreate(result)) { - DynamicBoat boat = new BoatImpl(competitor.getName(), boatClass, competitor.getBoatID()); - List teamMembers = new ArrayList(); - for (String teamMemberName : competitor.getName().split("[-+&]")) { - teamMembers.add(new PersonImpl(teamMemberName.trim(), getOrCreateNationality(competitor.getThreeLetterIOCCode()), - /* dateOfBirth */ null, teamMemberName.trim())); - } - DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); - result = competitorStore.getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), raceId, boatClass), - competitor.getName(), null /*displayColor*/, team, boat); - } - return result; - } - - @Override - public Competitor getOrCreateCompetitor(String boatID, String threeLetterIOCCode, String name, String raceId, BoatClass boatClass) { - return getOrCreateCompetitor(new CompetitorImpl(boatID, threeLetterIOCCode, name), raceId, boatClass); - } @Override public RaceDefinition createRaceDefinition(Regatta regatta, String raceID, Iterable competitors, @@ -193,7 +190,7 @@ public class DomainFactoryImpl implements DomainFactory { Iterable competitors = createCompetitorList(startList, race.getRaceID(), race.getBoatClass()); logger.info("Creating RaceDefinitionImpl for race "+race.getRaceID()); BoatClass boatClass = race.getBoatClass() != null ? race.getBoatClass() : getRaceTypeFromRaceID(race.getRaceID()).getBoatClass(); - RaceDefinition result = new RaceDefinitionImpl(race.getRaceID(), domainCourse, boatClass, competitors); + RaceDefinition result = new RaceDefinitionImpl(race.getRaceName(), domainCourse, boatClass, competitors, race.getRaceID()); regatta.addRace(result); return result; } @@ -246,7 +243,12 @@ public class DomainFactoryImpl implements DomainFactory { private Iterable createCompetitorList(StartList startList, String raceId, BoatClass boatClass) { List result = new ArrayList(); for (com.sap.sailing.domain.swisstimingadapter.Competitor swissTimingCompetitor : startList.getCompetitors()) { - Competitor domainCompetitor = getOrCreateCompetitor(swissTimingCompetitor, raceId, boatClass); + Competitor domainCompetitor; + if(swissTimingCompetitor.getID() != null) { + domainCompetitor = createCompetitorWithID(swissTimingCompetitor, boatClass); + } else { + domainCompetitor = createCompetitorWithoutID(swissTimingCompetitor, raceId, boatClass); + } result.add(domainCompetitor); } return result; @@ -309,11 +311,11 @@ public class DomainFactoryImpl implements DomainFactory { @Override public void updateCourseWaypoints(com.sap.sailing.domain.base.Course courseToUpdate, Iterable marks) throws PatchFailedException { - List> newDomainControlPoints = new ArrayList>(); + List> newDomainControlPoints = new ArrayList>(); for (Mark mark : marks) { // TODO bug 1043: propagate the mark names to the waypoint names com.sap.sailing.domain.base.ControlPoint domainControlPoint = getOrCreateControlPoint(mark.getDevices()); - newDomainControlPoints.add(new Pair(domainControlPoint, null)); + newDomainControlPoints.add(new com.sap.sse.common.Util.Pair(domainControlPoint, null)); } courseToUpdate.update(newDomainControlPoints, baseDomainFactory); } @@ -348,10 +350,10 @@ public class DomainFactoryImpl implements DomainFactory { } @Override - public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, - String raceDescription, BoatClass boatClass, long delayToLiveInMillis, + public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, String raceName, + String raceDescription, BoatClass boatClass, StartList startList, long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore) { - return new SwissTimingTrackingConnectivityParameters(hostname, port, raceID, raceDescription, boatClass, delayToLiveInMillis, + return new SwissTimingTrackingConnectivityParameters(hostname, port, raceID, raceName, raceDescription, boatClass, startList, delayToLiveInMillis, swissTimingFactory, domainFactory, raceLogStore); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/RaceImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/RaceImpl.java index 6eb078fc334..94cb843f583 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/RaceImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/RaceImpl.java @@ -6,16 +6,18 @@ import com.sap.sailing.domain.swisstimingadapter.Race; public class RaceImpl implements Race { private final String description; private final String raceId; + private final String raceName; private final BoatClass boatClass; - public RaceImpl(String raceId, String description, BoatClass boatClass) { + public RaceImpl(String raceId, String raceName, String description, BoatClass boatClass) { this.raceId = raceId; + this.raceName = raceName; this.description = description; this.boatClass = boatClass; } - public RaceImpl(String raceId, String description) { - this(raceId, description, null); + public RaceImpl(String raceId, String raceName, String description) { + this(raceId, raceName, description, null); } @Override @@ -32,4 +34,9 @@ public class RaceImpl implements Race { public BoatClass getBoatClass() { return boatClass; } + + @Override + public String getRaceName() { + return raceName; + } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java index a2a82fe2702..532d1390863 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java @@ -39,8 +39,6 @@ import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.swisstimingadapter.Competitor; import com.sap.sailing.domain.swisstimingadapter.Course; import com.sap.sailing.domain.swisstimingadapter.Fix; @@ -54,6 +52,7 @@ import com.sap.sailing.domain.swisstimingadapter.SailMasterListener; import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage; import com.sap.sailing.domain.swisstimingadapter.StartList; import com.sap.sailing.domain.swisstimingadapter.TrackerType; +import com.sap.sse.common.Util; /** * Implements the connector to the SwissTiming Sail Master system. It uses a host name and port number to establish the @@ -90,8 +89,8 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement private final Set listeners; private final Thread receiverThread; private boolean stopped; - private boolean connected; private final String raceId; + private final String raceName; private final String raceDescription; private final BoatClass boatClass; @@ -108,6 +107,13 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement */ private String lastTimeZoneSuffix; + /** + * Some messages in the SwissTiming SailMaster protocol lack proper time stamp information. It is therefore + * necessary to keep track of time stamps received from other messages and use them as an approximation for + * the time point of messages received without explicit time stamp. + */ + private TimePoint lastRPDMessageTimePoint; + private TimePoint startTime; /** @@ -121,10 +127,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement private Long numberOfStoredMessages; - public SailMasterConnectorImpl(String host, int port, String raceId, String raceDescription, BoatClass boatClass) throws InterruptedException, ParseException { + public SailMasterConnectorImpl(String host, int port, String raceId, String raceName, String raceDescription, BoatClass boatClass) throws InterruptedException, ParseException { super(); maxSequenceNumber = -1l; this.raceId = raceId; // from this time on, the connector interprets messages for raceID + this.raceName = raceName; this.raceDescription = raceDescription; this.boatClass = boatClass; dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); @@ -134,45 +141,43 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement this.unprocessedMessagesByType = new HashMap>(); receiverThread = new Thread(this, "SwissTiming SailMaster Receiver"); receiverThread.start(); - synchronized (this) { - while (!connected) { - wait(); - } - } } public void run() { try { while (!stopped) { try { - ensureSocketIsOpen(); - String receivedMessage = receiveMessage(socket.getInputStream()); - if (receivedMessage == null) { - // reached EOF; this means the socket is or can be closed - if (socket != null && !socket.isClosed()) { - socket.close(); - } - socket = null; - } else { - SailMasterMessage message = new SailMasterMessageImpl(receivedMessage); - // drop race-specific messages for non-tracked races - if (message.getSequenceNumber() != null) { - maxSequenceNumber = Math.max(maxSequenceNumber, message.getSequenceNumber()); - if (maxSequenceNumber <= numberOfStoredMessages) { - notifyListenersStoredDataProgress(raceId, (double) maxSequenceNumber / (double) numberOfStoredMessages); + ensureSocketIsOpen(); // the result of this may be that the connector w\s stopped in between, so another check is required + if (!stopped && socket != null) { + String receivedMessage = receiveMessage(socket.getInputStream()); + if (receivedMessage == null) { + // reached EOF; this means the socket is or can be closed + if (socket != null && !socket.isClosed()) { + socket.close(); + } + socket = null; + } else { + SailMasterMessage message = new SailMasterMessageImpl(receivedMessage); + // drop race-specific messages for non-tracked races + if (message.getSequenceNumber() != null) { + maxSequenceNumber = Math.max(maxSequenceNumber, message.getSequenceNumber()); + if (maxSequenceNumber <= numberOfStoredMessages) { + notifyListenersStoredDataProgress(raceId, (double) maxSequenceNumber + / (double) numberOfStoredMessages); + } + } + if (message.isResponse()) { + // this is a response for an explicit request + rendevouz(message); + } else if (message.isEvent()) { + // a spontaneous event + logger.fine("notifying message " + message); + notifyListeners(message); + } + if (message.getType() == MessageType._STOPSERVER) { + logger.info("SailMasterConnector received " + MessageType._STOPSERVER.name()); + stop(); } - } - if (message.isResponse()) { - // this is a response for an explicit request - rendevouz(message); - } else if (message.isEvent()) { - // a spontaneous event - logger.fine("notifying message " + message); - notifyListeners(message); - } - if (message.getType() == MessageType._STOPSERVER) { - logger.info("SailMasterConnector received " + MessageType._STOPSERVER.name()); - stop(); } } } catch (SocketException se) { @@ -183,9 +188,9 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } } } catch (Exception e) { - logger.log(Level.SEVERE, "Exception in sail master connector "+SailMasterConnectorImpl.class.getName()+".run", e); + logger.log(Level.SEVERE, "Exception in sail master connector "+SailMasterConnectorImpl.class.getName()+".run for "+this, e); } - logger.info("Stopping Sail Master connector thread"); + logger.info("Stopping Sail Master connector thread for "+this); stopped = true; } @@ -196,14 +201,16 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement return result; } - private synchronized BlockingQueue getBlockingQueue(MessageType type) { - BlockingQueue blockingQueue; - blockingQueue = unprocessedMessagesByType.get(type); - if (blockingQueue == null) { - blockingQueue = new LinkedBlockingQueue(); - unprocessedMessagesByType.put(type, blockingQueue); + private BlockingQueue getBlockingQueue(MessageType type) { + synchronized (unprocessedMessagesByType) { + BlockingQueue blockingQueue; + blockingQueue = unprocessedMessagesByType.get(type); + if (blockingQueue == null) { + blockingQueue = new LinkedBlockingQueue(); + unprocessedMessagesByType.put(type, blockingQueue); + } + return blockingQueue; } - return blockingQueue; } private void rendevouz(SailMasterMessage message) { @@ -232,6 +239,9 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement case TMD: notifyListenersTMD(message); break; + case WND: + notifyListenersWND(message); + break; default: // ignore all other messages because there are no notification patterns for those } @@ -242,7 +252,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } private void notifyListenersStoredDataProgress(String raceID, double progress) { - for (SailMasterListener listener : getListeners(raceID)) { + for (SailMasterListener listener : getListeners()) { try { listener.storedDataProgress(raceID, progress); } catch (Exception e) { @@ -251,22 +261,38 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } } } + + private void notifyListenersWND(SailMasterMessage message) { + // example message: WND|W4702|1|320|5.4 + String raceID = message.getSections()[1]; + int zeroBasedMarkIndex = Integer.valueOf(message.getSections()[2]); + double windDirectionTrueDegrees = Double.valueOf(message.getSections()[3]); + double windSpeedInKnots = Double.valueOf(message.getSections()[4]); + for (SailMasterListener listener : getListeners()) { + try { + listener.receivedWindData(raceID, zeroBasedMarkIndex, windDirectionTrueDegrees, windSpeedInKnots); + } catch (Exception e) { + logger.info("Exception occurred trying to notify listener "+listener+" about "+message+": "+e.getMessage()); + logger.throwing(SailMasterConnectorImpl.class.getName(), "notifyListenersWND", e); + } + } + } private void notifyListenersTMD(SailMasterMessage message) { // example message: TMD|W4702|NZL 75|1|4;;00:49:43 String raceID = message.getSections()[1]; String boatID = message.getSections()[2]; int count = Integer.valueOf(message.getSections()[3]); - List> markIndicesRanksAndTimesSinceStartInMilliseconds = new ArrayList>(); + List> markIndicesRanksAndTimesSinceStartInMilliseconds = new ArrayList>(); for (int i = 0; i < count; i++) { String[] details = message.getSections()[4+i].split(";"); Integer markIndex = details.length <= 0 || details[0].trim().length() == 0 ? null : Integer.valueOf(details[0]); Integer rank = details.length <= 1 || details[1].trim().length() == 0 ? null : Integer.valueOf(details[1]); Long timeSinceStartInMilliseconds = details.length <= 2 || details[2].trim().length() == 0 ? null : parseHHMMSSToMilliseconds(details[2]); - markIndicesRanksAndTimesSinceStartInMilliseconds.add(new Triple(markIndex, rank, timeSinceStartInMilliseconds)); + markIndicesRanksAndTimesSinceStartInMilliseconds.add(new Util.Triple(markIndex, rank, timeSinceStartInMilliseconds)); } - for (SailMasterListener listener : getListeners(message.getRaceID())) { + for (SailMasterListener listener : getListeners()) { try { listener.receivedTimingData(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds); } catch (Exception e) { @@ -277,8 +303,8 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } private void notifyListenersCAM(SailMasterMessage message) throws ParseException { - List> clockAtMarkResults = parseClockAtMarkMessage(message); - for (SailMasterListener listener : getListeners(message.getRaceID())) { + List> clockAtMarkResults = parseClockAtMarkMessage(message); + for (SailMasterListener listener : getListeners()) { try { listener.receivedClockAtMark(message.getSections()[1], clockAtMarkResults); } catch (Exception e) { @@ -290,7 +316,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement private void notifyListenersSTL(SailMasterMessage message) { StartList startListMessage = parseStartListMessage(message); - for (SailMasterListener listener : getListeners(message.getRaceID())) { + for (SailMasterListener listener : getListeners()) { try { listener.receivedStartList(message.getSections()[1], startListMessage); } catch (Exception e) { @@ -302,7 +328,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement private void notifyListenersCCG(SailMasterMessage message) { Course course = parseCourseConfigurationMessage(message); - for (SailMasterListener listener : getListeners(message.getRaceID())) { + for (SailMasterListener listener : getListeners()) { try { listener.receivedCourseConfiguration(message.getSections()[1], course); } catch (Exception e) { @@ -314,7 +340,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement private void notifyListenersRAC(SailMasterMessage message) { Iterable races = parseAvailableRacesMessage(message); - for (SailMasterListener listener : listeners) { + for (SailMasterListener listener : getListeners()) { try { listener.receivedAvailableRaces(races); } catch (Exception e) { @@ -331,6 +357,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement String raceID = sections[1]; RaceStatus status = RaceStatus.values()[Integer.valueOf(sections[2])]; TimePoint timePoint = new MillisecondsTimePoint(parseTimeAndDateISO(sections[3], raceID)); + lastRPDMessageTimePoint = timePoint; String dateISO = sections[3].substring(0, sections[3].indexOf('T')); String startTimeEstimatedStartTimeISO = dateISO+"T"+sections[4]+lastTimeZoneSuffix; TimePoint startTimeEstimatedStartTime = sections[4].trim().length() == 0 ? null : new MillisecondsTimePoint( @@ -389,7 +416,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement rank, averageSpeedOverGround, velocityMadeGood, distanceToLeader, distanceToNextMark, boatIRM)); } } - Set allListeners = getListeners(message.getRaceID()); + Set allListeners = getListeners(); for (SailMasterListener listener : allListeners) { try { listener.receivedRacePositionData(raceID, status, timePoint, startTimeEstimatedStartTime, millisecondsSinceRaceStart, @@ -401,17 +428,28 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } } - private Set getListeners(String raceID) { - return Collections.unmodifiableSet(listeners); + private Set getListeners() { + synchronized (listeners) { + return Collections.unmodifiableSet(listeners); + } } @Override - public synchronized void stop() throws IOException { - logger.info("Stopping SailMasterConnector listening on port "+port+" with socket "+socket); + public String toString() { + return "SailMasterConnector listening on port "+port+" with socket "+socket+" for race "+raceName+" with ID "+raceId+" ("+raceDescription+")"; + } + + @Override + public void stop() throws IOException { + logger.info("Stopping "+this); stopped = true; - socket.close(); - socket = null; - notifyAll(); + if (socket != null) { + socket.close(); + socket = null; + } + synchronized (this) { + notifyAll(); + } } @Override @@ -421,13 +459,19 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement @Override public void addSailMasterListener(SailMasterListener listener) throws UnknownHostException, IOException, InterruptedException { - ensureSocketIsOpen(); - listeners.add(listener); + synchronized (listeners) { + listeners.add(listener); + } } @Override - public void removeSailMasterListener(SailMasterListener listener) { - listeners.remove(listener); + public void removeSailMasterListener(SailMasterListener listener) throws IOException { + synchronized (listeners) { + listeners.remove(listener); + if (listeners.isEmpty()) { + stop(); + } + } } public SailMasterMessage sendRequestAndGetResponse(MessageType messageType, String... args) throws UnknownHostException, IOException, InterruptedException { @@ -458,68 +502,75 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement return sailMasterMessage; } - private synchronized void ensureSocketIsOpen() throws InterruptedException { - while (!stopped && socket == null) { - try { - logger.info("Opening socket to " + host + ":" + port + " and sending " + MessageType.OPN.name() + " message..."); - socket = new Socket(host, port); - final OutputStream os = socket.getOutputStream(); - final InputStream is = socket.getInputStream(); - final SailMasterMessage opnRequest = createSailMasterMessage(MessageType.OPN, raceId); - sendMessage(opnRequest, os); - SailMasterMessage opnResponse = new SailMasterMessageImpl(receiveMessage(is)); - if (opnResponse.getType() != MessageType.OPN || !"OK".equals(opnResponse.getSections()[1])) { - logger.info("Recevied non-OK response " + opnResponse + " for our request " - + opnRequest + ". Closing socket and trying again in 1s..."); - closeAndNullSocketAndWaitABit(); - } else { - logger.info("Received " + opnResponse + " which seems OK. Continuing with " + MessageType.LSN.name() + " request..."); - numberOfStoredMessages = Long.valueOf(opnResponse.getSections()[2]); - List lsnArgs = new ArrayList<>(); - lsnArgs.add("ON"); // request live messages always; why not? - if (maxSequenceNumber != -1) { - logger.info("Requesting messages starting from sequence number "+(maxSequenceNumber+1)); - // already received a numbered message; ask only for newer messages with greater sequence number - lsnArgs.add(new Long(maxSequenceNumber+1).toString()); - } - final SailMasterMessage lsnRequest = createSailMasterMessage(MessageType.LSN, lsnArgs.toArray(new String[0])); - sendMessage(lsnRequest, os); - SailMasterMessageImpl lsnResponse = new SailMasterMessageImpl(receiveMessage(is)); - if (lsnResponse.getType() != MessageType.LSN || !"OK".equals(lsnResponse.getSections()[1])) { - logger.info("Received non-OK response " + lsnResponse + " for our request " + lsnRequest - + ". Closing socket and trying again in 1s..."); - closeAndNullSocketAndWaitABit(); + private final Object ensureSocketIsOpenSemaphor = new Object(); + private void ensureSocketIsOpen() throws InterruptedException { + synchronized (ensureSocketIsOpenSemaphor) { + while (!stopped && socket == null) { + try { + logger.info("Opening socket to " + host + ":" + port + " and sending " + MessageType.OPN.name() + + " message..."); + socket = new Socket(host, port); + final OutputStream os = socket.getOutputStream(); + final InputStream is = socket.getInputStream(); + final SailMasterMessage opnRequest = createSailMasterMessage(MessageType.OPN, raceId); + sendMessage(opnRequest, os); + SailMasterMessage opnResponse = new SailMasterMessageImpl(receiveMessage(is)); + if (opnResponse.getType() != MessageType.OPN || !"OK".equals(opnResponse.getSections()[1])) { + logger.info("Recevied non-OK response " + opnResponse + " in "+this+" for our request " + opnRequest + + ". Closing socket and stopping because we have no hope for recovery"); + stopped = true; + closeSocket(); } else { - logger.info("Received "+lsnResponse+" which seems to be OK. I think we're connected!"); - synchronized (this) { - logger.info("...successfully opened socket to " + host + ":" + port); - // TODO now request all messages starting after the last message received so far - connected = true; - notifyAll(); + logger.info("Received " + opnResponse + " in "+this+" which seems OK. Continuing with " + + MessageType.LSN.name() + " request..."); + numberOfStoredMessages = Long.valueOf(opnResponse.getSections()[2]); + List lsnArgs = new ArrayList<>(); + lsnArgs.add("ON"); // request live messages always; why not? + if (maxSequenceNumber != -1) { + logger.info("Requesting messages starting from sequence number " + (maxSequenceNumber + 1)+" in "+this); + // already received a numbered message; ask only for newer messages with greater sequence number + lsnArgs.add(new Long(maxSequenceNumber + 1).toString()); + } + final SailMasterMessage lsnRequest = createSailMasterMessage(MessageType.LSN, + lsnArgs.toArray(new String[0])); + sendMessage(lsnRequest, os); + SailMasterMessageImpl lsnResponse = new SailMasterMessageImpl(receiveMessage(is)); + if (lsnResponse.getType() != MessageType.LSN || !"OK".equals(lsnResponse.getSections()[1])) { + logger.info("Received non-OK response " + lsnResponse + " for our request " + lsnRequest + + " in "+this+". Closing socket and trying again in 1s..."); + closeAndNullSocketAndWaitABit(); + } else { + logger.info("Received " + lsnResponse + + " which seems to be OK. I think we're connected in "+this+"!"); } } + } catch (IOException e) { + logger.log(Level.INFO, "Exception trying to establish connection in "+this+". Trying again in 1s.", e); + closeAndNullSocketAndWaitABit(); } - } catch (IOException e) { - logger.log(Level.INFO, "Exception trying to establish SailMaster connection to " + host + ":" + port - + ". Trying again in 1s.", e); - closeAndNullSocketAndWaitABit(); } } } private void closeAndNullSocketAndWaitABit() throws InterruptedException { - try { - socket.close(); - } catch (IOException e) { - logger.log(Level.INFO, "Exception trying to close socket. Maybe already closed. Continuing", e); - } - socket = null; + closeSocket(); Thread.sleep(1000); } + private void closeSocket() { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + logger.log(Level.INFO, "Exception trying to close socket. Maybe already closed. Continuing", e); + } + socket = null; + } + } + @Override public Race getRace() { - return new RaceImpl(raceId, raceDescription, boatClass); + return new RaceImpl(raceId, raceName, raceDescription, boatClass); } private List parseAvailableRacesMessage(SailMasterMessage availableRacesMessage) { @@ -528,7 +579,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement List result = new ArrayList(); for (int i=0; i> getMarkPassingTimesInMillisecondsSinceRaceStart(String raceID, String boatID) + public Map> getMarkPassingTimesInMillisecondsSinceRaceStart(String raceID, String boatID) throws UnknownHostException, IOException, InterruptedException { SailMasterMessage response = sendRequestAndGetResponse(MessageType.TMD, raceID, boatID); String[] sections = response.getSections(); @@ -721,12 +777,12 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement assertRaceID(raceID, sections[1]); assertBoatID(boatID, sections[2]); int count = Integer.valueOf(sections[3]); - Map> result = new HashMap>(); + Map> result = new HashMap>(); for (int i=0; i( + result.put(Integer.valueOf(markTimeDetail[0]), new Util.Pair( markTimeDetail.length <= 1 || markTimeDetail[1].trim().length() == 0 ? null : Integer.valueOf(markTimeDetail[1]), millisecondsSinceStart)); } @@ -734,25 +790,25 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement } @Override - public List> getClockAtMark(String raceID) throws ParseException, UnknownHostException, IOException, InterruptedException { + public List> getClockAtMark(String raceID) throws ParseException, UnknownHostException, IOException, InterruptedException { SailMasterMessage response = sendRequestAndGetResponse(MessageType.CAM, raceID); String[] sections = response.getSections(); assertResponseType(MessageType.CAM, response); assertRaceID(raceID, sections[1]); - List> result = parseClockAtMarkMessage(response); + List> result = parseClockAtMarkMessage(response); return result; } - private List> parseClockAtMarkMessage(SailMasterMessage clockAtMarkMessage) throws ParseException { + private List> parseClockAtMarkMessage(SailMasterMessage clockAtMarkMessage) throws ParseException { assertMessageType(MessageType.CAM, clockAtMarkMessage); - List> result = new ArrayList>(); + List> result = new ArrayList>(); int count = Integer.valueOf(clockAtMarkMessage.getSections()[2]); for (int i=0; i( + result.add(new Util.Triple( markIndex, timePoint, clockAtMarkDetail.length <= 2 ? null : clockAtMarkDetail[2])); } return result; diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterLiveSimulatorConnectorImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterLiveSimulatorConnectorImpl.java index ec30ae326be..827570c8cd5 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterLiveSimulatorConnectorImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterLiveSimulatorConnectorImpl.java @@ -13,9 +13,9 @@ public class SailMasterLiveSimulatorConnectorImpl extends SailMasterConnectorImp private long messageDeliveryIntervalInMs = Long.valueOf(System.getProperty("simulateLiveMode.delayInMillis", "250")); - public SailMasterLiveSimulatorConnectorImpl(String host, int port, String raceId, String raceDescription, BoatClass boatClass) + public SailMasterLiveSimulatorConnectorImpl(String host, int port, String raceId, String raceName, String raceDescription, BoatClass boatClass) throws InterruptedException, ParseException { - super(host, port, raceId, raceDescription, boatClass); + super(host, port, raceId, raceName, raceDescription, boatClass); bufferedMessageList = Collections.synchronizedList(new ArrayList()); Thread messageDeliveryThread = new Thread("SailMasterLiveSimulatorConnector") { public void run() { diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterMessageImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterMessageImpl.java index f51285157c7..b98dab16877 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterMessageImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterMessageImpl.java @@ -68,7 +68,11 @@ public class SailMasterMessageImpl implements SailMasterMessage { } else { typeAsString = typeAsStringWithOptionalSuffix.substring(0, typeAsStringWithOptionalSuffix.length()-1); } - return MessageType.valueOf(typeAsString); + try { + return MessageType.valueOf(typeAsString); + } catch (IllegalArgumentException e) { + return null; + } } @Override diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java index 18807630ba3..544a7b682b7 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java @@ -1,26 +1,46 @@ package com.sap.sailing.domain.swisstimingadapter.impl; import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; import java.net.UnknownHostException; import java.text.ParseException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import javax.xml.bind.JAXBException; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.swisstimingadapter.Competitor; +import com.sap.sailing.domain.swisstimingadapter.CrewMember; +import com.sap.sailing.domain.swisstimingadapter.StartList; import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; import com.sap.sailing.domain.tracking.RacesHandle; -import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackerManager; +import com.sap.sailing.xrr.resultimport.ParserFactory; +import com.sap.sailing.xrr.resultimport.schema.Boat; +import com.sap.sailing.xrr.resultimport.schema.Crew; +import com.sap.sailing.xrr.resultimport.schema.Division; +import com.sap.sailing.xrr.resultimport.schema.Event; +import com.sap.sailing.xrr.resultimport.schema.Person; +import com.sap.sailing.xrr.resultimport.schema.RaceResult; +import com.sap.sailing.xrr.resultimport.schema.RegattaResults; +import com.sap.sailing.xrr.resultimport.schema.Team; public class SwissTimingAdapterImpl implements SwissTimingAdapter { private final SwissTimingFactory swissTimingFactory; private final com.sap.sailing.domain.swisstimingadapter.DomainFactory swissTimingDomainFactory; + private final long DEFAULT_SWISSTIMING_LIVE_DELAY_IN_MILLISECONDS = 6000; + public SwissTimingAdapterImpl(DomainFactory baseDomainFactory) { swissTimingFactory = SwissTimingFactory.INSTANCE; swissTimingDomainFactory = new DomainFactoryImpl(baseDomainFactory); @@ -46,13 +66,84 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter { } @Override - public RacesHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceDescription, - BoatClass boatClass, String hostname, int port, RaceLogStore logStore, long timeoutInMilliseconds) + public RacesHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription, + BoatClass boatClass, String hostname, int port, StartList startList, RaceLogStore logStore, long timeoutInMilliseconds) throws Exception { return trackerManager.addRace(regattaToAddTo, swissTimingDomainFactory.createTrackingConnectivityParameters(hostname, port, - raceID, raceDescription, boatClass, TrackedRace.DEFAULT_LIVE_DELAY_IN_MILLISECONDS, swissTimingFactory, + raceID, raceName, raceDescription, boatClass, startList, DEFAULT_SWISSTIMING_LIVE_DELAY_IN_MILLISECONDS, swissTimingFactory, swissTimingDomainFactory, logStore), timeoutInMilliseconds); } + @Override + public StartList readStartListForRace(String raceId, RegattaResults regattaResults) { + StartList result = null; + List competitors = new ArrayList(); + + Map persons = new HashMap(); + Map boats = new HashMap(); + Map teams = new HashMap(); + // collect boats, persons and teams + for (Object o : regattaResults.getPersonOrBoatOrTeam()) { + if(o instanceof Person) { + Person p = (Person) o; + persons.put(p.getPersonID(), p); + } else if (o instanceof Boat) { + Boat b = (Boat) o; + boats.put(b.getBoatID(), b); + } else if (o instanceof Team) { + Team t = (Team) o; + teams.put(t.getTeamID(), t); + } + } + + for (Object o : regattaResults.getPersonOrBoatOrTeam()) { + if (o instanceof Event) { + Event event = (Event) o; + for (Object eventO : event.getRaceOrDivisionOrRegattaSeriesResult()) { + if (eventO instanceof Division) { + Division division = (Division) eventO; + for(Object seriesResultOrRaceResultOrTRResult: division.getSeriesResultOrRaceResultOrTRResult()) { + if(seriesResultOrRaceResultOrTRResult instanceof RaceResult) { + RaceResult raceResult = (RaceResult) seriesResultOrRaceResultOrTRResult; + if(raceResult.getRaceID().equals(raceId)) { + Team team = teams.get(raceResult.getTeamID()); + if(team != null) { + Boat boat = boats.get(team.getBoatID()); + if(boat != null) { + List crew = new ArrayList(); + for(Crew crewMember: team.getCrew()) { + Person person = persons.get(crewMember.getPersonID()); + crew.add(new CrewMemberImpl(person.getGivenName() + " " + person.getFamilyName(), + person.getNOC().name(), crewMember.getPosition().name())); + } + Crew firstCrewMember = team.getCrew().get(0); + Person person = persons.get(firstCrewMember.getPersonID()); + String nationality = person.getNOC().name(); + CompetitorWithID competitor = new CompetitorWithID(team.getTeamID(), boat.getSailNumber(), nationality, team.getTeamName(), crew); + competitors.add(competitor); + } + } + } + } + } + } + } + } + } + if(!competitors.isEmpty()) { + result = new StartListImpl(raceId, competitors); + } + + return result; + } + + @Override + public RegattaResults readRegattaEntryListFromXrrUrl(String xrrEntryListUrl) throws IOException, JAXBException { + // try to read the entry list from manage2sail + URL regattaEntryListUrl = new URL(xrrEntryListUrl); + URLConnection regattaEntryListConn = regattaEntryListUrl.openConnection(); + RegattaResults regattaResults = ParserFactory.INSTANCE.createParser((InputStream) regattaEntryListConn.getContent(), xrrEntryListUrl).parse(); + return regattaResults; + } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java index 0af5164a34b..4326bb77b57 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java @@ -1,124 +1,126 @@ -package com.sap.sailing.domain.swisstimingadapter.impl; - -import java.io.IOException; -import java.net.UnknownHostException; -import java.text.ParseException; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Logger; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.common.impl.Util.Triple; -import com.sap.sailing.domain.racelog.RaceLogStore; -import com.sap.sailing.domain.swisstimingadapter.DomainFactory; -import com.sap.sailing.domain.swisstimingadapter.Race; -import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector; -import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage; -import com.sap.sailing.domain.swisstimingadapter.SailMasterTransceiver; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingConfiguration; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingMessageParser; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingRaceTracker; -import com.sap.sailing.domain.tracking.RaceTracker; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.domain.tracking.WindStore; - -public class SwissTimingFactoryImpl implements SwissTimingFactory { - private static final Logger logger = Logger.getLogger(SwissTimingFactoryImpl.class.getName()); - - private final Map, SailMasterConnector> connectors; - - public SwissTimingFactoryImpl() { - connectors = new HashMap<>(); - } - - @Override - public SwissTimingMessageParser createMessageParser() { - return new SwissTimingMessageParserImpl(); - } - - @Override - public SailMasterConnector getOrCreateSailMasterConnector(String host, int port, String raceId, - String raceDescription, BoatClass boatClass) throws InterruptedException, ParseException { - if (Boolean.valueOf(System.getProperty("simulateLiveMode", "false"))) { - return getOrCreateSailMasterLiveSimulatorConnector(host, port, raceId, raceDescription, boatClass); - } else { - Triple key = new Triple(host, port, raceId); - SailMasterConnector result = connectors.get(key); - if (result == null || result.isStopped()) { - if (result == null) { - logger.info("Creating a new connector for "+key+" because none found"); - } else { - logger.info("Creating a new connector for "+key+" because the old one was stopped"); - } - result = new SailMasterConnectorImpl(host, port, raceId, raceDescription, boatClass); - connectors.put(key, result); - // TODO how do connectors get stopped, terminated and removed from the connectors map again? - } else { - logger.info("Re-using connector for "+key+" because it wasn't stopped"); - } - return result; - } - } - - @Override - public SailMasterConnector getOrCreateSailMasterLiveSimulatorConnector(String host, int port, String raceId, - String raceDescription, BoatClass boatClass) throws InterruptedException, ParseException { - Triple key = new Triple<>(host, port, raceId); - SailMasterConnector result = connectors.get(key); - if (result == null) { - result = new SailMasterLiveSimulatorConnectorImpl(host, port, raceId, raceDescription, boatClass); - connectors.put(key, result); - // TODO how do connectors get stopped, terminated and removed from the connectors map again? - } else if (result.isStopped()) { - result = new SailMasterLiveSimulatorConnectorImpl(host, port, raceId, raceDescription, boatClass); - connectors.put(key, result); - } - return result; - } - - @Override - public SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, int port) { - return new SwissTimingConfigurationImpl(name, jsonURL, hostname, port); - } - - @Override - public SwissTimingRaceTracker createRaceTracker(String raceID, String raceDescription, BoatClass boatClass, String hostname, int port, - long delayToLiveInMillis, RaceLogStore raceLogStore, WindStore windStore, DomainFactory domainFactory, - TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, - UnknownHostException, IOException, ParseException { - return new SwissTimingRaceTrackerImpl(raceID, raceDescription, boatClass, hostname, port, raceLogStore, windStore, domainFactory, this, - trackedRegattaRegistry, delayToLiveInMillis); - } - - @Override - public RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceDescription, BoatClass boatClass, String hostname, - int port, long delayToLiveInMillis, WindStore windStore, DomainFactory domainFactory, - TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException, - InterruptedException, IOException, ParseException { - return new SwissTimingRaceTrackerImpl(regatta, raceID, raceDescription, boatClass, hostname, port, windStore, domainFactory, - this, trackedRegattaRegistry, delayToLiveInMillis); - } - - @Override - public SailMasterTransceiver createSailMasterTransceiver() { - return new SailMasterTransceiverImpl(); - } - - @Override - public SailMasterMessage createMessage(String message) { - return new SailMasterMessageImpl(message); - } - - @Override - public Race createRace(String raceId, String description, BoatClass boatClass) { - return new RaceImpl(raceId, description, boatClass); - } - - @Override - public SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration(String jsonUrl) { - return new SwissTimingArchiveConfigurationImpl(jsonUrl); - } -} +package com.sap.sailing.domain.swisstimingadapter.impl; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.swisstimingadapter.DomainFactory; +import com.sap.sailing.domain.swisstimingadapter.Race; +import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector; +import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage; +import com.sap.sailing.domain.swisstimingadapter.SailMasterTransceiver; +import com.sap.sailing.domain.swisstimingadapter.StartList; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingConfiguration; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingMessageParser; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingRaceTracker; +import com.sap.sailing.domain.tracking.RaceTracker; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sse.common.Util; + +public class SwissTimingFactoryImpl implements SwissTimingFactory { + private static final Logger logger = Logger.getLogger(SwissTimingFactoryImpl.class.getName()); + + private final Map, SailMasterConnector> connectors; + + public SwissTimingFactoryImpl() { + connectors = new HashMap<>(); + } + + @Override + public SwissTimingMessageParser createMessageParser() { + return new SwissTimingMessageParserImpl(); + } + + @Override + public SailMasterConnector getOrCreateSailMasterConnector(String host, int port, String raceId, String raceName, + String raceDescription, BoatClass boatClass) throws InterruptedException, ParseException { + if (Boolean.valueOf(System.getProperty("simulateLiveMode", "false"))) { + return getOrCreateSailMasterLiveSimulatorConnector(host, port, raceId, raceName, raceDescription, boatClass); + } else { + Util.Triple key = new Util.Triple(host, port, raceId); + SailMasterConnector result = connectors.get(key); + if (result == null || result.isStopped()) { + if (result == null) { + logger.info("Creating a new connector for "+key+" because none found"); + } else { + logger.info("Creating a new connector for "+key+" because the old one was stopped"); + } + result = new SailMasterConnectorImpl(host, port, raceId, raceName, raceDescription, boatClass); + connectors.put(key, result); + // TODO how do connectors get stopped, terminated and removed from the connectors map again? + } else { + logger.info("Re-using connector for "+key+" because it wasn't stopped"); + } + return result; + } + } + + @Override + public SailMasterConnector getOrCreateSailMasterLiveSimulatorConnector(String host, int port, String raceId, String raceName, + String raceDescription, BoatClass boatClass) throws InterruptedException, ParseException { + Util.Triple key = new Util.Triple<>(host, port, raceId); + SailMasterConnector result = connectors.get(key); + if (result == null) { + result = new SailMasterLiveSimulatorConnectorImpl(host, port, raceId, raceName, raceDescription, boatClass); + connectors.put(key, result); + // TODO how do connectors get stopped, terminated and removed from the connectors map again? + } else if (result.isStopped()) { + result = new SailMasterLiveSimulatorConnectorImpl(host, port, raceId, raceName, raceDescription, boatClass); + connectors.put(key, result); + } + return result; + } + + @Override + public SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, int port) { + return new SwissTimingConfigurationImpl(name, jsonURL, hostname, port); + } + + @Override + public SwissTimingRaceTracker createRaceTracker(String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, + StartList startList, long delayToLiveInMillis, RaceLogStore raceLogStore, WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, + TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, + UnknownHostException, IOException, ParseException { + return new SwissTimingRaceTrackerImpl(raceID, raceName, raceDescription, boatClass, hostname, port, startList, raceLogStore, windStore, gpsFixStore, domainFactory, this, + trackedRegattaRegistry, delayToLiveInMillis); + } + + @Override + public RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, + int port, StartList startList, long delayToLiveInMillis, WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, + TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException, + InterruptedException, IOException, ParseException { + return new SwissTimingRaceTrackerImpl(regatta, raceID, raceName, raceDescription, boatClass, hostname, port, startList, windStore, gpsFixStore, domainFactory, + this, trackedRegattaRegistry, delayToLiveInMillis); + } + + @Override + public SailMasterTransceiver createSailMasterTransceiver() { + return new SailMasterTransceiverImpl(); + } + + @Override + public SailMasterMessage createMessage(String message) { + return new SailMasterMessageImpl(message); + } + + @Override + public Race createRace(String raceId, String raceName, String description, BoatClass boatClass) { + return new RaceImpl(raceId, raceName, description, boatClass); + } + + @Override + public SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration(String jsonUrl) { + return new SwissTimingArchiveConfigurationImpl(jsonUrl); + } +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java index f81771cfa4d..99b235e41e8 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java @@ -6,7 +6,9 @@ import java.net.UnknownHostException; import java.text.ParseException; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.NavigableSet; import java.util.Set; import java.util.TreeMap; @@ -15,23 +17,30 @@ import java.util.logging.Logger; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CompetitorStore; 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.base.Sideline; 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.TimePoint; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; +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.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.swisstimingadapter.Course; import com.sap.sailing.domain.swisstimingadapter.DomainFactory; import com.sap.sailing.domain.swisstimingadapter.Fix; import com.sap.sailing.domain.swisstimingadapter.Race; import com.sap.sailing.domain.swisstimingadapter.RaceStatus; +import com.sap.sailing.domain.swisstimingadapter.RaceType; import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector; import com.sap.sailing.domain.swisstimingadapter.SailMasterListener; import com.sap.sailing.domain.swisstimingadapter.StartList; @@ -49,9 +58,12 @@ import com.sap.sailing.domain.tracking.RacesHandle; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; +import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sse.common.Util; import difflib.PatchFailedException; @@ -60,12 +72,15 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen private final SailMasterConnector connector; private final String raceID; + private final String raceName; private final String raceDescription; private final BoatClass boatClass; private final DomainFactory domainFactory; - private final Triple id; + private final com.sap.sse.common.Util.Triple id; private final Regatta regatta; private final WindStore windStore; + private final GPSFixStore gpsFixStore; + private final boolean startListFromManage2Sail; /** * Starts out as null and is set when the race definition has been created. When this happens, this object is @@ -75,6 +90,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen private Course course; private StartList startList; + private Map competitorsByBoatId; private DynamicTrackedRace trackedRace; private final DynamicTrackedRegatta trackedRegatta; @@ -87,33 +103,38 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen * tracker by calling {@link #receivedTimingData(String, String, List)}. */ private final TMDMessageQueue tmdMessageQueue; - - protected SwissTimingRaceTrackerImpl(String raceID, String raceDescription, BoatClass boatClass, String hostname, int port, RaceLogStore raceLogStore, - WindStore windStore, DomainFactory domainFactory, SwissTimingFactory factory, + + protected SwissTimingRaceTrackerImpl(String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, StartList startList, RaceLogStore raceLogStore, + WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, SwissTimingFactory factory, TrackedRegattaRegistry trackedRegattaRegistry, long delayToLiveInMillis) throws InterruptedException, - UnknownHostException, IOException, ParseException { - this(domainFactory.getOrCreateDefaultRegatta(raceLogStore, raceID, boatClass, trackedRegattaRegistry), raceID, - raceDescription, boatClass, hostname, port, windStore, domainFactory, factory, trackedRegattaRegistry, + UnknownHostException, IOException, ParseException { + this(domainFactory.getOrCreateDefaultRegatta(raceLogStore, raceID, boatClass, trackedRegattaRegistry), raceID, raceName, + raceDescription, boatClass, hostname, port, startList, windStore, gpsFixStore, domainFactory, factory, trackedRegattaRegistry, delayToLiveInMillis); } - - protected SwissTimingRaceTrackerImpl(Regatta regatta, String raceID, String raceDescription, BoatClass boatClass, String hostname, int port, - WindStore windStore, DomainFactory domainFactory, SwissTimingFactory factory, + + protected SwissTimingRaceTrackerImpl(Regatta regatta, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, StartList startList, + WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, SwissTimingFactory factory, TrackedRegattaRegistry trackedRegattaRegistry, long delayToLiveInMillis) throws InterruptedException, UnknownHostException, IOException, ParseException { super(); this.tmdMessageQueue = new TMDMessageQueue(this); this.regatta = regatta; - this.connector = factory.getOrCreateSailMasterConnector(hostname, port, raceID, raceDescription, boatClass); + this.connector = factory.getOrCreateSailMasterConnector(hostname, port, raceID, raceName, raceDescription, boatClass); this.domainFactory = domainFactory; this.raceID = raceID; + this.raceName = raceName; + this.startList = startList; + this.startListFromManage2Sail = startList != null; this.raceDescription = raceDescription; this.boatClass = boatClass; this.windStore = windStore; + this.gpsFixStore = gpsFixStore; this.id = createID(raceID, hostname, port); connector.addSailMasterListener(this); trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(regatta); this.delayToLiveInMillis = delayToLiveInMillis; + this.competitorsByBoatId = new HashMap(); } @Override @@ -121,8 +142,8 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen return trackedRegatta; } - static Triple createID(String raceID, String hostname, int port) { - return new Triple(raceID, hostname, port); + static com.sap.sse.common.Util.Triple createID(String raceID, String hostname, int port) { + return new com.sap.sse.common.Util.Triple(raceID, hostname, port); } @Override @@ -166,20 +187,26 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen public Set getRaces(long timeoutInMilliseconds) { long start = System.currentTimeMillis(); synchronized (this) { - RaceDefinition result = race; + RaceDefinition preResult = race; boolean interrupted = false; - while ((System.currentTimeMillis()-start < timeoutInMilliseconds) && !interrupted && result == null) { + while ((System.currentTimeMillis()-start < timeoutInMilliseconds) && !interrupted && preResult == null) { try { long timeToWait = timeoutInMilliseconds - (System.currentTimeMillis() - start); if (timeToWait > 0) { this.wait(timeToWait); } - result = race; + preResult = race; } catch (InterruptedException e) { interrupted = true; } } - return result == null ? null : Collections.singleton(result); + final Set result; + if (preResult == null) { + result = Collections.emptySet(); + } else { + result = Collections.singleton(preResult); + } + return result; } } @@ -200,13 +227,18 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen return windStore; } + @Override + public GPSFixStore getGPSFixStore() { + return gpsFixStore; + } + @Override public Regatta getRegatta() { return regatta; } @Override - public Triple getID() { + public com.sap.sse.common.Util.Triple getID() { return id; } @@ -234,10 +266,18 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen trackedRace.recordFix(mark, gpsFix); break; case COMPETITOR: - Competitor competitor = domainFactory.getCompetitorByBoatIDAndRaceIDOrBoatClass( - fix.getBoatID(), raceID, boatClass); - DynamicGPSFixTrack competitorTrack = trackedRace.getTrack(competitor); - competitorTrack.addGPSFix(gpsFix); + String boatID = fix.getBoatID(); + Competitor competitor = getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, raceID, boatClass); + if(competitor == null) { + // TODO: read startlist again from Manage2Sail + // use competitorStore.isCompetitorToUpdateDuringGetOrCreate(result) + } + if(competitor != null) { + DynamicGPSFixTrack competitorTrack = trackedRace.getTrack(competitor); + competitorTrack.addGPSFix(gpsFix); + } else { + logger.info("Unknown competitor " + boatID + " found for race with id " + raceID); + } break; default: logger.info("Unknown tracker type " + fix.getTrackerType()); @@ -255,51 +295,90 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen } + private Competitor getCompetitorByBoatIDAndRaceType(String boatID, RaceType raceType) { + return domainFactory.getBaseDomainFactory().getExistingCompetitorById(domainFactory.getCompetitorID(boatID, raceType)); + } + + private Competitor getCompetitorByBoatIDAndBoatClass(String boatID, BoatClass boatClass) { + return domainFactory.getBaseDomainFactory().getExistingCompetitorById(domainFactory.getCompetitorID(boatID, boatClass)); + } + + @Override + public Competitor getCompetitorByBoatIDAndRaceIDOrBoatClass(String boatID, String raceID, BoatClass boatClass) { + Competitor result = null; + // first look into the temp cache + result = competitorsByBoatId.get(boatID); + if(result == null) { + if(boatClass != null) { + result = getCompetitorByBoatIDAndBoatClass(boatID, boatClass); + } else { + RaceType raceType = domainFactory.getRaceTypeFromRaceID(raceID); + if (raceType != null) { + result = getCompetitorByBoatIDAndRaceType(boatID, raceType); + } + } + } + return result; + } + @Override public void receivedTimingData(String raceID, String boatID, - List> markIndicesRanksAndTimesSinceStartInMilliseconds) { + List> markIndicesRanksAndTimesSinceStartInMilliseconds) { assert this.raceID.equals(raceID); if (isTrackedRaceStillReachable()) { - Competitor competitor = domainFactory.getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, - raceID, boatClass); - // the list of mark indices and time stamps is partial and usually only shows the last mark passing; - // we need to use this to *update* the competitor's mark passings list, not *replace* it - TreeMap markPassingsByMarkIndex = new TreeMap(); - // now fill with the already existing mark passings for the competitor identified by boatID... - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - trackedRace.lockForRead(markPassings); - try { - for (MarkPassing markPassing : markPassings) { - markPassingsByMarkIndex.put( - trackedRace.getRace().getCourse().getIndexOfWaypoint(markPassing.getWaypoint()), - markPassing); + Competitor competitor = getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, raceID, boatClass); + if (competitor == null) { + logger.info("Received timing data for boat ID " + boatID + " in race " + raceID + + " but couldn't find a competitor with that boat ID in this race. Ignoring."); + } else { + // the list of mark indices and time stamps is partial and usually only shows the last mark passing; + // we need to use this to *update* the competitor's mark passings list, not *replace* it + TreeMap markPassingsByMarkIndex = new TreeMap(); + // now fill with the already existing mark passings for the competitor identified by boatID... + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + trackedRace.lockForRead(markPassings); + try { + for (MarkPassing markPassing : markPassings) { + markPassingsByMarkIndex.put( + trackedRace.getRace().getCourse().getIndexOfWaypoint(markPassing.getWaypoint()), + markPassing); + } + } finally { + trackedRace.unlockAfterRead(markPassings); } - } finally { - trackedRace.unlockAfterRead(markPassings); - } - // ...and then overwrite those for which we received "new evidence" - for (Triple markIndexRankAndTimeSinceStartInMilliseconds : markIndicesRanksAndTimesSinceStartInMilliseconds) { - Waypoint waypoint = Util.get(trackedRace.getRace().getCourse().getWaypoints(), - markIndexRankAndTimeSinceStartInMilliseconds.getA()); - // update mark passing only if we have a start time; guessed start times don't make sense and - // for the start line would lead subsequent calls to getStartOfRace() return that guessed start time - // which then cannot be identified as "guessed" anymore... - if (trackedRace.getStartOfRace() != null) { - final TimePoint startTime = trackedRace.getStartOfRace(); - MillisecondsTimePoint timePoint = new MillisecondsTimePoint( - startTime.asMillis() + markIndexRankAndTimeSinceStartInMilliseconds.getC()); - MarkPassing markPassing = domainFactory.createMarkPassing(timePoint, waypoint, - domainFactory.getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, raceID, boatClass)); - markPassingsByMarkIndex.put(markIndexRankAndTimeSinceStartInMilliseconds.getA(), markPassing); - } else { - // - logger.warning("Received mark passing with time relative to start of race "+trackedRace.getRace().getName()+ - " before having received a race start time." - + " Queueing message for re-application when a start time has been received."); - tmdMessageQueue.enqueue(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds); + // ...and then overwrite those for which we received "new evidence" + for (com.sap.sse.common.Util.Triple markIndexRankAndTimeSinceStartInMilliseconds : markIndicesRanksAndTimesSinceStartInMilliseconds) { + Waypoint waypoint = Util.get(trackedRace.getRace().getCourse().getWaypoints(), + markIndexRankAndTimeSinceStartInMilliseconds.getA()); + // If the rank and time information is empty, we interpret this by clearing the mark rounding if any + // (see + // also bug 1911): + if (markIndexRankAndTimeSinceStartInMilliseconds.getC() == null) { + markPassingsByMarkIndex.remove(markIndexRankAndTimeSinceStartInMilliseconds.getA()); + } else { + // update mark passing only if we have a start time; guessed start times don't make sense and + // for the start line would lead subsequent calls to getStartOfRace() return that guessed start + // time + // which then cannot be identified as "guessed" anymore... + if (trackedRace.getStartOfRace() != null) { + final TimePoint startTime = trackedRace.getStartOfRace(); + MillisecondsTimePoint timePoint = new MillisecondsTimePoint(startTime.asMillis() + + markIndexRankAndTimeSinceStartInMilliseconds.getC()); + MarkPassing markPassing = domainFactory.createMarkPassing(timePoint, waypoint, + getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, raceID, boatClass)); + markPassingsByMarkIndex.put(markIndexRankAndTimeSinceStartInMilliseconds.getA(), + markPassing); + } else { + // + logger.warning("Received mark passing with time relative to start of race " + + trackedRace.getRace().getName() + " before having received a race start time." + + " Queueing message for re-application when a start time has been received."); + tmdMessageQueue.enqueue(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds); + } + } } + trackedRace.updateMarkPassings(competitor, markPassingsByMarkIndex.values()); } - trackedRace.updateMarkPassings(competitor, markPassingsByMarkIndex.values()); } else { if (!loggedIgnore) { logger.info("Ignoring timing data " + markIndicesRanksAndTimesSinceStartInMilliseconds + " for SwissTiming race " + raceID @@ -329,14 +408,15 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen @Override public void receivedClockAtMark(String raceID, - List> markIndicesTimePointsAndBoatIDs) { + List> markIndicesTimePointsAndBoatIDs) { // Ignored because it's covered by TMD. Mail from Kai Hahndorf of 2011-11-15T12:42:00Z: // "Die TMD werden immer gesendet. Das CAM Protokoll ist nur fuer unsere TV-Grafik wichtig, da damit die Rueckstandsuhr gestartet wird." } @Override public void receivedStartList(String raceID, StartList startList) { - if (this.raceID.equals(raceID)) { + // ignore STL messages if the startlist has been already provided by Manage2Sail + if (!startListFromManage2Sail && this.raceID.equals(raceID)) { StartList oldStartList = this.startList; this.startList = startList; if (oldStartList == null && course != null) { @@ -345,14 +425,40 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen } } + @Override + public void receivedWindData(String raceID, int zeroBasedMarkIndex, double windDirectionTrueDegrees, double windSpeedInKnots) { + if (this.raceID.equals(raceID)) { + DynamicTrackedRace trackedRace = getTrackedRace(); + if (trackedRace == null) { + logger.warning("Received wind data at mark " + zeroBasedMarkIndex + ": " + windDirectionTrueDegrees + "deg at " + + windSpeedInKnots + "kts but didn't find tracked race; ignoring"); + } else { + Waypoint wp = Util.get(trackedRace.getRace().getCourse().getWaypoints(), zeroBasedMarkIndex); + final TimePoint timePoint = connector.getLastRPDMessageTimePoint(); + Position waypointPosition = trackedRace.getApproximatePosition(wp, timePoint); + Wind wind = new WindImpl(waypointPosition, timePoint, new KnotSpeedWithBearingImpl(windSpeedInKnots, new DegreeBearingImpl(windDirectionTrueDegrees))); + WindSource windSource = new WindSourceWithAdditionalID(WindSourceType.RACECOMMITTEE, "@"+zeroBasedMarkIndex); + trackedRace.recordWind(wind, windSource); + } + } + } + private void createRaceDefinition(Course course) { assert this.raceID.equals(raceID); assert startList != null; assert course != null; // now we can create the RaceDefinition and most other things - Race swissTimingRace = new RaceImpl(raceID, raceDescription, boatClass); + Race swissTimingRace = new RaceImpl(raceID, raceName, raceDescription, boatClass); race = domainFactory.createRaceDefinition(regatta, swissTimingRace, startList, course); - trackedRace = getTrackedRegatta().createTrackedRace(race, Collections. emptyList(), windStore, delayToLiveInMillis, + // temp + CompetitorStore competitorStore = domainFactory.getBaseDomainFactory().getCompetitorStore(); + for (com.sap.sailing.domain.swisstimingadapter.Competitor c : startList.getCompetitors()) { + Competitor existingCompetitor = competitorStore.getExistingCompetitorByIdAsString(c.getID()); + if (existingCompetitor != null) { + competitorsByBoatId.put(c.getBoatID(), existingCompetitor); + } + } + trackedRace = getTrackedRegatta().createTrackedRace(race, Collections. emptyList(), windStore, gpsFixStore, delayToLiveInMillis, WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, /* time over which to average speed */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(), new DynamicRaceDefinitionSet() { @@ -404,6 +510,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen } } + @Override public void receivedAvailableRaces(Iterable races) { // don't care diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java index 3242641c9c1..ae5104d67c1 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java @@ -1,62 +1,68 @@ -package com.sap.sailing.domain.swisstimingadapter.impl; - - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.racelog.RaceLogStore; -import com.sap.sailing.domain.swisstimingadapter.DomainFactory; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; -import com.sap.sailing.domain.tracking.RaceTracker; -import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.domain.tracking.WindStore; - -public class SwissTimingTrackingConnectivityParameters implements RaceTrackingConnectivityParameters { - private final String hostname; - private final int port; - private final String raceID; - private final String raceDescription; - private final BoatClass boatClass; - private final SwissTimingFactory swissTimingFactory; - private final DomainFactory domainFactory; - private final RaceLogStore raceLogStore; - private final long delayToLiveInMillis; - - public SwissTimingTrackingConnectivityParameters(String hostname, int port, String raceID, String raceDescription, BoatClass boatClass, - long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, - RaceLogStore raceLogStore) { - super(); - this.hostname = hostname; - this.port = port; - this.raceID = raceID; - this.raceDescription = raceDescription; - this.boatClass = boatClass; - this.delayToLiveInMillis = delayToLiveInMillis; - this.swissTimingFactory = swissTimingFactory; - this.domainFactory = domainFactory; - this.raceLogStore = raceLogStore; - } - - @Override - public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) throws Exception { - return swissTimingFactory.createRaceTracker(raceID, raceDescription, boatClass, hostname, port, delayToLiveInMillis, raceLogStore, windStore, domainFactory, - trackedRegattaRegistry); - } - - @Override - public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) - throws Exception { - return swissTimingFactory.createRaceTracker(regatta, raceID, raceDescription, boatClass, hostname, port, delayToLiveInMillis, windStore, domainFactory, - trackedRegattaRegistry); - } - - @Override - public Object getTrackerID() { - return SwissTimingRaceTrackerImpl.createID(raceID, hostname, port); - } - - @Override - public long getDelayToLiveInMillis() { - return delayToLiveInMillis; - } -} +package com.sap.sailing.domain.swisstimingadapter.impl; + + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.swisstimingadapter.DomainFactory; +import com.sap.sailing.domain.swisstimingadapter.StartList; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; +import com.sap.sailing.domain.tracking.RaceTracker; +import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.WindStore; + +public class SwissTimingTrackingConnectivityParameters implements RaceTrackingConnectivityParameters { + private final String hostname; + private final int port; + private final String raceID; + private final String raceName; + private final String raceDescription; + private final BoatClass boatClass; + private final SwissTimingFactory swissTimingFactory; + private final DomainFactory domainFactory; + private final RaceLogStore raceLogStore; + private final long delayToLiveInMillis; + private final StartList startList; + + public SwissTimingTrackingConnectivityParameters(String hostname, int port, String raceID, String raceName, String raceDescription, BoatClass boatClass, + StartList startList, long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, + RaceLogStore raceLogStore) { + super(); + this.hostname = hostname; + this.port = port; + this.raceID = raceID; + this.raceName = raceName; + this.raceDescription = raceDescription; + this.boatClass = boatClass; + this.startList = startList; + this.delayToLiveInMillis = delayToLiveInMillis; + this.swissTimingFactory = swissTimingFactory; + this.domainFactory = domainFactory; + this.raceLogStore = raceLogStore; + } + + @Override + public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, GPSFixStore gpsFixStore) throws Exception { + return swissTimingFactory.createRaceTracker(raceID, raceName, raceDescription, boatClass, hostname, port, startList, delayToLiveInMillis, raceLogStore, windStore, gpsFixStore, domainFactory, + trackedRegattaRegistry); + } + + @Override + public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, GPSFixStore gpsFixStore) + throws Exception { + return swissTimingFactory.createRaceTracker(regatta, raceID, raceName, raceDescription, boatClass, hostname, port, startList, delayToLiveInMillis, windStore, gpsFixStore, domainFactory, + trackedRegattaRegistry); + } + + @Override + public Object getTrackerID() { + return SwissTimingRaceTrackerImpl.createID(raceID, hostname, port); + } + + @Override + public long getDelayToLiveInMillis() { + return delayToLiveInMillis; + } +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/TMDMessageQueue.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/TMDMessageQueue.java index fd4a5c5003f..a2f0010c9a4 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/TMDMessageQueue.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/TMDMessageQueue.java @@ -12,10 +12,10 @@ import java.util.logging.Logger; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Course; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Util; /** * When TMD messages about mark passings arrive with their race start time-relative time stamp, and no race start time @@ -31,10 +31,10 @@ public class TMDMessageQueue { private static class TMDMessageContents { private final String raceID; private final String boatID; - private final List> markIndicesRanksAndTimesSinceStartInMilliseconds; + private final List> markIndicesRanksAndTimesSinceStartInMilliseconds; public TMDMessageContents(String raceID, String boatID, - List> markIndicesRanksAndTimesSinceStartInMilliseconds) { + List> markIndicesRanksAndTimesSinceStartInMilliseconds) { super(); this.raceID = raceID; this.boatID = boatID; @@ -49,7 +49,7 @@ public class TMDMessageQueue { return boatID; } - public List> getMarkIndicesRanksAndTimesSinceStartInMilliseconds() { + public List> getMarkIndicesRanksAndTimesSinceStartInMilliseconds() { return markIndicesRanksAndTimesSinceStartInMilliseconds; } @@ -58,7 +58,7 @@ public class TMDMessageQueue { StringBuilder result = new StringBuilder("TMD|"+getRaceID()+"|"+getBoatID()+"|"); result.append(getMarkIndicesRanksAndTimesSinceStartInMilliseconds().size()); result.append('|'); - for (Triple markIndexRankAndTimeSinceStartInMilliseconds : getMarkIndicesRanksAndTimesSinceStartInMilliseconds()) { + for (Util.Triple markIndexRankAndTimeSinceStartInMilliseconds : getMarkIndicesRanksAndTimesSinceStartInMilliseconds()) { result.append(markIndexRankAndTimeSinceStartInMilliseconds.getA()); result.append(';'); if (markIndexRankAndTimeSinceStartInMilliseconds.getB() != null) { @@ -82,7 +82,7 @@ public class TMDMessageQueue { queuedMessages = new HashSet<>(); } - public synchronized void enqueue(String raceID, String boatID, List> markIndicesRanksAndTimesSinceStartInMilliseconds) { + public synchronized void enqueue(String raceID, String boatID, List> markIndicesRanksAndTimesSinceStartInMilliseconds) { final TMDMessageContents message = new TMDMessageContents(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds); queuedMessages.add(message); logger.info("Queued TMD message "+message+" for replay when start time has been received"); @@ -101,39 +101,51 @@ public class TMDMessageQueue { // otherwise, this could lead to the "guessed time" to while (i.hasNext()) { TMDMessageContents messageContents = i.next(); - Competitor competitor = raceTracker.getDomainFactory().getCompetitorByBoatIDAndRaceIDOrBoatClass(messageContents.getBoatID(), + Competitor competitor = raceTracker.getCompetitorByBoatIDAndRaceIDOrBoatClass(messageContents.getBoatID(), messageContents.getRaceID(), raceTracker.getBoatClass()); - NavigableSet oldMarkPassings = trackedRace.getMarkPassings(competitor); - TreeMap cleansedMarkPassingsForCompetitor = cleansedMarkPassings.get(competitor); - if (cleansedMarkPassingsForCompetitor == null) { - cleansedMarkPassingsForCompetitor = new TreeMap<>(); - cleansedMarkPassings.put(competitor, cleansedMarkPassingsForCompetitor); - Course course = trackedRace.getRace().getCourse(); - course.lockForRead(); - try { - trackedRace.lockForRead(oldMarkPassings); + if (competitor == null) { + logger.info("Received timing data for boat ID " + messageContents.getBoatID() + " in race " + messageContents.getRaceID() + + " but couldn't find a competitor with that boat ID in this race. Ignoring."); + } else { + NavigableSet oldMarkPassings = trackedRace.getMarkPassings(competitor); + TreeMap cleansedMarkPassingsForCompetitor = cleansedMarkPassings.get(competitor); + if (cleansedMarkPassingsForCompetitor == null) { + cleansedMarkPassingsForCompetitor = new TreeMap<>(); + cleansedMarkPassings.put(competitor, cleansedMarkPassingsForCompetitor); + Course course = trackedRace.getRace().getCourse(); + course.lockForRead(); try { - for (MarkPassing oldMarkPassing : oldMarkPassings) { - int waypointIndex = course.getIndexOfWaypoint(oldMarkPassing.getWaypoint()); - cleansedMarkPassingsForCompetitor.put(waypointIndex, oldMarkPassing); + trackedRace.lockForRead(oldMarkPassings); + try { + for (MarkPassing oldMarkPassing : oldMarkPassings) { + int waypointIndex = course.getIndexOfWaypoint(oldMarkPassing.getWaypoint()); + cleansedMarkPassingsForCompetitor.put(waypointIndex, oldMarkPassing); + } + } finally { + trackedRace.unlockAfterRead(oldMarkPassings); } } finally { - trackedRace.unlockAfterRead(oldMarkPassings); + course.unlockAfterRead(); } - } finally { - course.unlockAfterRead(); } - } - // remove those mark passings for which the TMD message has mark passing times; their time points would just have been guessed. - // This will avoid that the start time inference - // rules consider them and let them take precedence over the start time received - for (Triple markIndexRankAndTimeSinceStartInMilliseconds : messageContents.getMarkIndicesRanksAndTimesSinceStartInMilliseconds()) { - if (cleansedMarkPassingsForCompetitor.containsKey(markIndexRankAndTimeSinceStartInMilliseconds.getA())) { - logger.info("Removing mark passing for mark #"+markIndexRankAndTimeSinceStartInMilliseconds.getA()+ - " for competitor "+competitor.getName()+" because its time point "+ - cleansedMarkPassingsForCompetitor.get(markIndexRankAndTimeSinceStartInMilliseconds.getA()).getTimePoint()+ - " was guessed; will replace momentarily..."); - cleansedMarkPassingsForCompetitor.remove(markIndexRankAndTimeSinceStartInMilliseconds.getA()); + // remove those mark passings for which the TMD message has mark passing times; their time points would + // just have been guessed. + // This will avoid that the start time inference + // rules consider them and let them take precedence over the start time received + for (Util.Triple markIndexRankAndTimeSinceStartInMilliseconds : messageContents + .getMarkIndicesRanksAndTimesSinceStartInMilliseconds()) { + if (cleansedMarkPassingsForCompetitor.containsKey(markIndexRankAndTimeSinceStartInMilliseconds + .getA())) { + logger.info("Removing mark passing for mark #" + + markIndexRankAndTimeSinceStartInMilliseconds.getA() + + " for competitor " + + competitor.getName() + + " because its time point " + + cleansedMarkPassingsForCompetitor.get( + markIndexRankAndTimeSinceStartInMilliseconds.getA()).getTimePoint() + + " was guessed; will replace momentarily..."); + cleansedMarkPassingsForCompetitor.remove(markIndexRankAndTimeSinceStartInMilliseconds.getA()); + } } } } diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/META-INF/MANIFEST.MF index 093df8892cd..2586a20a1c9 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/META-INF/MANIFEST.MF @@ -9,4 +9,5 @@ Fragment-Host: com.sap.sailing.domain.swisstimingreplayadapter Import-Package: org.json.simple, org.json.simple.parser Require-Bundle: com.sap.sailing.domain.test, - org.junit4;bundle-version="4.8.2" + org.junit4;bundle-version="4.8.2", + com.sap.sse.common diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java index cb6c4a969a5..067d440befa 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.swisstimingreplayadapter.test; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; @@ -17,7 +18,6 @@ import org.junit.Test; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.swisstimingadapter.DomainFactory; import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayRace; import com.sap.sailing.domain.swisstimingreplayadapter.impl.SwissTimingReplayParserImpl; @@ -25,6 +25,7 @@ import com.sap.sailing.domain.swisstimingreplayadapter.impl.SwissTimingReplaySer import com.sap.sailing.domain.swisstimingreplayadapter.impl.SwissTimingReplayToDomainAdapter; import com.sap.sailing.domain.test.DummyTrackedRegattaRegistry; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Util; public class SwissTimingReplayAdapterServiceTest { @@ -195,6 +196,7 @@ public class SwissTimingReplayAdapterServiceTest { Iterable trackedRaces = replayListener.getTrackedRaces(); TrackedRace trackedRace = trackedRaces.iterator().next(); for (Competitor competitor : trackedRace.getRace().getCompetitors()) { + assertSame(DomainFactory.INSTANCE.getBaseDomainFactory().getOrCreateBoatClass("470"), competitor.getBoat().getBoatClass()); Distance distanceToLineAtStart = trackedRace.getDistanceToStartLine(competitor, trackedRace.getStartOfRace()); assertTrue(distanceToLineAtStart.getMeters() > 0); assertTrue(distanceToLineAtStart.getMeters() < 8.5); diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.swisstimingreplayadapter/META-INF/MANIFEST.MF index 0e38d9c32a7..39c07f68edd 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/META-INF/MANIFEST.MF @@ -10,7 +10,8 @@ Require-Bundle: com.sap.sailing.domain.common, com.sap.sailing.domain, org.json.simple;bundle-version="1.1.0", com.sap.sailing.domain.swisstimingadapter, - com.googlecode.java-diff-utils;bundle-version="1.3.0" + com.googlecode.java-diff-utils;bundle-version="1.3.0", + com.sap.sse.common Export-Package: com.sap.sailing.domain.swisstimingreplayadapter Import-Package: org.osgi.framework;version="1.7.0" Bundle-Activator: com.sap.sailing.domain.swisstimingreplayadapter.impl.Activator diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayRace.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayRace.java index a84cc9b1606..8cc4aaeb78f 100755 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayRace.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayRace.java @@ -2,7 +2,7 @@ package com.sap.sailing.domain.swisstimingreplayadapter; import java.util.Date; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; public interface SwissTimingReplayRace extends Named { diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java index b0195e434f7..499b9b7d44e 100755 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java @@ -34,10 +34,9 @@ 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.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.swisstimingadapter.DomainFactory; import com.sap.sailing.domain.swisstimingreplayadapter.CompetitorStatus; import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayListener; @@ -53,6 +52,7 @@ import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sse.common.Util; import difflib.PatchFailedException; @@ -231,8 +231,8 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { CompetitorStatus competitorStatus, BoatType boatType, short cRank_Bracket, short cnPoints_x10_Bracket, short ctPoints_x10_Winner) { if (boatType == BoatType.Competitor) { - Competitor competitor = domainFactory.getOrCreateCompetitor(sailNumberOrTrackerID, threeLetterIOCCode.trim(), name.trim(), - currentRaceID, null /* boat class */); + Competitor competitor = domainFactory.createCompetitorWithoutID(sailNumberOrTrackerID, threeLetterIOCCode.trim(), name.trim(), + currentRaceID, domainFactory.getRaceTypeFromRaceID(currentRaceID).getBoatClass()); Set competitorsOfCurrentRace = competitorsPerRaceID.get(currentRaceID); if (competitorsOfCurrentRace == null) { competitorsOfCurrentRace = new HashSet<>(); @@ -289,9 +289,9 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { Course course = race.getCourse(); try { // TODO: Does SwissTiming also deliver the passing side for course marks? - List> courseToUpdate = new ArrayList>(); + List> courseToUpdate = new ArrayList>(); for (ControlPoint cp : currentCourseDefinition) { - courseToUpdate.add(new Pair(cp, null)); + courseToUpdate.add(new com.sap.sse.common.Util.Pair(cp, null)); } course.update(courseToUpdate, domainFactory.getBaseDomainFactory()); } catch (PatchFailedException e) { @@ -305,12 +305,12 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { private void createRace() { final Regatta myRegatta = regatta != null ? regatta : domainFactory.getOrCreateDefaultRegatta(EmptyRaceLogStore.INSTANCE, - currentRaceID, null /* boat class */, trackedRegattaRegistry); + currentRaceID, domainFactory.getRaceTypeFromRaceID(currentRaceID).getBoatClass(), trackedRegattaRegistry); RaceDefinition race = domainFactory.createRaceDefinition(myRegatta, currentRaceID, competitorsPerRaceID.get(currentRaceID), currentCourseDefinition); racePerRaceID.put(currentRaceID, race); DynamicTrackedRace trackedRace = trackedRegattaRegistry.getOrCreateTrackedRegatta(myRegatta). - createTrackedRace(race, Collections. emptyList(), EmptyWindStore.INSTANCE, TrackedRace.DEFAULT_LIVE_DELAY_IN_MILLISECONDS, + createTrackedRace(race, Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE,TrackedRace.DEFAULT_LIVE_DELAY_IN_MILLISECONDS, WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, /* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(), /* raceDefinitionSetToUpdate */ null); diff --git a/java/com.sap.sailing.domain.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.test/META-INF/MANIFEST.MF index dbccead117f..7fddd901b49 100755 --- a/java/com.sap.sailing.domain.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.test/META-INF/MANIFEST.MF @@ -16,6 +16,7 @@ Require-Bundle: com.sap.sailing.domain, org.hamcrest;bundle-version="1.1.0", org.objenesis;bundle-version="1.3.0", org.junit4;bundle-version="4.8.2", - org.json.simple;bundle-version="1.1.0" + org.json.simple;bundle-version="1.1.0", + com.sap.sse.common Export-Package: com.sap.sailing.domain.test, com.sap.sailing.domain.test.mock diff --git a/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_29.mtb b/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_29.mtb new file mode 100755 index 00000000000..bec8d39ba68 Binary files /dev/null and b/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_29.mtb differ diff --git a/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_29.txt b/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_29.txt new file mode 100755 index 00000000000..1c840382fe9 --- /dev/null +++ b/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_29.txt @@ -0,0 +1,582 @@ +stored-uri:datafiles/event_20140429_ESSQingdao-Race_29.mtb +StartReplay:1 + +// Input from params_static: + +// Input from event_parameters: + +CompMarkerUUID:1 +CodanIsSponsor:0 +DefaultTailLength:60 +APzoomChangeTime:2500 +disableAutoProduce:0 +autoProduce:0 +APstartLineShowOfLeg:0.25 +APsideOfStartLine:0.30 +APstartSwitch:0.8 +APwholeLegBehindMarks:0.15 +APmarkSwitch:0.13 +APmarkZoomBehindMarks:0.10 +APmarkZoomInFrontOfMarks:0.20 +PopupMarkerUUID:2 +ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf +//live-uri:tcp://192.168.1.203:4400 +//stored-uri:tcp://192.168.1.203:4401 +Marker1UUID:1 +Marker1Type:RGBMarkerFactory +Marker1Directional:1 +Marker1Image:markers/boat_FF0000_big.png +Marker1Length:5 +Marker1MinPixelSize:30 +Startlocation:120.38632089843748,36.049596263422494 +MaxDiffTime:1200 +MaxMetersPerSecond:20 +PrecisionMeters:20 +ShortAvgTime:180 +LongAvgTime:900 +TimeZone:8 +CompMarkerTimeout:60 +FramesPerSecond:8 +replayspeedup:5 +MatchRaceClock:0 +MatchRaceClockNo:3 +MatchRaceClockSpacing:300 +Minimap:1 +MinimapUseLayers:2 +Minimapgeorect:120.36068115234377,36.06814954054894,120.41175086669921,36.03604889362103 +MinimapMarkerUUID:2 +Marker4UUID:2 +Marker4Type:RGBmarkerFactory +Marker4Directional:1 +Marker4Image:markers/boat_FF0000_small.png +MetersPerPixel:18 +MetersPerPixelMax:30 +MetersPerPixelMin:0.2 +isMatchRace:0 +DefaultDistanceUnits:meters +weatherEnabled:0 +DefaultSpeedUnits:KmH +showRaceClock:1 +BackGroundImage:graphics/background.jpg +DisableRotation:0 +ControlPointMarkerRadius:13 +disableselection:0 +disableleaderline:0 +DisableChangeRace:0 +PopUpRow1Name: +PopUpRow1Type:Name +PopUpRow2Name:Knots +PopUpRow2Type:BoatSpeed +PopUpRow3Name:Ranking +PopUpRow3Type:RowNumber +PopUpRow4Name:Sail +PopUpRow4Type:Ini +PopUpRow5Name:After +PopUpRow5Type:BoatAfter +ResultColumn1Name:# +ResultColumn1Size:20 +ResultColumn1Type:RowNumber +ResultColumn2Name:sail +ResultColumn2Size:45 +ResultColumn2Type:Ini +ResultColumn3Name:name +ResultColumn3Size:110 +ResultColumn3Type:Name +ResultColumn4Name:after +ResultColumn4Size:45 +ResultColumn4Type:BoatAfter +ResultColumn5Name:kts +ResultColumn5Size:40 +ResultColumn5Type:BoatSpeed +DisableLeaderboard:0 +DisableAnalysis:0 +TableFontSize:11 +compInitialsFontSize:11 +AutoRotate:0 +MassStart:0 +SegmentFile:segments.php +StartType:Mass +ReCalcDirAndSpeed:0 +RaceHandicapSystem:NONE +TeamCompetitionType:NONE +LiveDelaySecs: 8 + +//-------- AUTO GENERATED PARAMETERS START ---------------- + +EventID:8b85f120-ada6-0131-e74f-60a44ce903c3 +EventName:ESS Qingdao 2014 +EventDB:event_20140429_ESSQingdao +EventStartTime:2014-04-28 16:01:00 +EventEndTime:2014-05-04 15:00:00 +EventType:a1b74ca0-fdd8-11dc-8811-005056c00008 + +RaceID:f6ec0720-b56d-0131-1f9f-10b11c4ed8fd +RaceName:Race 29 +RaceStartTime:2014-05-04 07:47:59 +RaceEndTime: +RaceTrackingStartTime:2014-05-04 07:43:59 +RaceTrackingEndTime:2014-05-04 08:00:13 +RaceDefaultRouteUUID:f6e91fa0-b56d-0131-1f9e-10b11c4ed8fd +RaceHandicapSystem:NONE + +Image1UUID:64a7b348-83db-11e2-88c0-10bf48d758ce +Image1Type:Logo +Image1Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif +Image1Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif + +Image2UUID:64c3a8fa-83db-11e2-88c0-10bf48d758ce +Image2Type:Logo +Image2Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif +Image2Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif + +Image3UUID:651130a2-83db-11e2-88c0-10bf48d758ce +Image3Type:Logo +Image3Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/KOR.gif +Image3Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/KOR.gif + +Image4UUID:653c1ab0-83db-11e2-88c0-10bf48d758ce +Image4Type:Logo +Image4Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif +Image4Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif + +Image5UUID:65637da8-83db-11e2-88c0-10bf48d758ce +Image5Type:Logo +Image5Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif +Image5Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif + +Image6UUID:65875138-83db-11e2-88c0-10bf48d758ce +Image6Type:Logo +Image6Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/OMN.gif +Image6Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/OMN.gif + +Class1UUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Class1Name:Extreme40 + +Team1UUID:649f1dbe-83db-11e2-88c0-10bf48d758ce +Team1Name:TeamA + +Team2UUID:64bf0854-83db-11e2-88c0-10bf48d758ce +Team2Name:TeamB + +Team3UUID:64dd7988-83db-11e2-88c0-10bf48d758ce +Team3Name:TeamC +Team3Comp1UUID:64da7c24-83db-11e2-88c0-10bf48d758ce + +Team4UUID:64ffc402-83db-11e2-88c0-10bf48d758ce +Team4Name:TeamD + +Team5UUID:65351d82-83db-11e2-88c0-10bf48d758ce +Team5Name:TeamE + +Team6UUID:655ed82a-83db-11e2-88c0-10bf48d758ce +Team6Name:TeamF +Team6Comp1UUID:655bd56c-83db-11e2-88c0-10bf48d758ce + +Team7UUID:65829fa8-83db-11e2-88c0-10bf48d758ce +Team7Name:TeamG + +Team8UUID:e4459a20-83db-11e2-88c0-10bf48d758ce +Team8Name:TeamH +Team8Comp1UUID:e442a414-83db-11e2-88c0-10bf48d758ce + +Team9UUID:a73aabc0-d6d6-0130-8be2-60a44ce903c3 +Team9Name:TeamI +Team9Comp1UUID:93085e80-7ad9-0131-e5b3-60a44ce903c3 + +Team10UUID:bbf81f60-d6d6-0130-8be3-60a44ce903c3 +Team10Name:TeamJ +Team10Comp1UUID:f097f4f0-7ad9-0131-e5b4-60a44ce903c3 + +Team11UUID:65659280-eda3-0130-c5c0-10b11c4ed8fd +Team11Name:TeamK + +Team12UUID:9ee14560-77af-0131-e5af-60a44ce903c3 +Team12Name:TeamL + +Team13UUID:b9677300-7ad7-0131-e5b0-60a44ce903c3 +Team13Name:TeamM +Team13Comp1UUID:84e68b70-7ada-0131-e5b7-60a44ce903c3 + +Team14UUID:c9689a00-7ad7-0131-e5b1-60a44ce903c3 +Team14Name:TeamN + +Team15UUID:d5712f30-7ad7-0131-e5b2-60a44ce903c3 +Team15Name:TeamO + +Comp1UUID:64da7c24-83db-11e2-88c0-10bf48d758ce +Comp1Name:Realteam by Realstone +Comp1NameShort:Realstone +Comp1Color:000000 +Comp1Nationality:AFG +Comp1Description: +Comp1Handicap:1.0000000000 +Comp1HandicapDistance:0.00000 +Comp1StartTime:2014-05-04 07:47:59 +Comp1PictureUUID:64a7b348-83db-11e2-88c0-10bf48d758ce +Comp1ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp1RouteUUID:f6e91fa0-b56d-0131-1f9e-10b11c4ed8fd + +Comp2UUID:655bd56c-83db-11e2-88c0-10bf48d758ce +Comp2Name:SAP Extreme Sailing Team +Comp2NameShort:SAP +Comp2Color:FFFFFF +Comp2Nationality:DEN +Comp2Description: +Comp2Handicap:1.0000000000 +Comp2HandicapDistance:0.00000 +Comp2StartTime:2014-05-04 07:47:59 +Comp2PictureUUID:65637da8-83db-11e2-88c0-10bf48d758ce +Comp2ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp2RouteUUID:f6e91fa0-b56d-0131-1f9e-10b11c4ed8fd + +Comp3UUID:e442a414-83db-11e2-88c0-10bf48d758ce +Comp3Name:Invite +Comp3NameShort:Invite +Comp3Color:FFFF00 +Comp3Nationality:SIN +Comp3Description: +Comp3Handicap:1.0000000000 +Comp3HandicapDistance:0.00000 +Comp3StartTime:2014-05-04 07:47:59 +Comp3ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp3RouteUUID:f6e91fa0-b56d-0131-1f9e-10b11c4ed8fd + +Comp4UUID:93085e80-7ad9-0131-e5b3-60a44ce903c3 +Comp4Name:Emirates Team New Zealand +Comp4NameShort:Emirates NZ +Comp4Color:CC9900 +Comp4Nationality:NZL +Comp4Description: +Comp4Handicap:1.0000000000 +Comp4HandicapDistance:0.00000 +Comp4StartTime:2014-05-04 07:47:59 +Comp4PictureUUID:64c3a8fa-83db-11e2-88c0-10bf48d758ce +Comp4ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp4RouteUUID:f6e91fa0-b56d-0131-1f9e-10b11c4ed8fd + +Comp5UUID:f097f4f0-7ad9-0131-e5b4-60a44ce903c3 +Comp5Name:Gazprom Team Russia +Comp5NameShort:Team Russia +Comp5Color:999900 +Comp5Nationality:RUS +Comp5Description: +Comp5Handicap:1.0000000000 +Comp5HandicapDistance:0.00000 +Comp5StartTime:2014-05-04 07:47:59 +Comp5ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp5RouteUUID:f6e91fa0-b56d-0131-1f9e-10b11c4ed8fd + +Comp6UUID:84e68b70-7ada-0131-e5b7-60a44ce903c3 +Comp6Name:Oman Air +Comp6NameShort:Oman Air +Comp6Color:CCFF99 +Comp6Nationality:OMA +Comp6Description: +Comp6Handicap:1.0000000000 +Comp6HandicapDistance:0.00000 +Comp6StartTime:2014-05-04 07:47:59 +Comp6PictureUUID:65875138-83db-11e2-88c0-10bf48d758ce +Comp6ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp6RouteUUID:f6e91fa0-b56d-0131-1f9e-10b11c4ed8fd + +ControlPoint1UUID:1100ea60-7adb-0131-e5b8-60a44ce903c3 +ControlPoint1Name:Red-Green +ControlPoint1HasTwoPoints:1 +ControlPoint1ShortName:RG +ControlPoint1DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Red###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Red###BREAKLINE###P1.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Green###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Green###BREAKLINE###P2.UUID=785c3e44-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint1Mark1Lon:120.383118 +ControlPoint1Mark1Lat:36.055012 +ControlPoint1Mark2Lon:120.379540 +ControlPoint1Mark2Lat:36.057548 + +ControlPoint2UUID:1286f15e-83be-11e2-88c0-10bf48d758ce +ControlPoint2Name:Blue +ControlPoint2HasTwoPoints:0 +ControlPoint2ShortName:Bl +ControlPoint2DataSheet:Name=Blue###BREAKLINE###Type=Buoy###BREAKLINE###Color=Blue###BREAKLINE###Shape=Conical +ControlPoint2Mark1Lon:120.380615 +ControlPoint2Mark1Lat:36.057808 + +ControlPoint3UUID:1a94c6a0-7adc-0131-e5f4-60a44ce903c3 +ControlPoint3Name:Black Conical-Blue +ControlPoint3HasTwoPoints:1 +ControlPoint3ShortName:BB +ControlPoint3DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Conical###BREAKLINE###P1.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Blue###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Blue###BREAKLINE###P2.UUID=1286f15e-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint3Mark1Lon:120.381707 +ControlPoint3Mark1Lat:36.054402 +ControlPoint3Mark2Lon:120.380615 +ControlPoint3Mark2Lat:36.057808 + +ControlPoint4UUID:2dc7d9d0-7b84-0131-b9c3-10b11c4ed8fd +ControlPoint4Name:Black Cylinder - Black Conical +ControlPoint4HasTwoPoints:1 +ControlPoint4ShortName:BB +ControlPoint4DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Cylinder###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Cylinder###BREAKLINE###P1.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Conical###BREAKLINE###P2.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce +ControlPoint4Mark1Lon:120.384185 +ControlPoint4Mark1Lat:36.054085 +ControlPoint4Mark2Lon:120.381707 +ControlPoint4Mark2Lat:36.054402 + +ControlPoint5UUID:2e37b410-7adc-0131-e5fe-60a44ce903c3 +ControlPoint5Name:Black Conical-Yellow +ControlPoint5HasTwoPoints:1 +ControlPoint5ShortName:BY +ControlPoint5DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Conical###BREAKLINE###P1.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint5Mark1Lon:120.381707 +ControlPoint5Mark1Lat:36.054402 +ControlPoint5Mark2Lon:120.383560 +ControlPoint5Mark2Lat:36.056408 + +ControlPoint6UUID:3240586c-83c0-11e2-88c0-10bf48d758ce +ControlPoint6Name:Finish Pole +ControlPoint6HasTwoPoints:0 +ControlPoint6ShortName:F +ControlPoint6DataSheet:Color=Black###BREAKLINE###Type=Landmark###BREAKLINE###Name=Finish Pole +ControlPoint6Mark1Lon:120.382858 +ControlPoint6Mark1Lat:36.052662 + +ControlPoint7UUID:37cb36e0-7adb-0131-e5c2-60a44ce903c3 +ControlPoint7Name:Red-Blue +ControlPoint7HasTwoPoints:1 +ControlPoint7ShortName:RB +ControlPoint7DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Red###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Red###BREAKLINE###P1.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Blue###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Blue###BREAKLINE###P2.UUID=1286f15e-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint7Mark1Lon:120.383118 +ControlPoint7Mark1Lat:36.055012 +ControlPoint7Mark2Lon:120.380615 +ControlPoint7Mark2Lat:36.057808 + +ControlPoint8UUID:3e6c1c94-83bf-11e2-88c0-10bf48d758ce +ControlPoint8Name:Race Committee +ControlPoint8HasTwoPoints:0 +ControlPoint8ShortName:RC +ControlPoint8DataSheet:Type=StartBoat###BREAKLINE###Name=Race Committee +ControlPoint8Mark1Lon:120.383445 +ControlPoint8Mark1Lat:36.055175 + +ControlPoint9UUID:3fdd2402-83be-11e2-88c0-10bf48d758ce +ControlPoint9Name:Yellow +ControlPoint9HasTwoPoints:0 +ControlPoint9ShortName:Y +ControlPoint9DataSheet:Type=Buoy###BREAKLINE###Color=Yellow###BREAKLINE###Shape=Conical###BREAKLINE###Name=Yellow +ControlPoint9Mark1Lon:120.383560 +ControlPoint9Mark1Lat:36.056408 + +ControlPoint10UUID:4c304720-7adc-0131-e608-60a44ce903c3 +ControlPoint10Name:Black Conical-Red +ControlPoint10HasTwoPoints:1 +ControlPoint10ShortName:BR +ControlPoint10DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Conical###BREAKLINE###P1.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Red###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Red###BREAKLINE###P2.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint10Mark1Lon:120.381707 +ControlPoint10Mark1Lat:36.054402 +ControlPoint10Mark2Lon:120.383118 +ControlPoint10Mark2Lat:36.055012 + +ControlPoint11UUID:4e0c2a90-7adb-0131-e5cc-60a44ce903c3 +ControlPoint11Name:Red-Yellow +ControlPoint11HasTwoPoints:1 +ControlPoint11ShortName:RY +ControlPoint11DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Red###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Red###BREAKLINE###P1.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint11Mark1Lon:120.383118 +ControlPoint11Mark1Lat:36.055012 +ControlPoint11Mark2Lon:120.383560 +ControlPoint11Mark2Lat:36.056408 + +ControlPoint12UUID:5427c7d2-83be-11e2-88c0-10bf48d758ce +ControlPoint12Name:Red +ControlPoint12HasTwoPoints:0 +ControlPoint12ShortName:R +ControlPoint12DataSheet:Type=Buoy###BREAKLINE###Color=Red###BREAKLINE###Shape=Conical###BREAKLINE###Name=Red +ControlPoint12Mark1Lon:120.383118 +ControlPoint12Mark1Lat:36.055012 + +ControlPoint13UUID:584cfe30-b328-0131-5246-10b11c4ed8fd +ControlPoint13Name:R2-Green +ControlPoint13HasTwoPoints:0 +ControlPoint13ShortName:G +ControlPoint13DataSheet:Name=Green +ControlPoint13Mark1Lon:120.386972 +ControlPoint13Mark1Lat:36.046795 + +ControlPoint14UUID:621831d0-7adc-0131-e612-60a44ce903c3 +ControlPoint14Name:Black Conical-Green +ControlPoint14HasTwoPoints:1 +ControlPoint14ShortName:BG +ControlPoint14DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Conical###BREAKLINE###P1.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Green###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Green###BREAKLINE###P2.UUID=785c3e44-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint14Mark1Lon:120.381707 +ControlPoint14Mark1Lat:36.054402 +ControlPoint14Mark2Lon:120.379540 +ControlPoint14Mark2Lat:36.057548 + +ControlPoint15UUID:77b9e620-7adb-0131-e5d6-60a44ce903c3 +ControlPoint15Name:Green-Blue +ControlPoint15HasTwoPoints:1 +ControlPoint15ShortName:GB +ControlPoint15DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Green###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Green###BREAKLINE###P1.UUID=785c3e44-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Blue###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Blue###BREAKLINE###P2.UUID=1286f15e-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint15Mark1Lon:120.379540 +ControlPoint15Mark1Lat:36.057548 +ControlPoint15Mark2Lon:120.380615 +ControlPoint15Mark2Lat:36.057808 + +ControlPoint16UUID:785c3e44-83be-11e2-88c0-10bf48d758ce +ControlPoint16Name:Green +ControlPoint16HasTwoPoints:0 +ControlPoint16ShortName:G +ControlPoint16DataSheet:Type=Buoy###BREAKLINE###Color=Green###BREAKLINE###Shape=Conical###BREAKLINE###Name=Green +ControlPoint16Mark1Lon:120.379540 +ControlPoint16Mark1Lat:36.057548 + +ControlPoint17UUID:7d9d1730-b3e8-0131-1d10-10b11c4ed8fd +ControlPoint17Name:Race Committee - Finish Pole +ControlPoint17HasTwoPoints:1 +ControlPoint17ShortName:RF +ControlPoint17DataSheet:P1.Type=StartBoat###BREAKLINE###P1.Name=Race Committee###BREAKLINE###P1.UUID=3e6c1c94-83bf-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Type=Landmark###BREAKLINE###P2.Name=Finish Pole###BREAKLINE###P2.UUID=3240586c-83c0-11e2-88c0-10bf48d758ce +ControlPoint17Mark1Lon:120.383445 +ControlPoint17Mark1Lat:36.055175 +ControlPoint17Mark2Lon:120.382858 +ControlPoint17Mark2Lat:36.052662 + +ControlPoint18UUID:7e1b7a80-7d0b-0131-a109-10b11c4ed8fd +ControlPoint18Name:Black Gate +ControlPoint18HasTwoPoints:1 +ControlPoint18ShortName:S +ControlPoint18DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Cylinder###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Cylinder###BREAKLINE###P1.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Conical###BREAKLINE###P2.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint18Mark1Lon:103.855782 +ControlPoint18Mark1Lat:1.282103 +ControlPoint18Mark2Lon:103.857560 +ControlPoint18Mark2Lat:1.286382 + +ControlPoint19UUID:88ef6d40-7adc-0131-e61c-60a44ce903c3 +ControlPoint19Name:Finish Pole-Cylinder +ControlPoint19HasTwoPoints:1 +ControlPoint19ShortName:F +ControlPoint19DataSheet:P1.Color=Black###BREAKLINE###P1.Type=Landmark###BREAKLINE###P1.Name=Finish Pole###BREAKLINE###P1.UUID=3240586c-83c0-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Cylinder###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Cylinder###BREAKLINE###P2.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint19Mark1Lon:120.382858 +ControlPoint19Mark1Lat:36.052662 +ControlPoint19Mark2Lon:120.384185 +ControlPoint19Mark2Lat:36.054085 + +ControlPoint20UUID:8f9a4ee0-7adb-0131-e5e0-60a44ce903c3 +ControlPoint20Name:Green-Yellow +ControlPoint20HasTwoPoints:1 +ControlPoint20ShortName:GY +ControlPoint20DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Green###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Green###BREAKLINE###P1.UUID=785c3e44-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint20Mark1Lon:120.379540 +ControlPoint20Mark1Lat:36.057548 +ControlPoint20Mark2Lon:120.383560 +ControlPoint20Mark2Lat:36.056408 + +ControlPoint21UUID:a0f71190-7adb-0131-e5ea-60a44ce903c3 +ControlPoint21Name:Blue-Yellow +ControlPoint21HasTwoPoints:1 +ControlPoint21ShortName:BY +ControlPoint21DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Blue###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Blue###BREAKLINE###P1.UUID=1286f15e-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint21Mark1Lon:120.380615 +ControlPoint21Mark1Lat:36.057808 +ControlPoint21Mark2Lon:120.383560 +ControlPoint21Mark2Lat:36.056408 + +ControlPoint22UUID:a68c6350-7adc-0131-e626-60a44ce903c3 +ControlPoint22Name:RC-Black Conical +ControlPoint22HasTwoPoints:1 +ControlPoint22ShortName:S +ControlPoint22DataSheet:P1.Type=StartBoat###BREAKLINE###P1.Name=Race Committee###BREAKLINE###P1.UUID=3e6c1c94-83bf-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Conical###BREAKLINE###P2.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint22Mark1Lon:120.383445 +ControlPoint22Mark1Lat:36.055175 +ControlPoint22Mark2Lon:120.381707 +ControlPoint22Mark2Lat:36.054402 + +ControlPoint23UUID:b153dfd6-83be-11e2-88c0-10bf48d758ce +ControlPoint23Name:Black Cylinder +ControlPoint23HasTwoPoints:0 +ControlPoint23ShortName:B +ControlPoint23DataSheet:Type=Buoy###BREAKLINE###Color=Black###BREAKLINE###Shape=Cylinder###BREAKLINE###Pattern=Checkered###BREAKLINE###Name=Black Cylinder +ControlPoint23Mark1Lon:120.384185 +ControlPoint23Mark1Lat:36.054085 + +ControlPoint24UUID:b62e0bf0-7adc-0131-e630-60a44ce903c3 +ControlPoint24Name:RC-Black Cylinder +ControlPoint24HasTwoPoints:1 +ControlPoint24ShortName:S +ControlPoint24DataSheet:P1.Type=StartBoat###BREAKLINE###P1.Name=Race Committee###BREAKLINE###P1.UUID=3e6c1c94-83bf-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Cylinder###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Cylinder###BREAKLINE###P2.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint24Mark1Lon:120.383445 +ControlPoint24Mark1Lat:36.055175 +ControlPoint24Mark2Lon:120.384185 +ControlPoint24Mark2Lat:36.054085 + +ControlPoint25UUID:bd1fc330-7c10-0131-9c24-10b11c4ed8fd +ControlPoint25Name:Black Cylinder-Yellow +ControlPoint25HasTwoPoints:1 +ControlPoint25ShortName:S +ControlPoint25DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Cylinder###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Cylinder###BREAKLINE###P1.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint25Mark1Lon:120.384185 +ControlPoint25Mark1Lat:36.054085 +ControlPoint25Mark2Lon:120.383560 +ControlPoint25Mark2Lat:36.056408 + +ControlPoint26UUID:bf6777e6-83bd-11e2-88c0-10bf48d758ce +ControlPoint26Name:White S +ControlPoint26HasTwoPoints:0 +ControlPoint26ShortName:W +ControlPoint26DataSheet:Type=Buoy###BREAKLINE###Color=White###BREAKLINE###Shape=Conical###BREAKLINE###Name=White S +ControlPoint26Mark1Lon:120.382568 +ControlPoint26Mark1Lat:36.054268 + +ControlPoint27UUID:cd3c1dc0-7c11-0131-9c2e-10b11c4ed8fd +ControlPoint27Name:Finish Pole-Conical +ControlPoint27HasTwoPoints:1 +ControlPoint27ShortName:S +ControlPoint27DataSheet:P1.Color=Black###BREAKLINE###P1.Type=Landmark###BREAKLINE###P1.Name=Finish Pole###BREAKLINE###P1.UUID=3240586c-83c0-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Conical###BREAKLINE###P2.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint27Mark1Lon:120.382858 +ControlPoint27Mark1Lat:36.052662 +ControlPoint27Mark2Lon:120.381707 +ControlPoint27Mark2Lat:36.054402 + +ControlPoint28UUID:d39a0d00-7adc-0131-e63a-60a44ce903c3 +ControlPoint28Name:White Gate +ControlPoint28HasTwoPoints:1 +ControlPoint28ShortName:W +ControlPoint28DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=White###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=White S###BREAKLINE###P1.UUID=bf6777e6-83bd-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=White###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=White P###BREAKLINE###P2.UUID=e9ec42f8-83bd-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint28Mark1Lon:120.382568 +ControlPoint28Mark1Lat:36.054268 +ControlPoint28Mark2Lon:120.381890 +ControlPoint28Mark2Lat:36.053973 + +ControlPoint29UUID:d78695ea-83be-11e2-88c0-10bf48d758ce +ControlPoint29Name:Black Conical +ControlPoint29HasTwoPoints:0 +ControlPoint29ShortName:B +ControlPoint29DataSheet:Type=Buoy###BREAKLINE###Color=Black###BREAKLINE###Shape=Conical###BREAKLINE###Pattern=Checkered###BREAKLINE###Name=Black Conical +ControlPoint29Mark1Lon:120.381707 +ControlPoint29Mark1Lat:36.054402 + +ControlPoint30UUID:e9ec42f8-83bd-11e2-88c0-10bf48d758ce +ControlPoint30Name:White P +ControlPoint30HasTwoPoints:0 +ControlPoint30ShortName:W +ControlPoint30DataSheet:Type=Buoy###BREAKLINE###Color=White###BREAKLINE###Shape=Conical###BREAKLINE###Name=White P +ControlPoint30Mark1Lon:120.381890 +ControlPoint30Mark1Lat:36.053973 + +ControlPoint31UUID:eff23330-7b84-0131-b9d2-10b11c4ed8fd +ControlPoint31Name:RC-Red +ControlPoint31HasTwoPoints:1 +ControlPoint31ShortName:S +ControlPoint31DataSheet:P1.Type=StartBoat###BREAKLINE###P1.Name=Race Committee###BREAKLINE###P1.UUID=3e6c1c94-83bf-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Red###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Red###BREAKLINE###P2.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint31Mark1Lon:120.383445 +ControlPoint31Mark1Lat:36.055175 +ControlPoint31Mark2Lon:120.383118 +ControlPoint31Mark2Lat:36.055012 + + +SegmentFile:segments.php +Route1UUID:f6e91fa0-b56d-0131-1f9e-10b11c4ed8fd +Route1Description:Race 29 +Route1Length:0 +Route1DataSheet:Seq.1=Line###BREAKLINE###Seq.2=Starboard###BREAKLINE###Seq.3=Gate###BREAKLINE###Seq.4=Starboard###BREAKLINE###Seq.5=Line###BREAKLINE### +Route1ControlPoint1UUID:a68c6350-7adc-0131-e626-60a44ce903c3 +Route1ControlPoint2UUID:1286f15e-83be-11e2-88c0-10bf48d758ce +Route1ControlPoint3UUID:d39a0d00-7adc-0131-e63a-60a44ce903c3 +Route1ControlPoint4UUID:1286f15e-83be-11e2-88c0-10bf48d758ce +Route1ControlPoint5UUID:88ef6d40-7adc-0131-e61c-60a44ce903c3 + +//-------- AUTO GENERATED PARAMETERS END ------------------ diff --git a/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_4.mtb b/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_4.mtb new file mode 100755 index 00000000000..54f88b77491 Binary files /dev/null and b/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_4.mtb differ diff --git a/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_4.txt b/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_4.txt new file mode 100755 index 00000000000..4c78d6c6d26 --- /dev/null +++ b/java/com.sap.sailing.domain.test/resources/event_20140429_ESSQingdao-Race_4.txt @@ -0,0 +1,653 @@ +stored-uri:datafiles/event_20140429_ESSQingdao-Race_4.mtb +StartReplay:1 + +// Input from params_static: + +// Input from event_parameters: + +CompMarkerUUID:1 +CodanIsSponsor:0 +DefaultTailLength:60 +APzoomChangeTime:2500 +disableAutoProduce:0 +autoProduce:0 +APstartLineShowOfLeg:0.25 +APsideOfStartLine:0.30 +APstartSwitch:0.8 +APwholeLegBehindMarks:0.15 +APmarkSwitch:0.13 +APmarkZoomBehindMarks:0.10 +APmarkZoomInFrontOfMarks:0.20 +PopupMarkerUUID:2 +ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf +//live-uri:tcp://192.168.1.203:4400 +//stored-uri:tcp://192.168.1.203:4401 +Marker1UUID:1 +Marker1Type:RGBMarkerFactory +Marker1Directional:1 +Marker1Image:markers/boat_FF0000_big.png +Marker1Length:5 +Marker1MinPixelSize:30 +Startlocation:120.38632089843748,36.049596263422494 +MaxDiffTime:1200 +MaxMetersPerSecond:20 +PrecisionMeters:20 +ShortAvgTime:180 +LongAvgTime:900 +TimeZone: +CompMarkerTimeout:60 +FramesPerSecond:8 +replayspeedup:5 +MatchRaceClock:0 +MatchRaceClockNo:3 +MatchRaceClockSpacing:300 +Minimap:1 +MinimapUseLayers:2 +Minimapgeorect:120.36068115234377,36.06814954054894,120.41175086669921,36.03604889362103 +MinimapMarkerUUID:2 +Marker4UUID:2 +Marker4Type:RGBmarkerFactory +Marker4Directional:1 +Marker4Image:markers/boat_FF0000_small.png +MetersPerPixel:18 +MetersPerPixelMax:30 +MetersPerPixelMin:0.2 +isMatchRace:0 +DefaultDistanceUnits:meters +weatherEnabled:0 +DefaultSpeedUnits:KmH +showRaceClock:1 +BackGroundImage:graphics/background.jpg +DisableRotation:0 +ControlPointMarkerRadius:13 +disableselection:0 +disableleaderline:0 +DisableChangeRace:0 +PopUpRow1Name: +PopUpRow1Type:Name +PopUpRow2Name:Knots +PopUpRow2Type:BoatSpeed +PopUpRow3Name:Ranking +PopUpRow3Type:RowNumber +PopUpRow4Name:Sail +PopUpRow4Type:Ini +PopUpRow5Name:After +PopUpRow5Type:BoatAfter +ResultColumn1Name:# +ResultColumn1Size:20 +ResultColumn1Type:RowNumber +ResultColumn2Name:sail +ResultColumn2Size:45 +ResultColumn2Type:Ini +ResultColumn3Name:name +ResultColumn3Size:110 +ResultColumn3Type:Name +ResultColumn4Name:after +ResultColumn4Size:45 +ResultColumn4Type:BoatAfter +ResultColumn5Name:kts +ResultColumn5Size:40 +ResultColumn5Type:BoatSpeed +DisableLeaderboard:0 +DisableAnalysis:0 +TableFontSize:11 +compInitialsFontSize:11 +AutoRotate:0 +MassStart:0 +SegmentFile:segments.php +StartType:Mass +ReCalcDirAndSpeed:0 +RaceHandicapSystem:NONE +TeamCompetitionType:NONE +LiveDelaySecs: 4 + +//-------- AUTO GENERATED PARAMETERS START ---------------- + +EventID:8b85f120-ada6-0131-e74f-60a44ce903c3 +EventName:ESS Qingdao 2014 +EventDB:event_20140429_ESSQingdao +EventStartTime:2014-04-28 16:01:00 +EventEndTime:2014-05-04 15:00:00 +EventType:a1b74ca0-fdd8-11dc-8811-005056c00008 + +RaceID:05f3fbf0-b310-0131-50e4-10b11c4ed8fd +RaceName:Race 4 +RaceStartTime:2014-05-01 06:59:59 +RaceEndTime: +RaceTrackingStartTime:2014-05-01 06:56:20 +RaceTrackingEndTime:2014-05-01 07:19:39 +RaceDefaultRouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd +RaceHandicapSystem:NONE + +Image1UUID:64a7b348-83db-11e2-88c0-10bf48d758ce +Image1Type:Logo +Image1Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif +Image1Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif + +Image2UUID:64c3a8fa-83db-11e2-88c0-10bf48d758ce +Image2Type:Logo +Image2Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif +Image2Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NZL.gif + +Image3UUID:651130a2-83db-11e2-88c0-10bf48d758ce +Image3Type:Logo +Image3Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/KOR.gif +Image3Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/KOR.gif + +Image4UUID:653c1ab0-83db-11e2-88c0-10bf48d758ce +Image4Type:Logo +Image4Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif +Image4Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif + +Image5UUID:65637da8-83db-11e2-88c0-10bf48d758ce +Image5Type:Logo +Image5Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif +Image5Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif + +Image6UUID:65875138-83db-11e2-88c0-10bf48d758ce +Image6Type:Logo +Image6Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/OMN.gif +Image6Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/OMN.gif + +Class1UUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Class1Name:Extreme40 + +Team1UUID:649f1dbe-83db-11e2-88c0-10bf48d758ce +Team1Name:TeamA +Team1Comp1UUID:648bd4f2-83db-11e2-88c0-10bf48d758ce + +Team2UUID:64bf0854-83db-11e2-88c0-10bf48d758ce +Team2Name:TeamB +Team2Comp1UUID:64bbff7e-83db-11e2-88c0-10bf48d758ce + +Team3UUID:64dd7988-83db-11e2-88c0-10bf48d758ce +Team3Name:TeamC +Team3Comp1UUID:64da7c24-83db-11e2-88c0-10bf48d758ce + +Team4UUID:64ffc402-83db-11e2-88c0-10bf48d758ce +Team4Name:TeamD + +Team5UUID:65351d82-83db-11e2-88c0-10bf48d758ce +Team5Name:TeamE +Team5Comp1UUID:65322500-83db-11e2-88c0-10bf48d758ce + +Team6UUID:655ed82a-83db-11e2-88c0-10bf48d758ce +Team6Name:TeamF +Team6Comp1UUID:655bd56c-83db-11e2-88c0-10bf48d758ce + +Team7UUID:65829fa8-83db-11e2-88c0-10bf48d758ce +Team7Name:TeamG +Team7Comp1UUID:657fa5a0-83db-11e2-88c0-10bf48d758ce + +Team8UUID:e4459a20-83db-11e2-88c0-10bf48d758ce +Team8Name:TeamH +Team8Comp1UUID:e442a414-83db-11e2-88c0-10bf48d758ce + +Team9UUID:a73aabc0-d6d6-0130-8be2-60a44ce903c3 +Team9Name:TeamI +Team9Comp1UUID:93085e80-7ad9-0131-e5b3-60a44ce903c3 + +Team10UUID:bbf81f60-d6d6-0130-8be3-60a44ce903c3 +Team10Name:TeamJ +Team10Comp1UUID:f097f4f0-7ad9-0131-e5b4-60a44ce903c3 + +Team11UUID:65659280-eda3-0130-c5c0-10b11c4ed8fd +Team11Name:TeamK +Team11Comp1UUID:19dc3eb0-7ada-0131-e5b5-60a44ce903c3 + +Team12UUID:9ee14560-77af-0131-e5af-60a44ce903c3 +Team12Name:TeamL +Team12Comp1UUID:5e15e7c0-7ada-0131-e5b6-60a44ce903c3 + +Team13UUID:b9677300-7ad7-0131-e5b0-60a44ce903c3 +Team13Name:TeamM +Team13Comp1UUID:84e68b70-7ada-0131-e5b7-60a44ce903c3 + +Team14UUID:c9689a00-7ad7-0131-e5b1-60a44ce903c3 +Team14Name:TeamN + +Team15UUID:d5712f30-7ad7-0131-e5b2-60a44ce903c3 +Team15Name:TeamO + +Comp1UUID:648bd4f2-83db-11e2-88c0-10bf48d758ce +Comp1Name:Alinghi +Comp1NameShort:Alinghi +Comp1Color:00FF00 +Comp1Nationality:SUI +Comp1Description: +Comp1Handicap:1.0000000000 +Comp1HandicapDistance:0.00000 +Comp1StartTime:2014-05-01 06:59:59 +Comp1PictureUUID:64a7b348-83db-11e2-88c0-10bf48d758ce +Comp1ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp1RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp2UUID:64bbff7e-83db-11e2-88c0-10bf48d758ce +Comp2Name:GAC Pindar +Comp2NameShort:GAC Pindar +Comp2Color:000080 +Comp2Nationality:AUS +Comp2Description: +Comp2Handicap:1.0000000000 +Comp2HandicapDistance:0.00000 +Comp2StartTime:2014-05-01 06:59:59 +Comp2PictureUUID:64c3a8fa-83db-11e2-88c0-10bf48d758ce +Comp2ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp2RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp3UUID:64da7c24-83db-11e2-88c0-10bf48d758ce +Comp3Name:Realteam by Realstone +Comp3NameShort:Realstone +Comp3Color:000000 +Comp3Nationality:AFG +Comp3Description: +Comp3Handicap:1.0000000000 +Comp3HandicapDistance:0.00000 +Comp3StartTime:2014-05-01 06:59:59 +Comp3PictureUUID:64a7b348-83db-11e2-88c0-10bf48d758ce +Comp3ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp3RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp4UUID:65322500-83db-11e2-88c0-10bf48d758ce +Comp4Name:Red Bull Sailing Team +Comp4NameShort:Red Bull +Comp4Color:008080 +Comp4Nationality:AUT +Comp4Description: +Comp4Handicap:1.0000000000 +Comp4HandicapDistance:0.00000 +Comp4StartTime:2014-05-01 06:59:59 +Comp4PictureUUID:653c1ab0-83db-11e2-88c0-10bf48d758ce +Comp4ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp4RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp5UUID:655bd56c-83db-11e2-88c0-10bf48d758ce +Comp5Name:SAP Extreme Sailing Team +Comp5NameShort:SAP +Comp5Color:FFFFFF +Comp5Nationality:DEN +Comp5Description: +Comp5Handicap:1.0000000000 +Comp5HandicapDistance:0.00000 +Comp5StartTime:2014-05-01 06:59:59 +Comp5PictureUUID:65637da8-83db-11e2-88c0-10bf48d758ce +Comp5ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp5RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp6UUID:657fa5a0-83db-11e2-88c0-10bf48d758ce +Comp6Name:The Wave, Muscat +Comp6NameShort:The Wave +Comp6Color:800080 +Comp6Nationality:OMA +Comp6Description: +Comp6Handicap:1.0000000000 +Comp6HandicapDistance:0.00000 +Comp6StartTime:2014-05-01 06:59:59 +Comp6PictureUUID:65875138-83db-11e2-88c0-10bf48d758ce +Comp6ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp6RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp7UUID:e442a414-83db-11e2-88c0-10bf48d758ce +Comp7Name:Invite +Comp7NameShort:Invite +Comp7Color:FFFF00 +Comp7Nationality:SIN +Comp7Description: +Comp7Handicap:1.0000000000 +Comp7HandicapDistance:0.00000 +Comp7StartTime:2014-05-01 06:59:59 +Comp7ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp7RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp8UUID:93085e80-7ad9-0131-e5b3-60a44ce903c3 +Comp8Name:Emirates Team New Zealand +Comp8NameShort:Emirates NZ +Comp8Color:CC9900 +Comp8Nationality:NZL +Comp8Description: +Comp8Handicap:1.0000000000 +Comp8HandicapDistance:0.00000 +Comp8StartTime:2014-05-01 06:59:59 +Comp8PictureUUID:64c3a8fa-83db-11e2-88c0-10bf48d758ce +Comp8ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp8RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp9UUID:f097f4f0-7ad9-0131-e5b4-60a44ce903c3 +Comp9Name:Gazprom Team Russia +Comp9NameShort:Team Russia +Comp9Color:999900 +Comp9Nationality:RUS +Comp9Description: +Comp9Handicap:1.0000000000 +Comp9HandicapDistance:0.00000 +Comp9StartTime:2014-05-01 06:59:59 +Comp9ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp9RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp10UUID:19dc3eb0-7ada-0131-e5b5-60a44ce903c3 +Comp10Name:Groupama sailing team +Comp10NameShort:Groupama +Comp10Color:33CC00 +Comp10Nationality:AFG +Comp10Description: +Comp10Handicap:1.0000000000 +Comp10HandicapDistance:0.00000 +Comp10StartTime:2014-05-01 06:59:59 +Comp10ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp10RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp11UUID:5e15e7c0-7ada-0131-e5b6-60a44ce903c3 +Comp11Name:J.P. Morgan BAR +Comp11NameShort:J.P. Morgan +Comp11Color:CCFF00 +Comp11Nationality:GBR +Comp11Description: +Comp11Handicap:1.0000000000 +Comp11HandicapDistance:0.00000 +Comp11StartTime:2014-05-01 06:59:59 +Comp11ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp11RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +Comp12UUID:84e68b70-7ada-0131-e5b7-60a44ce903c3 +Comp12Name:Oman Air +Comp12NameShort:Oman Air +Comp12Color:CCFF99 +Comp12Nationality:OMA +Comp12Description: +Comp12Handicap:1.0000000000 +Comp12HandicapDistance:0.00000 +Comp12StartTime:2014-05-01 06:59:59 +Comp12PictureUUID:65875138-83db-11e2-88c0-10bf48d758ce +Comp12ClassUUID:648ee25a-83db-11e2-88c0-10bf48d758ce +Comp12RouteUUID:05ee3850-b310-0131-50e3-10b11c4ed8fd + +ControlPoint1UUID:1100ea60-7adb-0131-e5b8-60a44ce903c3 +ControlPoint1Name:Red-Green +ControlPoint1HasTwoPoints:1 +ControlPoint1ShortName:RG +ControlPoint1DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Red###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Red###BREAKLINE###P1.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Green###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Green###BREAKLINE###P2.UUID=785c3e44-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint1Mark1Lon:120.386452 +ControlPoint1Mark1Lat:36.046283 +ControlPoint1Mark2Lon:120.386955 +ControlPoint1Mark2Lat:36.046803 + +ControlPoint2UUID:1286f15e-83be-11e2-88c0-10bf48d758ce +ControlPoint2Name:Blue +ControlPoint2HasTwoPoints:0 +ControlPoint2ShortName:Bl +ControlPoint2DataSheet:Name=Blue +ControlPoint2Mark1Lon:120.380233 +ControlPoint2Mark1Lat:36.049133 + +ControlPoint3UUID:1a94c6a0-7adc-0131-e5f4-60a44ce903c3 +ControlPoint3Name:Black Conical-Blue +ControlPoint3HasTwoPoints:1 +ControlPoint3ShortName:BB +ControlPoint3DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Conical###BREAKLINE###P1.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Blue###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Blue###BREAKLINE###P2.UUID=1286f15e-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint3Mark1Lon:120.380500 +ControlPoint3Mark1Lat:36.049228 +ControlPoint3Mark2Lon:120.380233 +ControlPoint3Mark2Lat:36.049133 + +ControlPoint4UUID:2dc7d9d0-7b84-0131-b9c3-10b11c4ed8fd +ControlPoint4Name:Black Cylinder - Black Conical +ControlPoint4HasTwoPoints:1 +ControlPoint4ShortName:BB +ControlPoint4DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Cylinder###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Cylinder###BREAKLINE###P1.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Conical###BREAKLINE###P2.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce +ControlPoint4Mark1Lon:120.380302 +ControlPoint4Mark1Lat:36.048843 +ControlPoint4Mark2Lon:120.380500 +ControlPoint4Mark2Lat:36.049228 + +ControlPoint5UUID:2e37b410-7adc-0131-e5fe-60a44ce903c3 +ControlPoint5Name:Black Conical-Yellow +ControlPoint5HasTwoPoints:1 +ControlPoint5ShortName:BY +ControlPoint5DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Conical###BREAKLINE###P1.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint5Mark1Lon:120.380500 +ControlPoint5Mark1Lat:36.049228 +ControlPoint5Mark2Lon:120.386192 +ControlPoint5Mark2Lat:36.046997 + +ControlPoint6UUID:3240586c-83c0-11e2-88c0-10bf48d758ce +ControlPoint6Name:Finish Pole +ControlPoint6HasTwoPoints:0 +ControlPoint6ShortName:F +ControlPoint6DataSheet:Color=Black###BREAKLINE###Type=Landmark###BREAKLINE###Name=Finish Pole +ControlPoint6Mark1Lon:58.304032 +ControlPoint6Mark1Lat:23.615582 + +ControlPoint7UUID:37cb36e0-7adb-0131-e5c2-60a44ce903c3 +ControlPoint7Name:Red-Blue +ControlPoint7HasTwoPoints:1 +ControlPoint7ShortName:RB +ControlPoint7DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Red###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Red###BREAKLINE###P1.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Blue###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Blue###BREAKLINE###P2.UUID=1286f15e-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint7Mark1Lon:120.386452 +ControlPoint7Mark1Lat:36.046283 +ControlPoint7Mark2Lon:120.380233 +ControlPoint7Mark2Lat:36.049133 + +ControlPoint8UUID:3e6c1c94-83bf-11e2-88c0-10bf48d758ce +ControlPoint8Name:Race Committee +ControlPoint8HasTwoPoints:0 +ControlPoint8ShortName:RC +ControlPoint8DataSheet:Type=StartBoat###BREAKLINE###Name=Race Committee +ControlPoint8Mark1Lon:120.380227 +ControlPoint8Mark1Lat:36.047118 + +ControlPoint9UUID:3fdd2402-83be-11e2-88c0-10bf48d758ce +ControlPoint9Name:Yellow +ControlPoint9HasTwoPoints:0 +ControlPoint9ShortName:Y +ControlPoint9DataSheet:Type=Buoy###BREAKLINE###Color=Yellow###BREAKLINE###Shape=Conical###BREAKLINE###Name=Yellow +ControlPoint9Mark1Lon:120.386192 +ControlPoint9Mark1Lat:36.046997 + +ControlPoint10UUID:4c304720-7adc-0131-e608-60a44ce903c3 +ControlPoint10Name:Black Conical-Red +ControlPoint10HasTwoPoints:1 +ControlPoint10ShortName:BR +ControlPoint10DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Conical###BREAKLINE###P1.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Red###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Red###BREAKLINE###P2.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint10Mark1Lon:120.380500 +ControlPoint10Mark1Lat:36.049228 +ControlPoint10Mark2Lon:120.386452 +ControlPoint10Mark2Lat:36.046283 + +ControlPoint11UUID:4e0c2a90-7adb-0131-e5cc-60a44ce903c3 +ControlPoint11Name:Red-Yellow +ControlPoint11HasTwoPoints:1 +ControlPoint11ShortName:RY +ControlPoint11DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Red###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Red###BREAKLINE###P1.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint11Mark1Lon:120.386452 +ControlPoint11Mark1Lat:36.046283 +ControlPoint11Mark2Lon:120.386192 +ControlPoint11Mark2Lat:36.046997 + +ControlPoint12UUID:5427c7d2-83be-11e2-88c0-10bf48d758ce +ControlPoint12Name:Red +ControlPoint12HasTwoPoints:0 +ControlPoint12ShortName:R +ControlPoint12DataSheet:Type=Buoy###BREAKLINE###Color=Red###BREAKLINE###Shape=Conical###BREAKLINE###Name=Red +ControlPoint12Mark1Lon:120.386452 +ControlPoint12Mark1Lat:36.046283 + +ControlPoint13UUID:584cfe30-b328-0131-5246-10b11c4ed8fd +ControlPoint13Name:R2-Green +ControlPoint13HasTwoPoints:0 +ControlPoint13ShortName:G +ControlPoint13Mark1Lon:120.386972 +ControlPoint13Mark1Lat:36.046795 + +ControlPoint14UUID:621831d0-7adc-0131-e612-60a44ce903c3 +ControlPoint14Name:Black Conical-Green +ControlPoint14HasTwoPoints:1 +ControlPoint14ShortName:BG +ControlPoint14DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Conical###BREAKLINE###P1.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Green###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Green###BREAKLINE###P2.UUID=785c3e44-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint14Mark1Lon:120.380500 +ControlPoint14Mark1Lat:36.049228 +ControlPoint14Mark2Lon:120.386955 +ControlPoint14Mark2Lat:36.046803 + +ControlPoint15UUID:77b9e620-7adb-0131-e5d6-60a44ce903c3 +ControlPoint15Name:Green-Blue +ControlPoint15HasTwoPoints:1 +ControlPoint15ShortName:GB +ControlPoint15DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Green###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Green###BREAKLINE###P1.UUID=785c3e44-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Blue###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Blue###BREAKLINE###P2.UUID=1286f15e-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint15Mark1Lon:120.386955 +ControlPoint15Mark1Lat:36.046803 +ControlPoint15Mark2Lon:120.380233 +ControlPoint15Mark2Lat:36.049133 + +ControlPoint16UUID:785c3e44-83be-11e2-88c0-10bf48d758ce +ControlPoint16Name:Green +ControlPoint16HasTwoPoints:0 +ControlPoint16ShortName:G +ControlPoint16DataSheet:Type=Buoy###BREAKLINE###Color=Green###BREAKLINE###Shape=Conical###BREAKLINE###Name=Green +ControlPoint16Mark1Lon:120.386955 +ControlPoint16Mark1Lat:36.046803 + +ControlPoint17UUID:7e1b7a80-7d0b-0131-a109-10b11c4ed8fd +ControlPoint17Name:Black Gate +ControlPoint17HasTwoPoints:1 +ControlPoint17ShortName:S +ControlPoint17DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Cylinder###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Cylinder###BREAKLINE###P1.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Conical###BREAKLINE###P2.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint17Mark1Lon:103.855782 +ControlPoint17Mark1Lat:1.282103 +ControlPoint17Mark2Lon:103.857560 +ControlPoint17Mark2Lat:1.286382 + +ControlPoint18UUID:88ef6d40-7adc-0131-e61c-60a44ce903c3 +ControlPoint18Name:Finish Pole-Cylinder +ControlPoint18HasTwoPoints:1 +ControlPoint18ShortName:F +ControlPoint18DataSheet:P1.Color=Black###BREAKLINE###P1.Type=Landmark###BREAKLINE###P1.Name=Finish Pole###BREAKLINE###P1.UUID=3240586c-83c0-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Cylinder###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Cylinder###BREAKLINE###P2.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint18Mark1Lon:58.304032 +ControlPoint18Mark1Lat:23.615582 +ControlPoint18Mark2Lon:120.380302 +ControlPoint18Mark2Lat:36.048843 + +ControlPoint19UUID:8f9a4ee0-7adb-0131-e5e0-60a44ce903c3 +ControlPoint19Name:Green-Yellow +ControlPoint19HasTwoPoints:1 +ControlPoint19ShortName:GY +ControlPoint19DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Green###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Green###BREAKLINE###P1.UUID=785c3e44-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint19Mark1Lon:120.386955 +ControlPoint19Mark1Lat:36.046803 +ControlPoint19Mark2Lon:120.386192 +ControlPoint19Mark2Lat:36.046997 + +ControlPoint20UUID:a0f71190-7adb-0131-e5ea-60a44ce903c3 +ControlPoint20Name:Blue-Yellow +ControlPoint20HasTwoPoints:1 +ControlPoint20ShortName:BY +ControlPoint20DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Blue###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=Blue###BREAKLINE###P1.UUID=1286f15e-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint20Mark1Lon:120.380233 +ControlPoint20Mark1Lat:36.049133 +ControlPoint20Mark2Lon:120.386192 +ControlPoint20Mark2Lat:36.046997 + +ControlPoint21UUID:a68c6350-7adc-0131-e626-60a44ce903c3 +ControlPoint21Name:RC-Black Conical +ControlPoint21HasTwoPoints:1 +ControlPoint21ShortName:F +ControlPoint21DataSheet:P1.Type=StartBoat###BREAKLINE###P1.Name=Race Committee###BREAKLINE###P1.UUID=3e6c1c94-83bf-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Conical###BREAKLINE###P2.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint21Mark1Lon:120.380227 +ControlPoint21Mark1Lat:36.047118 +ControlPoint21Mark2Lon:120.380500 +ControlPoint21Mark2Lat:36.049228 + +ControlPoint22UUID:b153dfd6-83be-11e2-88c0-10bf48d758ce +ControlPoint22Name:Black Cylinder +ControlPoint22HasTwoPoints:0 +ControlPoint22ShortName:B +ControlPoint22DataSheet:Type=Buoy###BREAKLINE###Color=Black###BREAKLINE###Shape=Cylinder###BREAKLINE###Pattern=Checkered###BREAKLINE###Name=Black Cylinder +ControlPoint22Mark1Lon:120.380302 +ControlPoint22Mark1Lat:36.048843 + +ControlPoint23UUID:b62e0bf0-7adc-0131-e630-60a44ce903c3 +ControlPoint23Name:RC-Black Cylinder +ControlPoint23HasTwoPoints:1 +ControlPoint23ShortName:S +ControlPoint23DataSheet:P1.Type=StartBoat###BREAKLINE###P1.Name=Race Committee###BREAKLINE###P1.UUID=3e6c1c94-83bf-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Cylinder###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Cylinder###BREAKLINE###P2.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint23Mark1Lon:120.380227 +ControlPoint23Mark1Lat:36.047118 +ControlPoint23Mark2Lon:120.380302 +ControlPoint23Mark2Lat:36.048843 + +ControlPoint24UUID:bd1fc330-7c10-0131-9c24-10b11c4ed8fd +ControlPoint24Name:Black Cylinder-Yellow +ControlPoint24HasTwoPoints:1 +ControlPoint24ShortName:S +ControlPoint24DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=Black###BREAKLINE###P1.Shape=Cylinder###BREAKLINE###P1.Pattern=Checkered###BREAKLINE###P1.Name=Black Cylinder###BREAKLINE###P1.UUID=b153dfd6-83be-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Yellow###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Yellow###BREAKLINE###P2.UUID=3fdd2402-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint24Mark1Lon:120.380302 +ControlPoint24Mark1Lat:36.048843 +ControlPoint24Mark2Lon:120.386192 +ControlPoint24Mark2Lat:36.046997 + +ControlPoint25UUID:bf6777e6-83bd-11e2-88c0-10bf48d758ce +ControlPoint25Name:White S +ControlPoint25HasTwoPoints:0 +ControlPoint25ShortName:W +ControlPoint25DataSheet:Type=Buoy###BREAKLINE###Color=White###BREAKLINE###Shape=Conical###BREAKLINE###Name=White S +ControlPoint25Mark1Lon:120.381432 +ControlPoint25Mark1Lat:36.047478 + +ControlPoint26UUID:cd3c1dc0-7c11-0131-9c2e-10b11c4ed8fd +ControlPoint26Name:Finish Pole-Conical +ControlPoint26HasTwoPoints:1 +ControlPoint26ShortName:S +ControlPoint26DataSheet:P1.Color=Black###BREAKLINE###P1.Type=Landmark###BREAKLINE###P1.Name=Finish Pole###BREAKLINE###P1.UUID=3240586c-83c0-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Black###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Pattern=Checkered###BREAKLINE###P2.Name=Black Conical###BREAKLINE###P2.UUID=d78695ea-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint26Mark1Lon:58.304032 +ControlPoint26Mark1Lat:23.615582 +ControlPoint26Mark2Lon:120.380500 +ControlPoint26Mark2Lat:36.049228 + +ControlPoint27UUID:d39a0d00-7adc-0131-e63a-60a44ce903c3 +ControlPoint27Name:White Gate +ControlPoint27HasTwoPoints:1 +ControlPoint27ShortName:W +ControlPoint27DataSheet:P1.Type=Buoy###BREAKLINE###P1.Color=White###BREAKLINE###P1.Shape=Conical###BREAKLINE###P1.Name=White S###BREAKLINE###P1.UUID=bf6777e6-83bd-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=White###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=White P###BREAKLINE###P2.UUID=e9ec42f8-83bd-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint27Mark1Lon:120.381432 +ControlPoint27Mark1Lat:36.047478 +ControlPoint27Mark2Lon:120.381187 +ControlPoint27Mark2Lat:36.048325 + +ControlPoint28UUID:d78695ea-83be-11e2-88c0-10bf48d758ce +ControlPoint28Name:Black Conical +ControlPoint28HasTwoPoints:0 +ControlPoint28ShortName:B +ControlPoint28DataSheet:Type=Buoy###BREAKLINE###Color=Black###BREAKLINE###Shape=Conical###BREAKLINE###Pattern=Checkered###BREAKLINE###Name=Black Conical +ControlPoint28Mark1Lon:120.380500 +ControlPoint28Mark1Lat:36.049228 + +ControlPoint29UUID:e9ec42f8-83bd-11e2-88c0-10bf48d758ce +ControlPoint29Name:White P +ControlPoint29HasTwoPoints:0 +ControlPoint29ShortName:W +ControlPoint29DataSheet:Type=Buoy###BREAKLINE###Color=White###BREAKLINE###Shape=Conical###BREAKLINE###Name=White P +ControlPoint29Mark1Lon:120.381187 +ControlPoint29Mark1Lat:36.048325 + +ControlPoint30UUID:eff23330-7b84-0131-b9d2-10b11c4ed8fd +ControlPoint30Name:RC-Red +ControlPoint30HasTwoPoints:1 +ControlPoint30ShortName:S +ControlPoint30DataSheet:P1.Type=StartBoat###BREAKLINE###P1.Name=Race Committee###BREAKLINE###P1.UUID=3e6c1c94-83bf-11e2-88c0-10bf48d758ce###BREAKLINE###P2.Type=Buoy###BREAKLINE###P2.Color=Red###BREAKLINE###P2.Shape=Conical###BREAKLINE###P2.Name=Red###BREAKLINE###P2.UUID=5427c7d2-83be-11e2-88c0-10bf48d758ce###BREAKLINE### +ControlPoint30Mark1Lon:120.380227 +ControlPoint30Mark1Lat:36.047118 +ControlPoint30Mark2Lon:120.386452 +ControlPoint30Mark2Lat:36.046283 + + +SegmentFile:segments.php +Route1UUID:05ee3850-b310-0131-50e3-10b11c4ed8fd +Route1Description:Race 4 +Route1Length:0 +Route1DataSheet:Seq.1=Line###BREAKLINE###Seq.2=Starboard###BREAKLINE###Seq.3=Gate###BREAKLINE###Seq.4=Starboard###BREAKLINE###Seq.5=Line###BREAKLINE### +Route1ControlPoint1UUID:b62e0bf0-7adc-0131-e630-60a44ce903c3 +Route1ControlPoint2UUID:785c3e44-83be-11e2-88c0-10bf48d758ce +Route1ControlPoint3UUID:d39a0d00-7adc-0131-e63a-60a44ce903c3 +Route1ControlPoint4UUID:3fdd2402-83be-11e2-88c0-10bf48d758ce +Route1ControlPoint5UUID:a68c6350-7adc-0131-e626-60a44ce903c3 + +//-------- AUTO GENERATED PARAMETERS END ------------------ diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaTrackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaTrackTest.java index 75cd78a4515..0ea678cf1bb 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaTrackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaTrackTest.java @@ -1,13 +1,14 @@ package com.sap.sailing.domain.common; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.Date; import org.junit.Test; import com.sap.sailing.domain.common.media.MediaTrack; -import static org.junit.Assert.*; - public class MediaTrackTest { @Test diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaUtilTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaUtilTest.java index 256b4809925..b5f4d581dd4 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaUtilTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaUtilTest.java @@ -1,14 +1,14 @@ package com.sap.sailing.domain.common; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.Date; import org.junit.Test; import com.sap.sailing.domain.common.media.MediaUtil; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - public class MediaUtilTest { @Test diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/BeaufortTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/BeaufortTest.java index ccf01878328..cb54b39a073 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/BeaufortTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/BeaufortTest.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.common.test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java index b1feeddb4e0..ba5e9886d66 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java @@ -34,10 +34,9 @@ import com.sap.sailing.domain.common.dto.LegEntryDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.test.StoredTrackBasedTest; import com.sap.sailing.util.ClonerImpl; +import com.sap.sse.common.Util; /** * Tests the compressing / de-compressing functionality of {@link LeaderboardDTO} and {@link IncrementalLeaderboardDTO}. @@ -145,18 +144,18 @@ public class LeaderboardDTODiffingTest { } newVersion.rows = newRows; Map rowsBeforeStripping = newVersion.rows; - Map, List> previousLegDetailsBeforeStripping = new HashMap<>(); + Map, List> previousLegDetailsBeforeStripping = new HashMap<>(); for (Map.Entry e : rowsBeforeStripping.entrySet()) { for (Map.Entry e2 : e.getValue().fieldsByRaceColumnName.entrySet()) { if (e2.getValue().legDetails != null) { - previousLegDetailsBeforeStripping.put(new Pair(e.getKey(), e2.getKey()), + previousLegDetailsBeforeStripping.put(new com.sap.sse.common.Util.Pair(e.getKey(), e2.getKey()), new ArrayList<>(e2.getValue().legDetails)); } } } newVersion.strip(previousVersion); // see bug 1455; check that the stripping doesn't kill the previous leaderboard's legDetails lists - for (Map.Entry, List> e : previousLegDetailsBeforeStripping.entrySet()) { + for (Map.Entry, List> e : previousLegDetailsBeforeStripping.entrySet()) { final LeaderboardRowDTO leaderboardRowDTO = rowsBeforeStripping.get(e.getKey().getA()); if (leaderboardRowDTO != null) { List newLegDetails = leaderboardRowDTO.fieldsByRaceColumnName.get(e.getKey().getB()).legDetails; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/NaturalComparatorTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/NaturalComparatorTest.java index a3dd0421262..3e23aae89d9 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/NaturalComparatorTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/NaturalComparatorTest.java @@ -3,6 +3,10 @@ package com.sap.sailing.domain.common.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import org.junit.Before; import org.junit.Test; @@ -20,10 +24,18 @@ public class NaturalComparatorTest { } @Test - public void testCaseInsesitive(){ + public void testCaseInsensitive(){ assertEquals(0, caseInsensitive.compare("E", "e")); } + @Test + public void testCaseInsensitiveIsDifferent(){ + assertEquals(-1, caseInsensitive.compare("E", "F")); + assertEquals(-1, caseInsensitive.compare("E", "f")); + assertEquals(1, caseInsensitive.compare("F", "E")); + assertEquals(1, caseInsensitive.compare("f", "E")); + } + @Test public void testEmpty() { assertTrue(comparator.compare("", "a") < 0); @@ -103,5 +115,12 @@ public class NaturalComparatorTest { assertTrue(comparator.compare("0.01", "0.2") < 0); assertTrue(comparator.compare("0.1.5b", "0.2a") < 0); } + + @Test + public void testSortSailIDs() { + List list = Arrays.asList("Groupama", "GER8829", "A", "FRA 56893", "Cam", "GAC Pindar", "Grün", "a/m", "d/n"); + Collections.sort(list, new NaturalComparator(/* case sensitive */ false)); + assertTrue(list.indexOf("A") < list.indexOf("Cam")); + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/TimePointTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/TimePointTest.java new file mode 100644 index 00000000000..345f922c499 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/TimePointTest.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.common.test; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; + +public class TimePointTest { + @Test + public void compare() { + TimePoint one = new MillisecondsTimePoint(Long.MIN_VALUE); + TimePoint two = new MillisecondsTimePoint(Long.MAX_VALUE); + assertTrue(one.before(two)); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/TimeRangeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/TimeRangeTest.java new file mode 100644 index 00000000000..d28decf1b2c --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/TimeRangeTest.java @@ -0,0 +1,132 @@ +package com.sap.sailing.domain.common.test; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import junit.framework.Assert; + +import org.junit.Test; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimeRange; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.TimeRangeImpl; + +public class TimeRangeTest { + protected TimeRange create(long from, long to) { + return new TimeRangeImpl(new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)); + } + + protected TimePoint create(long millis) { + return new MillisecondsTimePoint(millis); + } + + private void liesWithin(long fromOuter, long toOuter, long fromInner, long toInner) { + TimeRange outer = create(fromOuter, toOuter); + TimeRange inner = create(fromInner, toInner); + + Assert.assertTrue(inner.liesWithin(outer)); + Assert.assertTrue(outer.includes(inner)); + Assert.assertFalse(inner.includes(outer)); + Assert.assertFalse(outer.liesWithin(inner)); + } + + @Test + public void liesWithin() { + liesWithin(0, 100, 10, 90); + liesWithin(0, 100, 0, 90); + liesWithin(0, 100, 10, 100); + } + + @Test + public void includesTimePoint() { + TimeRange range = create(0, 100); + Assert.assertTrue(range.includes(create(0))); + Assert.assertTrue(range.includes(create(1))); + Assert.assertTrue(range.includes(create(100))); + Assert.assertFalse(range.includes(create(101))); + } + + @Test + public void intersects() { + TimeRange one = create(0, 50); + TimeRange two = create(20, 70); + + Assert.assertFalse(one.liesWithin(two)); + Assert.assertFalse(two.liesWithin(one)); + Assert.assertTrue(one.intersects(two)); + Assert.assertTrue(two.intersects(one)); + } + + @Test + public void openRanges() { + TimeRange one = new TimeRangeImpl(new MillisecondsTimePoint(0), null); + TimeRange two = create(5, 10); + TimeRange three = new TimeRangeImpl(null, new MillisecondsTimePoint(15)); + + assertTrue(two.liesWithin(one)); + assertTrue(two.liesWithin(three)); + assertTrue(one.intersects(three)); + assertTrue(one.endsAfter(three)); + assertFalse(one.startsBefore(three)); + } + + @Test + public void union() { + TimeRange one = create(5, 10); + TimeRange two = create(7, 12); + + TimeRange union = one.union(two); + assertEquals(5, union.from().asMillis()); + assertEquals(12, union.to().asMillis()); + + two = create(11, 16); + assertNull(one.union(two)); + + two = create(7, Long.MAX_VALUE); + union = two.union(one); + TimeRange union2 = one.union(two); + assertEquals(5, union.from().asMillis()); + assertEquals(5, union2.from().asMillis()); + assertEquals(Long.MAX_VALUE, union.to().asMillis()); + assertEquals(Long.MAX_VALUE, union2.to().asMillis()); + + one = create(Long.MIN_VALUE, 10); + union = two.union(one); + assertTrue(union.openBeginning()); + assertTrue(union.openEnd()); + } + + @Test + public void intersection() { + TimeRange one = create(5, 10); + TimeRange two = create(7, 12); + + TimeRange intersection = one.intersection(two); + assertEquals(7, intersection.from().asMillis()); + assertEquals(10, intersection.to().asMillis()); + + two = create(11, 16); + assertNull(one.intersection(two)); + + two = create(7, Long.MAX_VALUE); + intersection = two.intersection(one); + assertEquals(7, intersection.from().asMillis()); + assertEquals(10, intersection.to().asMillis()); + + one = create(0, Long.MAX_VALUE); + two = create(Long.MIN_VALUE, 10); + intersection = two.intersection(one); + assertEquals(0, intersection.from().asMillis()); + assertEquals(10, intersection.to().asMillis()); + } + + @Test + public void timeDifferenceTest() { + TimeRange one = create(5, 10); + assertEquals(2, one.timeDifference(new MillisecondsTimePoint(3)).asMillis()); + assertEquals(0, one.timeDifference(new MillisecondsTimePoint(7)).asMillis()); + assertEquals(2, one.timeDifference(new MillisecondsTimePoint(12)).asMillis()); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/UtilTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/UtilTest.java index 05f1bcedc31..4b0b0e722b7 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/UtilTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/UtilTest.java @@ -10,7 +10,7 @@ import java.util.List; import org.junit.Before; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sse.common.Util; public class UtilTest { private List l; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractManeuverDetectionTestCase.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractManeuverDetectionTestCase.java index 3490ba807a1..594cc9108d2 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractManeuverDetectionTestCase.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractManeuverDetectionTestCase.java @@ -38,20 +38,20 @@ public abstract class AbstractManeuverDetectionTestCase extends OnlineTracTracBa * The type of maneuver that should have happened to the given time point. * @param maneuverTimePoint * The time point the maneuver type should have happened. - * @param tolerance + * @param toleranceInMillis * The tolerance of time, the maneuver should have happened in milliseconds. */ protected void assertManeuver(List maneuverList, ManeuverType maneuverType, - TimePoint maneuverTimePoint, int tolerance) { + TimePoint maneuverTimePoint, int toleranceInMillis) { for (Maneuver maneuver : maneuverList) { assertNotNull(maneuver.getTimePoint()); if (maneuver.getType() == maneuverType - && Math.abs(maneuver.getTimePoint().asMillis() - maneuverTimePoint.asMillis()) <= tolerance) { + && Math.abs(maneuver.getTimePoint().asMillis() - maneuverTimePoint.asMillis()) <= toleranceInMillis) { maneuversInvalid.remove(maneuver); return; } } - fail("Didn't find maneuver type " + maneuverType + " in " + tolerance + "ms around " + maneuverTimePoint); + fail("Didn't find maneuver type " + maneuverType + " in " + toleranceInMillis + "ms around " + maneuverTimePoint); } /** diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java index 1b23c69fa1b..71bd4100723 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java @@ -24,11 +24,11 @@ import org.junit.rules.Timeout; import com.maptrack.client.io.TypeController; import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tractracadapter.Receiver; import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; import com.sap.sailing.domain.tractracadapter.impl.ControlPointAdapter; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.ControlPoint; import com.tractrac.clientmodule.Event; import com.tractrac.clientmodule.data.DataController; @@ -205,10 +205,10 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest impl System.err.println("Error with live data "+arg0); } - public static Iterable> getTracTracControlPointsWithPassingInstructions(Iterable controlPoints) { - List> ttControlPoints = new ArrayList>(); + public static Iterable> getTracTracControlPointsWithPassingInstructions(Iterable controlPoints) { + List> ttControlPoints = new ArrayList>(); for (com.tractrac.clientmodule.ControlPoint cp : controlPoints) { - ttControlPoints.add(new Pair(new ControlPointAdapter(cp), null)); + ttControlPoints.add(new Util.Pair(new ControlPointAdapter(cp), null)); } return ttControlPoints; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BearingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BearingTest.java index 396a14828b3..06c3d47cdbd 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BearingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BearingTest.java @@ -12,12 +12,12 @@ import org.junit.Test; import com.sap.sailing.domain.base.BearingWithConfidence; import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; import com.sap.sailing.domain.base.impl.PositionWithConfidenceImpl; -import com.sap.sailing.domain.base.impl.ScalablePosition; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.RadianBearingImpl; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; import com.sap.sailing.domain.confidence.ConfidenceFactory; import com.sap.sailing.domain.confidence.HasConfidence; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java new file mode 100755 index 00000000000..a719b7c4496 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java @@ -0,0 +1,210 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.CompetitorChangeListener; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Nationality; +import com.sap.sailing.domain.base.Team; +import com.sap.sailing.domain.base.WithNationality; +import com.sap.sailing.domain.base.impl.DomainFactoryImpl; +import com.sap.sailing.domain.base.impl.DynamicCompetitor; +import com.sap.sailing.domain.common.Color; +import com.sap.sailing.domain.common.impl.RGBColor; + +/** + * Tests the {@link CompetitorChangeListener} stuff including the serialization of the transitive listener + * pattern on the {@link Team} structure. + * + * @author Axel Uhl (D043530) + * + */ +public class CompetitorListenerTest extends AbstractSerializationTest { + private CompetitorChangeListener listener; + private boolean nationalityChanged; + private Object whatChangedNationality; + private Object oldNationality; + private Object newNationality; + private boolean sailIdChanged; + private String oldSailId; + private String newSailId; + private boolean nameChanged; + private String oldName; + private String newName; + private boolean colorChanged; + private Color oldColor; + private Color newColor; + private DynamicCompetitor competitor; + private DomainFactoryImpl baseDomainFactory; + + @Before + public void setUp() { + baseDomainFactory = new DomainFactoryImpl(); + competitor = TrackBasedTest.createCompetitor("Hasso"); + nationalityChanged = false; + whatChangedNationality = null; + oldNationality = null; + newNationality = null; + sailIdChanged = false; + oldSailId = null; + newSailId = null; + nameChanged = false; + oldName = null; + newName = null; + colorChanged = false; + oldColor = null; + newColor = null; + listener = new CompetitorChangeListener() { + private static final long serialVersionUID = 4581029778988240209L; + + @Override + public void nationalityChanged(WithNationality what, Nationality oldNationality, Nationality newNationality) { + nationalityChanged = true; + whatChangedNationality = what; + CompetitorListenerTest.this.oldNationality = oldNationality; + CompetitorListenerTest.this.newNationality = newNationality; + } + + @Override + public void sailIdChanged(String oldSailId, String newSailId) { + sailIdChanged = true; + CompetitorListenerTest.this.oldSailId = oldSailId; + CompetitorListenerTest.this.newSailId = newSailId; + } + + @Override + public void nameChanged(String oldName, String newName) { + nameChanged = true; + CompetitorListenerTest.this.oldName = oldName; + CompetitorListenerTest.this.newName = newName; + } + + @Override + public void colorChanged(Color oldColor, Color newColor) { + colorChanged = true; + CompetitorListenerTest.this.oldColor = oldColor; + CompetitorListenerTest.this.newColor = newColor; + } + }; + competitor.addCompetitorChangeListener(listener); + } + + @Test + public void testNullNationality() { + competitor.getTeam().setNationality(null); + assertNull(competitor.getTeam().getNationality()); + competitor.getTeam().setNationality(null); + assertNull(competitor.getTeam().getNationality()); + competitor.getTeam().setNationality(baseDomainFactory.getOrCreateNationality("GER")); + assertEquals(baseDomainFactory.getOrCreateNationality("GER"), competitor.getTeam().getNationality()); + competitor.getTeam().setNationality(null); + assertNull(competitor.getTeam().getNationality()); + } + + @Test + public void testSimpleCompetitorListenerPatternForName() { + competitor.setName("Dr. Hasso Plattner"); + assertTrue(nameChanged); + assertEquals("Hasso", oldName); + assertEquals("Dr. Hasso Plattner", newName); + assertFalse(sailIdChanged); + assertFalse(colorChanged); + assertFalse(nationalityChanged); + } + + @Test + public void testSimpleCompetitorListenerPatternForSailId() { + final String myOldSailId = competitor.getBoat().getSailID(); + competitor.getBoat().setSailId("POR 40"); + assertFalse(nameChanged); + assertTrue(sailIdChanged); + assertEquals(myOldSailId, oldSailId); + assertEquals("POR 40", newSailId); + assertFalse(colorChanged); + assertFalse(nationalityChanged); + } + + @Test + public void testSimpleCompetitorListenerPatternForColor() { + final Color myOldColor = competitor.getColor(); + final RGBColor myNewColor = new RGBColor(123, 12, 234); + competitor.setColor(myNewColor); + assertFalse(nameChanged); + assertFalse(sailIdChanged); + assertTrue(colorChanged); + assertEquals(myOldColor, oldColor); + assertEquals(myNewColor, newColor); + assertFalse(nationalityChanged); + } + + @Test + public void testSimpleCompetitorListenerPatternForNationality() { + final Nationality myOldNationality = competitor.getTeam().getNationality(); + final Nationality myNewNationality = DomainFactory.INSTANCE.getOrCreateNationality("POR"); + competitor.getTeam().setNationality(myNewNationality); + assertFalse(nameChanged); + assertFalse(sailIdChanged); + assertFalse(colorChanged); + assertTrue(nationalityChanged); + assertEquals(myOldNationality, oldNationality); + assertEquals(myNewNationality, newNationality); + assertSame(competitor.getTeam(), whatChangedNationality); + } + + @Test + public void testCompetitorSerializationLosesAllExternalListeners() throws ClassNotFoundException, IOException { + DomainFactory baseDomainFactory = new DomainFactoryImpl(); + DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory); + clonedCompetitor.setName("Dr. Hasso Plattner"); + clonedCompetitor.getBoat().setSailId("POR 40"); + final RGBColor myNewColor = new RGBColor(123, 12, 234); + clonedCompetitor.setColor(myNewColor); + final Nationality myNewNationality = baseDomainFactory.getOrCreateNationality("POR"); + clonedCompetitor.getTeam().setNationality(myNewNationality); + assertFalse(nameChanged); + assertFalse(sailIdChanged); + assertFalse(colorChanged); + assertFalse(nationalityChanged); + } + + @Test + public void testCompetitorSerializationListenerPatternForNameOnSerializedCompetitor() throws ClassNotFoundException, IOException { + DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory); + competitor = clonedCompetitor; + competitor.addCompetitorChangeListener(listener); + testSimpleCompetitorListenerPatternForName(); + } + + @Test + public void testCompetitorSerializationListenerPatternForColorOnSerializedCompetitor() throws ClassNotFoundException, IOException { + DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory); + competitor = clonedCompetitor; + competitor.addCompetitorChangeListener(listener); + testSimpleCompetitorListenerPatternForColor(); + } + + @Test + public void testCompetitorSerializationListenerPatternForSailIdOnSerializedCompetitor() throws ClassNotFoundException, IOException { + DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory); + competitor = clonedCompetitor; + competitor.addCompetitorChangeListener(listener); + testSimpleCompetitorListenerPatternForSailId(); + } + + @Test + public void testCompetitorSerializationListenerPatternForNationalityOnSerializedCompetitor() throws ClassNotFoundException, IOException { + DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory); + competitor = clonedCompetitor; + competitor.addCompetitorChangeListener(listener); + testSimpleCompetitorListenerPatternForNationality(); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrentWeakHashMapTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrentWeakHashMapTest.java new file mode 100755 index 00000000000..b9f0cdb1937 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrentWeakHashMapTest.java @@ -0,0 +1,133 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Set; + +import org.junit.Test; + +import com.sap.sailing.util.impl.ConcurrentWeakHashMap; + +/** + * Tests the class {@link ConcurrentWeakHashMap}. + * + * @author Axel Uhl (D043530) + * + */ +public class ConcurrentWeakHashMapTest { + @Test + public void testPutAndGet() { + ConcurrentWeakHashMap m = new ConcurrentWeakHashMap<>(); + m.put("a", "b"); + assertEquals("b", m.get("a")); + } + + @Test + public void testPutAndIterate() { + ConcurrentWeakHashMap m = new ConcurrentWeakHashMap<>(); + m.put("a", "b"); + assertEquals("b", m.values().iterator().next()); + assertEquals("a", m.keySet().iterator().next()); + assertEquals("a", m.entrySet().iterator().next().getKey()); + } + + @Test + public void testPutAndPutAgain() { + ConcurrentWeakHashMap m = new ConcurrentWeakHashMap<>(); + Integer one1 = new Integer(1); + Integer one2 = new Integer(1); + assertNotSame(one1, one2); + m.put(one1, "a"); + String replaced = m.put(one2, "b"); + assertEquals("a", replaced); + assertEquals(1, m.size()); + assertEquals(m.keySet().iterator().next(), one2); + } + + @Test + public void testRemoveWithSame() { + ConcurrentWeakHashMap m = new ConcurrentWeakHashMap<>(); + Integer one = new Integer(1); + m.put(one, "a"); + m.remove(one); + assertTrue(m.isEmpty()); + } + + @Test + public void testRemoveImpactOnKeySetAndEntrySet() { + ConcurrentWeakHashMap m = new ConcurrentWeakHashMap<>(); + Integer one = new Integer(1); + m.put(one, "a"); + Set keySet = m.keySet(); + Set> entrySet = m.entrySet(); + assertEquals(1, keySet.size()); + assertEquals(1, entrySet.size()); + m.remove(one); + assertTrue(m.isEmpty()); + assertTrue(keySet.isEmpty()); + assertTrue(entrySet.isEmpty()); + } + + @Test + public void testRemoveWithEqual() { + ConcurrentWeakHashMap m = new ConcurrentWeakHashMap<>(); + Integer one1 = new Integer(1); + Integer one2 = new Integer(1); + assertNotSame(one1, one2); + m.put(one1, "a"); + m.remove(one2); + assertTrue(m.isEmpty()); + } + + @Test + public void removeThrougKeySet() { + ConcurrentWeakHashMap m = new ConcurrentWeakHashMap<>(); + Integer one1 = new Integer(1); + m.put(one1, "a"); + final Set keySet = m.keySet(); + Iterator keySetIter = keySet.iterator(); + keySetIter.next(); + keySetIter.remove(); + assertTrue(m.isEmpty()); + assertTrue(keySet.isEmpty()); + assertTrue(m.entrySet().isEmpty()); + } + + @Test + public void testPutAndGetWithSubsequentGC() { + Object key = createKeyObject(); + ConcurrentWeakHashMap m = new ConcurrentWeakHashMap<>(); + m.put(key, "b"); + assertEquals("b", m.get(key)); + key = null; + forceEnqueuableWeakReferencesToBeEnqueued(); + assertTrue(m.isEmpty()); + } + + private void forceEnqueuableWeakReferencesToBeEnqueued() { + System.gc(); + try { + long[] hugeArrayThatWontGetAllocated = new long[Integer.MAX_VALUE]; + assertTrue(hugeArrayThatWontGetAllocated.length > 0); + } catch (OutOfMemoryError e) { + // Ignore OME + } + try { + Thread.sleep(50); // give GC some time to finish enqueuing and finalizing all weakly-referenced objects + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + /** + * Use a separate method for this, assuming that the compiler won't put the object on the using method's stack + * @return + */ + private Object createKeyObject() { + return new Object(); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConfidenceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConfidenceTest.java index 4fef5c8f3b4..a9936dcb80b 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConfidenceTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConfidenceTest.java @@ -15,7 +15,6 @@ import com.sap.sailing.domain.base.BearingWithConfidence; import com.sap.sailing.domain.base.PositionWithConfidence; import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; import com.sap.sailing.domain.base.impl.PositionWithConfidenceImpl; -import com.sap.sailing.domain.base.impl.ScalablePosition; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; @@ -24,12 +23,13 @@ import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.RadianBearingImpl; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; import com.sap.sailing.domain.confidence.ConfidenceFactory; import com.sap.sailing.domain.confidence.HasConfidence; import com.sap.sailing.domain.confidence.HasConfidenceAndIsScalable; -import com.sap.sailing.domain.confidence.ScalableValue; import com.sap.sailing.domain.confidence.Weigher; import com.sap.sailing.domain.confidence.impl.ScalableDoubleWithConfidence; import com.sap.sailing.domain.tracking.Wind; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CountryCodeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CountryCodeTest.java index 3302deffe94..7bd0e898fb6 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CountryCodeTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CountryCodeTest.java @@ -21,7 +21,7 @@ import org.junit.Test; import com.sap.sailing.domain.common.CountryCode; import com.sap.sailing.domain.common.CountryCodeFactory; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sse.common.Util; public class CountryCodeTest { private static final int NUMBER_OF_COUNTRY_CODES = 259; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java index 2f7f059076f..25a9de6f919 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java @@ -30,13 +30,13 @@ import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl; +import com.sap.sse.common.Util; import difflib.PatchFailedException; @@ -179,10 +179,10 @@ public class CourseTest { assertEquals(3, Util.size(course.getWaypoints())); assertEquals(2, Util.size(course.getLegs())); assertWaypointIndexes(course); - List> courseToUpdate = new ArrayList>(); - courseToUpdate.add(new Pair(wp2.getMarks().iterator().next(), wp2.getPassingInstructions())); - courseToUpdate.add(new Pair(wp3.getMarks().iterator().next(), wp3.getPassingInstructions())); - courseToUpdate.add(new Pair(wp1.getMarks().iterator().next(), wp1.getPassingInstructions())); + List> courseToUpdate = new ArrayList>(); + courseToUpdate.add(new com.sap.sse.common.Util.Pair(wp2.getMarks().iterator().next(), wp2.getPassingInstructions())); + courseToUpdate.add(new com.sap.sse.common.Util.Pair(wp3.getMarks().iterator().next(), wp3.getPassingInstructions())); + courseToUpdate.add(new com.sap.sse.common.Util.Pair(wp1.getMarks().iterator().next(), wp1.getPassingInstructions())); course.update(courseToUpdate, DomainFactory.INSTANCE); assertWaypointIndexes(course); } @@ -252,7 +252,7 @@ public class CourseTest { DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(/* trackedRegatta */new TrackedRegattaImpl( new RegattaImpl("test", null, new HashSet(), false, null, "test", null)), new RaceDefinitionImpl("Test Race", course, new BoatClassImpl("49er", /* upwind start */true), hasso), - Collections. emptyList(), EmptyWindStore.INSTANCE, /* delayToLiveInMillis */3000, + Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE,/* delayToLiveInMillis */3000, /* millisecondsOverWhichToAverageWind */30000, /* millisecondsOverWhichToAverageSpeed */8000); assertLegStructure(course, trackedRace); @@ -283,7 +283,7 @@ public class CourseTest { new RegattaImpl("test", null, new HashSet(), false, null, "test", null)), new RaceDefinitionImpl("Test Race", course, new BoatClassImpl("49er", /* upwind start */ true), hasso), Collections. emptyList(), - EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 3000, + EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 8000); assertLegStructure(course, trackedRace); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java index ffd86d338d1..0e5deaca910 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java @@ -30,8 +30,6 @@ import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.MarkImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; import com.sap.sailing.domain.tracking.DynamicTrackedRace; @@ -46,6 +44,7 @@ import com.sap.sailing.domain.tractracadapter.Receiver; import com.sap.sailing.domain.tractracadapter.impl.ControlPointAdapter; import com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl; import com.sap.sailing.domain.tractracadapter.impl.RaceCourseReceiver; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.Race; import com.tractrac.clientmodule.Route; import com.tractrac.clientmodule.data.RouteData; @@ -82,7 +81,7 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest { }, /* delayToLiveInMillis */ 0l, /* millisecondsOverWhichToAverageWind */ 30000, /* simulator */ null, /*courseDesignUpdateURI*/ null, /*tracTracUsername*/ null, /*tracTracPassword*/ null) { @Override - protected void handleEvent(Triple event) { + protected void handleEvent(com.sap.sse.common.Util.Triple event) { super.handleEvent(event); synchronized (routeData) { routeData[0] = event.getB(); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java index 1923389c476..14fcf36ecf1 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java @@ -48,7 +48,7 @@ public class DummyTrackedRegattaRegistry implements TrackedRegattaRegistry { } @Override - public boolean isRaceBeingTracked(RaceDefinition r) { + public boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r) { return false; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/EventLeaderboardGroupLinkTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/EventLeaderboardGroupLinkTest.java new file mode 100755 index 00000000000..42754e124f9 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/EventLeaderboardGroupLinkTest.java @@ -0,0 +1,43 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; + +import java.util.Collections; +import java.util.Iterator; +import java.util.UUID; + +import org.junit.Test; + +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.impl.EventImpl; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; +import com.sap.sse.common.Util; + +public class EventLeaderboardGroupLinkTest { + @Test + public void testLivelinessAndIteratorStabilityOfUnmodifiableLeaderboardGroupCollection() { + Event e = new EventImpl("E", null, null, "Venue", /* public */ true, UUID.randomUUID()); + LeaderboardGroupImpl lg1 = new LeaderboardGroupImpl("lg1", "LG1", /* displayGroupsInReverseOrder */ false, Collections.emptyList()); + LeaderboardGroupImpl lg2 = new LeaderboardGroupImpl("lg2", "LG2", /* displayGroupsInReverseOrder */ false, Collections.emptyList()); + LeaderboardGroupImpl lg3 = new LeaderboardGroupImpl("lg3", "LG3", /* displayGroupsInReverseOrder */ false, Collections.emptyList()); + LeaderboardGroupImpl lg4 = new LeaderboardGroupImpl("lg4", "LG4", /* displayGroupsInReverseOrder */ false, Collections.emptyList()); + e.addLeaderboardGroup(lg1); + Iterable lgs = e.getLeaderboardGroups(); + Iterator iter = lgs.iterator(); + assertSame(lg1, iter.next()); + assertEquals(1, Util.size(lgs)); + e.addLeaderboardGroup(lg3); + assertEquals(2, Util.size(lgs)); + assertEquals(lg1, Util.get(lgs, 0)); + assertEquals(lg3, Util.get(lgs, 1)); + assertFalse(iter.hasNext()); // insertion immediately after the current element is not recognized by the iterator + e.removeLeaderboardGroup(lg3); + e.addLeaderboardGroup(lg2); + e.addLeaderboardGroup(lg3); + e.addLeaderboardGroup(lg4); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/FetchTracksAndStoreLocallyTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/FetchTracksAndStoreLocallyTest.java index 20352794864..84353f4df6f 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/FetchTracksAndStoreLocallyTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/FetchTracksAndStoreLocallyTest.java @@ -12,8 +12,8 @@ import org.junit.Test; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; -import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.RaceListener; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java index bf39cfb080a..e3e1195d331 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java @@ -21,13 +21,13 @@ import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.LowPoint; import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tractracadapter.ReceiverType; +import com.sap.sse.common.Util; public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest { @@ -48,19 +48,19 @@ public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest { Competitor kevin = getCompetitorByName("Kevin"); RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */ false, defaultFleet); assertEquals(17, leaderboard.getTotalPoints(kevin, now), 0.000000001); - Pair key = new Pair(kevin, column); + Util.Pair key = new Util.Pair(kevin, column); assertEquals(17, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); assertEquals(17, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001); loadRace("event_20110609_KielerWoch-505_Race_3.txt", "event_20110609_KielerWoch-505_Race_3.mtb"); // 505 Race 3 column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */ false, defaultFleet); - key = new Pair(kevin, column); + key = new Util.Pair(kevin, column); // In Race 3, Hasso ranked 31st assertEquals(29, leaderboard.getTotalPoints(kevin, now), 0.000000001); assertEquals(12, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); assertEquals(12, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001); loadRace("event_20110609_KielerWoch-505_race_4.txt", "event_20110609_KielerWoch-505_race_4.mtb"); // 505 Race 4 column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */ false, defaultFleet); - key = new Pair(kevin, column); + key = new Util.Pair(kevin, column); // now the first race is discarded because Kevin ranked worst compared to the other two; in race 4 he ranked 10th assertEquals(22, leaderboard.getTotalPoints(kevin, now), 0.000000001); assertEquals(10, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java index 4df7ca3ee3b..23ac9d34ed1 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java @@ -34,8 +34,6 @@ import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.LowPoint; @@ -45,6 +43,7 @@ import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithFixedRankAndManyCom import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sse.common.Util; public class LeaderboardOfflineTest extends AbstractLeaderboardTest { private Set testRaces; @@ -140,7 +139,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { return null; } @Override - public boolean isRaceBeingTracked(RaceDefinition r) { + public boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r) { return true; } @Override @@ -148,17 +147,17 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { InterruptedException { } }; - LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE); + LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE); assertNotNull(leaderboardDTO); - assertSame(leaderboardDTO, leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE)); // assert it's cached + assertSame(leaderboardDTO, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE)); // assert it's cached leaderboard.getRaceColumnByName(columnName).releaseTrackedRace(defaultFleet); // this should clear the cache - LeaderboardDTO leaderboardDTO2 = leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE); + LeaderboardDTO leaderboardDTO2 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE); assertNotSame(leaderboardDTO, leaderboardDTO2); - assertSame(leaderboardDTO2, leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again + assertSame(leaderboardDTO2, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again leaderboard.getRaceColumnByName(columnName).setTrackedRace(defaultFleet, testRaces.iterator().next()); // clear cache again; requires listener(s) to still be attached - LeaderboardDTO leaderboardDTO3 = leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE); + LeaderboardDTO leaderboardDTO3 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE); assertNotSame(leaderboardDTO2, leaderboardDTO3); - assertSame(leaderboardDTO3, leaderboard.getLeaderboardDTO(now, emptySet, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again + assertSame(leaderboardDTO3, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again } @Test @@ -323,7 +322,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { int medalRacePoints = getMedalRacePoints(competitor, now, defaultFleet); for (TrackedRace race : testRaces) { RaceColumn raceColumn = raceColumnsInLeaderboard.get(race); - Pair key = new Pair(competitor, raceColumn); + com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(competitor, raceColumn); if (race.hasStarted(now)) { int rank = race.getRank(competitor, now); assertEquals(rank, leaderboard.getTrackedRank(competitor, raceColumn, now)); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index bc3427869e9..65bd69042fe 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -32,12 +32,12 @@ import com.sap.sailing.domain.base.impl.MarkImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.SeriesImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.Duration; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; @@ -57,6 +57,7 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.TimedComparator; import com.sap.sailing.util.impl.ArrayListNavigableSet; +import com.sap.sse.common.Util; public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { private ArrayList series; @@ -600,16 +601,16 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { lastMarkPassingTimesForCompetitors[2].put(c[2], finish); createAndAttachTrackedRacesWithStartTimeAndLastMarkPassingTimes(series.get(1), "Default", new Competitor[][] { f1, f2, f3 }, new TimePoint[] { earlier, now, later }, lastMarkPassingTimesForCompetitors); - long totalTimeSailedC0_InRace1 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], earlier.plus(1000)); - assertEquals(1000l, totalTimeSailedC0_InRace1); - long totalTimeSailedC0_InRace2 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], now.plus(1000)); - assertEquals(now.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace2); - long totalTimeSailedC0_InRace3 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], later.plus(1000)); - assertEquals(later.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace3); - long totalTimeSailedC0_AtEndOfRace3 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], finish); - assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AtEndOfRace3); - long totalTimeSailedC0_AfterRace3 = leaderboard.getTotalTimeSailedInMilliseconds(c[0], finish.plus(1000)); - assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AfterRace3); + Duration totalTimeSailedC0_InRace1 = leaderboard.getTotalTimeSailed(c[0], earlier.plus(1000)); + assertEquals(1000l, totalTimeSailedC0_InRace1.asMillis()); + Duration totalTimeSailedC0_InRace2 = leaderboard.getTotalTimeSailed(c[0], now.plus(1000)); + assertEquals(now.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace2.asMillis()); + Duration totalTimeSailedC0_InRace3 = leaderboard.getTotalTimeSailed(c[0], later.plus(1000)); + assertEquals(later.asMillis()-earlier.asMillis() + 1000, totalTimeSailedC0_InRace3.asMillis()); + Duration totalTimeSailedC0_AtEndOfRace3 = leaderboard.getTotalTimeSailed(c[0], finish); + assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AtEndOfRace3.asMillis()); + Duration totalTimeSailedC0_AfterRace3 = leaderboard.getTotalTimeSailed(c[0], finish.plus(1000)); + assertEquals(finish.asMillis()-earlier.asMillis(), totalTimeSailedC0_AfterRace3.asMillis()); } @Test diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LockTraceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LockTraceTest.java index 814131f7e03..daa6684533a 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LockTraceTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LockTraceTest.java @@ -4,12 +4,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import org.junit.Ignore; import org.junit.Test; +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.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; @@ -44,25 +48,58 @@ public class LockTraceTest { NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("testReentrantReadLocking-Lock", /* fair */ true); LockUtil.lockForRead(lock); assertTrue(lock.getReaders().contains(Thread.currentThread())); + assertEquals(1, lock.getReaders().size()); LockUtil.lockForRead(lock); assertTrue(lock.getReaders().contains(Thread.currentThread())); + assertEquals(2, lock.getReaders().size()); LockUtil.unlockAfterRead(lock); assertTrue(lock.getReaders().contains(Thread.currentThread())); + assertEquals(1, lock.getReaders().size()); + LockUtil.unlockAfterRead(lock); + assertFalse(lock.getReaders().contains(Thread.currentThread())); + assertEquals(0, lock.getReadHoldCount()); + assertEquals(0, lock.getReaders().size()); + } + + @Test + public void testReentrantWriteLockingWithInBetweenReadLocking() { + NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("testReentrantWriteLockingWithInBetweenReadLocking-Lock", /* fair */ false); + LockUtil.lockForWrite(lock); + assertTrue(lock.getWriter() == Thread.currentThread()); + LockUtil.lockForRead(lock); // reentrant read while holding write + assertTrue(lock.getReaders().contains(Thread.currentThread())); + LockUtil.lockForWrite(lock); // reentrant write + assertTrue(lock.getWriter() == Thread.currentThread()); + assertTrue(lock.getReaders().contains(Thread.currentThread())); LockUtil.unlockAfterRead(lock); assertFalse(lock.getReaders().contains(Thread.currentThread())); assertEquals(0, lock.getReadHoldCount()); + assertTrue(lock.getWriter() == Thread.currentThread()); + assertFalse(lock.getReaders().contains(Thread.currentThread())); + assertEquals(0, lock.getReadHoldCount()); + LockUtil.unlockAfterWrite(lock); + assertTrue(lock.getWriter() == Thread.currentThread()); + LockUtil.unlockAfterWrite(lock); + assertFalse(lock.getWriter() == Thread.currentThread()); } @Test - public void testLockingPerformance() { + public void testLockingPerformance() throws IOException { NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("Lock", /* fair */ true); long start = System.currentTimeMillis(); - for (int i=0; i<100000; i++) { + final int count = 100000; + for (int i=0; i maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate), + new MillisecondsTimePoint(toDate), /* waitForLatest */ true); + maneuversInvalid = new ArrayList(maneuvers); + + assertManeuver(maneuvers, ManeuverType.JIBE, new MillisecondsTimePoint(dateFormat.parse("05/01/2014-09:08:15")), 5000); + assertManeuver(maneuvers, ManeuverType.TACK, new MillisecondsTimePoint(dateFormat.parse("05/01/2014-09:08:51")), 5000); + for (Maneuver maneuver : maneuvers) { + // make sure there is no penalty detected in the time frame considered + assertNotSame("Found an unexpected penalty "+maneuver, ManeuverType.PENALTY_CIRCLE, maneuver.getType()); + } + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java index 74096a8454c..149bc97de85 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java @@ -15,8 +15,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; - import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; @@ -55,25 +55,25 @@ public class MultipleClassesInRegattaTest { httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=cce678c8-97e6-11e0-9aed-406186cbf87c"), new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI), /* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, /* simulateWithStartTimeNow */ false, - EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, new DummyTrackedRegattaRegistry()); + EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, new DummyTrackedRegattaRegistry()); kiwotest2 = domainFactory .createRaceTracker( new URL( httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=11290bd6-97e7-11e0-9aed-406186cbf87c"), new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI), - /* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, /* simulateWithStartTimeNow */ false, EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, new DummyTrackedRegattaRegistry()); + /* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, /* simulateWithStartTimeNow */ false, EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, new DummyTrackedRegattaRegistry()); kiwotest3 = domainFactory .createRaceTracker( new URL( httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=39635b24-97e7-11e0-9aed-406186cbf87c"), new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI), - /* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, /* simulateWithStartTimeNow */ false, EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, new DummyTrackedRegattaRegistry()); + /* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, /* simulateWithStartTimeNow */ false, EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, new DummyTrackedRegattaRegistry()); weym470may112014_2 = domainFactory .createRaceTracker( new URL( httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=04498426-7dfd-11e0-8236-406186cbf87c"), new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI), - /* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, /* simulateWithStartTimeNow */ false, EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, new DummyTrackedRegattaRegistry()); + /* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, /* simulateWithStartTimeNow */ false, EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, new DummyTrackedRegattaRegistry()); assertEquals("STG", kiwotest1.getRegatta().getBoatClass().getName()); assertEquals("505", kiwotest2.getRegatta().getBoatClass().getName()); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java index 85841f13b5b..de167da9325 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java @@ -17,6 +17,7 @@ import org.junit.Test; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Nationality; import com.sap.sailing.domain.base.Waypoint; @@ -24,10 +25,12 @@ import com.sap.sailing.domain.base.impl.BoatImpl; import com.sap.sailing.domain.base.impl.CompetitorImpl; import com.sap.sailing.domain.base.impl.CourseAreaImpl; import com.sap.sailing.domain.base.impl.DomainFactoryImpl; +import com.sap.sailing.domain.base.impl.EventImpl; import com.sap.sailing.domain.base.impl.PersonImpl; import com.sap.sailing.domain.base.impl.TeamImpl; import com.sap.sailing.domain.common.Color; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.domain.common.Duration; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; @@ -35,6 +38,7 @@ import com.sap.sailing.domain.leaderboard.impl.HighPoint; import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; +import com.sap.sse.common.Util; public class OfflineSerializationTest extends AbstractSerializationTest { /** @@ -48,16 +52,16 @@ public class OfflineSerializationTest extends AbstractSerializationTest { DomainFactory receiverDomainFactory = new DomainFactoryImpl(); final Throwable s1 = new Throwable(); final Throwable s2 = new Throwable(); - Pair p = - new Pair( + com.sap.sse.common.Util.Pair p = + new com.sap.sse.common.Util.Pair( s1, s2); - HashSet> s = - new HashSet>(); + HashSet> s = + new HashSet>(); s.add(p); - Set> ss = + Set> ss = cloneBySerialization(s, /* resolveAgainst */ receiverDomainFactory); - Pair ps = ss.iterator().next(); + com.sap.sse.common.Util.Pair ps = ss.iterator().next(); Throwable s1Des = ps.getA(); Throwable s2Des = ps.getB(); assertNotSame(s, ss); @@ -65,8 +69,8 @@ public class OfflineSerializationTest extends AbstractSerializationTest { assertNotSame(s1, s1Des); assertNotSame(s2, s2Des); assertEquals(1, ss.size()); - Pair pNew = - new Pair(s1Des, s2Des); + com.sap.sse.common.Util.Pair pNew = + new com.sap.sse.common.Util.Pair(s1Des, s2Des); assertEquals(ps.hashCode(), pNew.hashCode()); assertTrue(ss.contains(pNew)); } @@ -82,6 +86,20 @@ public class OfflineSerializationTest extends AbstractSerializationTest { int[] clone = cloneBySerialization(intArray, receiverDomainFactory); assertTrue(Arrays.equals(intArray, clone)); } + + @Test + public void testSerializingEventWithLeaderboardGroups() throws ClassNotFoundException, IOException { + DomainFactory receiverDomainFactory = new DomainFactoryImpl(); + Event e = new EventImpl("Event Name", MillisecondsTimePoint.now(), MillisecondsTimePoint.now().plus( + Duration.ONE_DAY.times(10)), "At Home", /* is public */true, UUID.randomUUID()); + LeaderboardGroup lg1 = new LeaderboardGroupImpl("LG1", "LG1 Description", /* displayGroupsInReverseOrder */ false, Collections. emptyList()); + e.addLeaderboardGroup(lg1); + LeaderboardGroup lg2 = new LeaderboardGroupImpl("LG2", "LG2 Description", /* displayGroupsInReverseOrder */ false, Collections. emptyList()); + e.addLeaderboardGroup(lg2); + Event deserialized = cloneBySerialization(e, receiverDomainFactory); + assertEquals(Util.size(e.getLeaderboardGroups()), Util.size(deserialized.getLeaderboardGroups())); + assertEquals(e.getLeaderboardGroups().iterator().next().getName(), deserialized.getLeaderboardGroups().iterator().next().getName()); + } /** * We had trouble de-serializing int[] through our specialized ObjectInputStream with its own resolveClass diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogAttachOnTrackedRaceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogAttachOnTrackedRaceTest.java index 78e7d9383fa..07de1bddc6e 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogAttachOnTrackedRaceTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogAttachOnTrackedRaceTest.java @@ -12,13 +12,13 @@ import org.junit.Test; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.LowPoint; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.test.mock.MockedTrackedRace; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Util; public class RaceLogAttachOnTrackedRaceTest { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReachingLegTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReachingLegTest.java index c6f20d197ea..fafcebf1551 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReachingLegTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReachingLegTest.java @@ -23,8 +23,8 @@ import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.impl.BoatClassImpl; import com.sap.sailing.domain.base.impl.CompetitorImpl; -import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.MarkImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; @@ -43,6 +43,7 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedLeg; @@ -75,14 +76,14 @@ public class ReachingLegTest extends TrackBasedTest { schomaeker = createCompetitor("Meike Schom�ker"); competitors.add(schomaeker); start = new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()); - setTrackedRace(createTestTrackedRace("Kieler Woche", "505 Race 2", "505", competitors, start)); + setTrackedRace(createTrackedRace("Kieler Woche", "505 Race 2", "505", competitors, start)); List hungersMarkPassings = createMarkPassings(hunger, start); getTrackedRace().updateMarkPassings(hunger, hungersMarkPassings); List plattnersMarkPassings = createMarkPassings(plattner, start); getTrackedRace().updateMarkPassings(plattner, plattnersMarkPassings); } - protected DynamicTrackedRace createTestTrackedRace(String regattaName, String raceName, String boatClassName, + protected DynamicTrackedRace createTrackedRace(String regattaName, String raceName, String boatClassName, Iterable competitors, TimePoint timePointForFixes) { BoatClassImpl boatClass = new BoatClassImpl(boatClassName, /* typicallyStartsUpwind */ true); Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, regattaName, boatClass, /* trackedRegattaRegistry */ null, @@ -102,7 +103,7 @@ public class ReachingLegTest extends TrackBasedTest { Course course = new CourseImpl(raceName, waypoints); RaceDefinition race = new RaceDefinitionImpl(raceName, course, boatClass, competitors); DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, Collections. emptyList(), EmptyWindStore.INSTANCE, - /* delayToLiveInMillis */ 0, + EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 0, /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000, /* delay for wind estimation cache invalidation */ 0); // in this simplified artificial course, the top mark is exactly north of the right leeward gate, the offset diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RouteAssemblyTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RouteAssemblyTest.java index 9a93d90d6ce..97dab070b8f 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RouteAssemblyTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RouteAssemblyTest.java @@ -11,9 +11,9 @@ import org.junit.Test; import com.maptrack.client.io.TypeController; import com.sap.sailing.domain.base.Course; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.Receiver; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.Route; import com.tractrac.clientmodule.data.ICallbackData; import com.tractrac.clientmodule.data.RouteData; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SerializationTestWithOnlineRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SerializationWithOnlineRaceTest.java similarity index 84% rename from java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SerializationTestWithOnlineRace.java rename to java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SerializationWithOnlineRaceTest.java index dca893da376..f74bf7406f2 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SerializationTestWithOnlineRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SerializationWithOnlineRaceTest.java @@ -24,16 +24,15 @@ import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tractracadapter.ReceiverType; +import com.sap.sse.common.Util; -public class SerializationTestWithOnlineRace extends OnlineTracTracBasedTest { - public SerializationTestWithOnlineRace() throws MalformedURLException, URISyntaxException { +public class SerializationWithOnlineRaceTest extends OnlineTracTracBasedTest { + public SerializationWithOnlineRaceTest() throws MalformedURLException, URISyntaxException { super(); } @@ -51,11 +50,11 @@ public class SerializationTestWithOnlineRace extends OnlineTracTracBasedTest { new DegreeBearingImpl(65))), new WindSourceImpl(WindSourceType.WEB)); } - private Pair getSerializationSizeAndTime(Serializable s) throws IOException { + private com.sap.sse.common.Util.Pair getSerializationSizeAndTime(Serializable s) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); long start = System.currentTimeMillis(); new ObjectOutputStream(bos).writeObject(s); - return new Pair(bos.size(), System.currentTimeMillis()-start); + return new com.sap.sse.common.Util.Pair(bos.size(), System.currentTimeMillis()-start); } @Test @@ -67,7 +66,7 @@ public class SerializationTestWithOnlineRace extends OnlineTracTracBasedTest { cloneOfFindelsTrack.lockForRead(); try { assertEquals(Util.size(findelsTrack.getFixes()), Util.size(cloneOfFindelsTrack.getFixes())); - Pair sizeAndTime = getSerializationSizeAndTime(findelsTrack); + com.sap.sse.common.Util.Pair sizeAndTime = getSerializationSizeAndTime(findelsTrack); System.out.println(sizeAndTime); assertTrue(sizeAndTime.getA() > 100000); } finally { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java index 93eb7995f62..101ebaedbb4 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java @@ -86,7 +86,7 @@ public class StarbordSideOfStartLineRecognitionTest { private static final long serialVersionUID = -8007932232555073829L; public MockedTrackedRaceImpl() { - super(null, null, Collections. emptyList(), null, 0, 0, 0, 0); + super(null, null, Collections. emptyList(), null, null, 0, 0, 0, 0); } @Override diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TimeInLegProgressesTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TimeInLegProgressesTest.java new file mode 100755 index 00000000000..29d8066a0c4 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TimeInLegProgressesTest.java @@ -0,0 +1,56 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.ParseException; +import java.util.GregorianCalendar; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.WindSourceImpl; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sailing.domain.tractracadapter.ReceiverType; + +public class TimeInLegProgressesTest extends OnlineTracTracBasedTest { + public TimeInLegProgressesTest() throws MalformedURLException, URISyntaxException { + super(); + } + + @Before + public void setUp() throws URISyntaxException, IOException, InterruptedException, ParseException { + super.setUp(); + URI storedUri = new URI("file:///"+new File("resources/event_20110609_KielerWoch-505_Race_2.mtb").getCanonicalPath().replace('\\', '/')); + super.setUp(new URL("file:///"+new File("resources/event_20110609_KielerWoch-505_Race_2.txt").getCanonicalPath()), + /* liveUri */ null, /* storedUri */ storedUri, + new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS }); + OnlineTracTracBasedTest.fixApproximateMarkPositionsForWindReadOut(getTrackedRace(), new MillisecondsTimePoint( + new GregorianCalendar(2011, 05, 23).getTime())); + getTrackedRace().recordWind( + new WindImpl(/* position */null, MillisecondsTimePoint.now(), new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(65))), new WindSourceImpl(WindSourceType.WEB)); + } + + @Test + public void testDifferentTimesSpentInLegAtDifferentTimes() throws ClassNotFoundException, IOException { + final Competitor findel = getCompetitorByName("Findel"); + TrackedLegOfCompetitor findelsSecondLeg = getTrackedRace().getTrackedLeg(getTrackedRace().getRace().getCourse().getLegs().get(1)).getTrackedLeg(findel); + TimePoint findelStartedHisSecondLegAt = findelsSecondLeg.getStartTime(); + assertEquals(null, findelsSecondLeg.getTime(findelStartedHisSecondLegAt.minus(1))); + assertEquals(0.0, findelsSecondLeg.getTime(findelStartedHisSecondLegAt).asMillis(), 0.00001); + assertEquals(10000.0, findelsSecondLeg.getTime(findelStartedHisSecondLegAt.plus(10000)).asMillis(), 0.00001); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java index c97769584b6..252acc9fdc4 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java @@ -17,8 +17,8 @@ import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.impl.BoatClassImpl; import com.sap.sailing.domain.base.impl.BoatImpl; import com.sap.sailing.domain.base.impl.CompetitorImpl; -import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.MarkImpl; import com.sap.sailing.domain.base.impl.NationalityImpl; import com.sap.sailing.domain.base.impl.PersonImpl; @@ -36,6 +36,7 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRegatta; @@ -92,7 +93,7 @@ public abstract class TrackBasedTest { * tracked race returned by this method) is important because otherwise confidences of wind readouts may * be ridiculously low. */ - protected DynamicTrackedRace createTestTrackedRace(String regattaName, String raceName, String boatClassName, + public static DynamicTrackedRace createTestTrackedRace(String regattaName, String raceName, String boatClassName, Iterable competitors, TimePoint timePointForFixes) { BoatClassImpl boatClass = new BoatClassImpl(boatClassName, /* typicallyStartsUpwind */ true); Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, regattaName, boatClass, /* trackedRegattaRegistry */ null, @@ -112,7 +113,7 @@ public abstract class TrackBasedTest { Course course = new CourseImpl(raceName, waypoints); RaceDefinition race = new RaceDefinitionImpl(raceName, course, boatClass, competitors); DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, Collections. emptyList(), EmptyWindStore.INSTANCE, - /* delayToLiveInMillis */ 0, + EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 0, /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000, /* delay for wind estimation cache invalidation */ 0); // in this simplified artificial course, the top mark is exactly north of the right leeward gate diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java index 809b4a1d79a..c08c2a1d461 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java @@ -39,9 +39,6 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.impl.MeterPerSecondSpeedWithDegreeBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; @@ -52,6 +49,7 @@ import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl; import com.sap.sailing.domain.tracking.impl.GPSFixImpl; import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.tracking.impl.TrackImpl; +import com.sap.sse.common.Util; public class TrackTest { private DynamicGPSFixTrack track; @@ -262,12 +260,12 @@ public class TrackTest { */ @Test public void testDistanceTraveledBackwardsQuery() { - final Set> cacheEntries = new HashSet<>(); + final Set> cacheEntries = new HashSet<>(); final DistanceCache distanceCache = new DistanceCache("test-DistanceCache") { @Override public void cache(TimePoint from, TimePoint to, Distance distance) { super.cache(from, to, distance); - cacheEntries.add(new Triple<>(from, to, distance)); + cacheEntries.add(new com.sap.sse.common.Util.Triple<>(from, to, distance)); } }; @@ -632,7 +630,7 @@ public class TrackTest { } invalidationCalls.clear(); assertEquals(speed.getMetersPerSecond()*(steps-1), track.getDistanceTraveled(now, start).getMeters(), 0.01); - final Pair> fullIntervalCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start); + final com.sap.sse.common.Util.Pair> fullIntervalCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start); assertNotNull(fullIntervalCacheEntry); // no more entry for "to"-value start in cache assertEquals(start, fullIntervalCacheEntry.getA()); assertEquals(now, fullIntervalCacheEntry.getB().getA()); @@ -648,7 +646,7 @@ public class TrackTest { final TimePoint timePointOfLastOriginalFix = track.getLastRawFix().getTimePoint(); assertEquals(speed.getMetersPerSecond() * (steps - 1), track.getDistanceTraveled(now, timePointOfLastOriginalFix).getMeters(), 0.01); - final Pair> newFullIntervalCacheEntry = distanceCache + final com.sap.sse.common.Util.Pair> newFullIntervalCacheEntry = distanceCache .getEarliestFromAndDistanceAtOrAfterFrom(now, timePointOfLastOriginalFix); assertNotNull(newFullIntervalCacheEntry); // no more entry for "to"-value start in cache assertEquals(timePointOfLastOriginalFix, newFullIntervalCacheEntry.getA()); @@ -665,7 +663,7 @@ public class TrackTest { assertTrue(invalidationCalls.iterator().next().equals(timePointOfLastRawFixBeforeLateOutlier)); invalidationCalls.clear(); // expect the invalidation to have started after the single cache entry, so the cache entry still has to be there: - final Pair> stillPresentFullIntervalCacheEntry = distanceCache + final com.sap.sse.common.Util.Pair> stillPresentFullIntervalCacheEntry = distanceCache .getEarliestFromAndDistanceAtOrAfterFrom(now, timePointOfLastOriginalFix); assertNull(stillPresentFullIntervalCacheEntry); // no more entry for "to"-value start in cache because new temporary outlier lies in previously cached interval GPSFix polishedLastFix = track.getLastFixBefore(new MillisecondsTimePoint(Long.MAX_VALUE)); // get the last smoothened fix... @@ -690,7 +688,7 @@ public class TrackTest { assertEquals(track.getLastFixBefore(timePointOfLastRawFixBeforeLateOutlier).getTimePoint().plus(1), invalidationCalls.iterator().next()); assertTrue(timePointForLateOutlier.compareTo(fix.getTimePoint()) < 0); // expect the invalidation to have started at the fix before the outlier, leaving the previous result ending at the fix right before the outlier intact - final Pair> stillStillPresentFullIntervalCacheEntry = distanceCache + final com.sap.sse.common.Util.Pair> stillStillPresentFullIntervalCacheEntry = distanceCache .getEarliestFromAndDistanceAtOrAfterFrom(now, timePointOfLastOriginalFix); assertNull(stillStillPresentFullIntervalCacheEntry); // still nothing in the cache track.lockForRead(); @@ -796,11 +794,11 @@ public class TrackTest { assertEquals(speed.getMetersPerSecond()*(steps-1), track.getDistanceTraveled(now, start).getMeters(), 0.01); assertTrue(invalidationCalls.isEmpty()); // expect a cache entry exactly for the strip's boundaries - Pair> stripCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(stripFrom, stripTo); + com.sap.sse.common.Util.Pair> stripCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(stripFrom, stripTo); assertEquals(stripTo, stripCacheEntry.getA()); assertEquals(stripFrom, stripCacheEntry.getB().getA()); // expect a cache entry exactly for the full boundaries - Pair> fullCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start); + com.sap.sse.common.Util.Pair> fullCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start); assertEquals(start, fullCacheEntry.getA()); assertEquals(now, fullCacheEntry.getB().getA()); } @@ -930,7 +928,7 @@ public class TrackTest { speed.travel(firstFixSoFar.getTimePoint(), firstFixSoFar.getTimePoint().plus(timespan))); GPSFixMoving newFirstFix = new GPSFixMovingImpl(newPosition, slightlyBeforeFirstFix, speed); track.addGPSFix(newFirstFix); - Pair intervalAffected = track.getEstimatedPositionTimePeriodAffectedBy(newFirstFix); + com.sap.sse.common.Util.Pair intervalAffected = track.getEstimatedPositionTimePeriodAffectedBy(newFirstFix); Position newPositionAtBeginningOfTime = track.getEstimatedPosition(beginningOfTime, /* extrapolate */false); assertFalse(newPositionAtBeginningOfTime.equals(positionAtBeginningOfTime)); assertTrue(!intervalAffected.getA().after(beginningOfTime)); @@ -949,7 +947,7 @@ public class TrackTest { speed.travel(lastFixSoFar.getTimePoint(), lastFixSoFar.getTimePoint().plus(timespan))); GPSFixMoving newLastFix = new GPSFixMovingImpl(newPosition, slightlyAfterLastFix, speed); track.addGPSFix(newLastFix); - Pair intervalAffected = track.getEstimatedPositionTimePeriodAffectedBy(newLastFix); + com.sap.sse.common.Util.Pair intervalAffected = track.getEstimatedPositionTimePeriodAffectedBy(newLastFix); Position newPositionAtEndOfTime = track.getEstimatedPosition(endOfTime, /* extrapolate */false); assertFalse(newPositionAtEndOfTime.equals(positionAtEndOfTime)); assertTrue(!intervalAffected.getB().before(endOfTime)); @@ -967,7 +965,7 @@ public class TrackTest { assertNull(positionAtEndOfTime); GPSFixMoving newFix = new GPSFixMovingImpl(new DegreePosition(12, 34), now, new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(123))); myTrack.addGPSFix(newFix); - Pair intervalAffected = myTrack.getEstimatedPositionTimePeriodAffectedBy(newFix); + com.sap.sse.common.Util.Pair intervalAffected = myTrack.getEstimatedPositionTimePeriodAffectedBy(newFix); Position newPositionAtBeginningOfTime = myTrack.getEstimatedPosition(beginningOfTime, /* extrapolate */false); Position newPositionAtEndOfTime = myTrack.getEstimatedPosition(now, /* extrapolate */false); assertFalse(newPositionAtBeginningOfTime.equals(positionAtBeginningOfTime)); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java index 22e6db0c9fc..05eab5a3c2d 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java @@ -15,6 +15,7 @@ import org.junit.Test; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; @@ -55,7 +56,7 @@ public class UnicodeCharactersInCompetitorNamesTest { + TracTracConnectionConstants.PORT_STORED), new URI("http://tracms.traclive.dk/update_course"), /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, - /* simulateWithStartTimeNow */ false, EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, "tracTest", "tracTest", "", + /* simulateWithStartTimeNow */ false, EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, "tracTest", "tracTest", "", new DummyTrackedRegattaRegistry()); Iterable competitors = fourtyninerYellow_2.getRacesHandle().getRaces().iterator().next() diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UpdateMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UpdateMarkPassingTest.java index 479d9109b0f..c4e09ac1d94 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UpdateMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UpdateMarkPassingTest.java @@ -23,12 +23,13 @@ import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl; +import com.sap.sse.common.Util; public class UpdateMarkPassingTest { @Test @@ -43,7 +44,7 @@ public class UpdateMarkPassingTest { when(race.getCompetitors()).thenReturn(Collections.singleton(competitor)); DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl( /* trackedRegatta */new TrackedRegattaImpl(new RegattaImpl("test", null, new HashSet(), false, null, - "test", null)), race, Collections. emptyList(), EmptyWindStore.INSTANCE, + "test", null)), race, Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */1000, /* millisecondsOverWhichToAverageWind */30000, /* millisecondsOverWhichToAverageSpeed */30000); TimePoint now = MillisecondsTimePoint.now(); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationLockingUnderLoadTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationLockingUnderLoadTest.java new file mode 100755 index 00000000000..80eb42a61a9 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationLockingUnderLoadTest.java @@ -0,0 +1,182 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; +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.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.domain.tracking.impl.TrackBasedEstimationWindTrackImpl; +import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; +import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sailing.domain.tracking.impl.WindTrackImpl; +import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl; + +/** + * See the issues documented in bug 1923 (http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=1923). + * This test mocks {@link TrackedRace} to the degree necessary and fires more or less random "measured" + * wind fixes against the {@link TrackBasedEstimationWindTrackImpl} while concurrently reading massively + * from it. With this, the test is supposed to reproduce conditions as observed during events, leading + * up to bug 1923. This can then be the basis for a fix. + * + * @author Axel Uhl (D043530) + * + */ +public class WindEstimationLockingUnderLoadTest { + private static final Logger logger = Logger.getLogger(WindEstimationLockingUnderLoadTest.class.getName()); + + private static final int MIN_WIND_SPEED_IN_KNOTS = 5; + + private static final int MAX_WIND_SPEED_IN_KNOTS = 20; + + private TrackedRace mockedTrackedRace; + + private WindSource realWindSource; + + private TrackBasedEstimationWindTrackImpl estimationTrack; + + private WindTrackImpl measuredTrack; + + @Before + public void setUp() { + realWindSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION, "1"); + measuredTrack = new WindTrackImpl(WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, /* useSpeed */ true, /* nameForReadWriteLock */ "Test wind track in "+getClass().getName()); + mockedTrackedRace = mockTrackedRace(); + estimationTrack = new TrackBasedEstimationWindTrackImpl(mockedTrackedRace, WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, 0.5); + } + + @After + public void tearDown() { + // clean up all Mockito stubbing leaks, particularly the InvocationImpl objects attached to any ThreadLocal; + // see also bug 1923, comment #9. + Mockito.reset(); + } + + private TrackedRace mockTrackedRace() { + TrackedRace result = mock(TrackedRace.class); + RaceDefinition mockedRaceDefinition = mock(RaceDefinition.class); + when(result.getRace()).thenReturn(mockedRaceDefinition); + when(mockedRaceDefinition.getName()).thenReturn("Test Race"); + when(result.getEstimatedWindDirectionWithConfidence((Position) any(), (TimePoint) any())).thenAnswer(new Answer>() { + public WindWithConfidence answer(InvocationOnMock invocation) { + return randomWindOrNull(); + } + }); + when(result.getOrCreateWindTrack(realWindSource)).thenReturn(measuredTrack); + when(result.getStatus()).thenReturn(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, /* loadingProgress */ 1.0)); + when(result.getMillisecondsOverWhichToAverageWind()).thenReturn(30000l); + return result; + } + + private WindWithConfidence randomWindOrNull() { + final WindWithConfidence result; + double speedInKnots = MAX_WIND_SPEED_IN_KNOTS*Math.random(); + if (speedInKnots < MIN_WIND_SPEED_IN_KNOTS) { + result = null; + } else { + double directionInDegrees = 360*Math.random(); + double confidence = Math.random(); + final MillisecondsTimePoint now = MillisecondsTimePoint.now(); + result = new WindWithConfidenceImpl(new WindImpl(new DegreePosition(49, 8), now, new KnotSpeedWithBearingImpl(speedInKnots, new DegreeBearingImpl(directionInDegrees))), + confidence, now, /* useSpeed */ true); + } + return result; + } + + @Test + public void testSimpleWindEstimation() { + // We can expect MIN_WIND_SPEED_IN_KNOTS/MAX_WIND_SPEED_IN_KNOTS results to be null, but as there is a significant + // difference between the two, with a sufficient amount of random samples there has to be at least one non-null + // result + int notNullCount = 0; + for (int i=0; i<1000; i++) { + Wind estimatedWind = estimationTrack.getAveragedWind(/* position */ null, MillisecondsTimePoint.now().minus(10000*i)); + if (estimatedWind != null) { + notNullCount++; + } + } + assertTrue(notNullCount > 0); + } + + @Test + public void testSendingNewWindFixToEstimationTrack() { + final WindImpl wind = new WindImpl(new DegreePosition(49, 8), MillisecondsTimePoint.now(), + new KnotSpeedWithBearingImpl(/* speedInKnots */12., new DegreeBearingImpl(47))); + addMeasuredWindFix(wind); + // no verdict, really; just expecting that there is no exception raised + } + + @Test + public void testAddingManyWindFixesWhileReadingLikeCrazy() throws InterruptedException { + Thread inserter = new Thread("Inserter thread in "+getClass().getName()+".testAddingManyWindFixesWhileReadingLikeCrazy") { + @Override + public void run() { + TimePoint now = MillisecondsTimePoint.now(); + for (int i = 0; i < 100000; i++) { + final WindImpl wind = new WindImpl(new DegreePosition(49, 8), now, new KnotSpeedWithBearingImpl( + /* speedInKnots */12., new DegreeBearingImpl(47))); + addMeasuredWindFix(wind); + now = now.plus(10); + } + logger.info("Inserter thread done"); + } + }; + Runnable readerRunnable = new Runnable() { + @Override + public void run() { + TimePoint now = MillisecondsTimePoint.now(); + for (int i=0; i<100000; i++) { + estimationTrack.getAveragedWind(/* position */ null, now); + now = now.plus(10); // the cache quantizes to a full second; so not all time points will automatically fetch uncached values + } + logger.info(Thread.currentThread().getName()+" done"); + } + }; + inserter.start(); + List readers = new ArrayList(); + for (int i = 0; i < 4; i++) { + Thread reader = new Thread(readerRunnable, "Reader thread "+i+" in " + getClass().getName() + + ".testAddingManyWindFixesWhileReadingLikeCrazy"); + reader.start(); + readers.add(reader); + } + inserter.join(); + for (Thread reader : readers) { + reader.join(); + } + } + + /** + * Adds the wind fix to the {@link #measuredTrack} and explicitly notifies {@link #estimationTrack} which is + * not a listener on the mocked tracked race or the wind track and therefore otherwise wouldn't receive the fix + */ + private void addMeasuredWindFix(final WindImpl wind) { + measuredTrack.add(wind); + estimationTrack.windDataReceived(wind, realWindSource); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnConstructedTracksTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnConstructedTracksTest.java index cd505684e59..2ebe9b2c901 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnConstructedTracksTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnConstructedTracksTest.java @@ -31,7 +31,6 @@ import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.NauticalMileDistance; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixMoving; @@ -45,6 +44,7 @@ import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.TrackBasedEstimationWindTrackImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sse.common.Util; public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest { private List competitors; @@ -116,14 +116,14 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest // produces estimated bearing of 170deg; result should be averaged between 170 (estimation) and 180 (web) deg setBearingForCompetitor(competitors.get(0), now, 305); setBearingForCompetitor(competitors.get(1), now, 35); - WindWithConfidence> combinedWindDirectionMinClusterSizeOne = getTrackedRace() + WindWithConfidence> combinedWindDirectionMinClusterSizeOne = getTrackedRace() .getWindWithConfidence(/* position */null, now); final double combinedDegreesMinClusterSizeOne = combinedWindDirectionMinClusterSizeOne.getObject().getBearing().getDegrees(); assertTrue(combinedDegreesMinClusterSizeOne > 170 && combinedDegreesMinClusterSizeOne < 180); // now produce a minimum cluster size of 2, raising the estimation's confidence setBearingForCompetitor(competitors.get(2), now, 305); setBearingForCompetitor(competitors.get(3), now, 35); - WindWithConfidence> combinedWindDirectionMinClusterSizeTwo = getTrackedRace() + WindWithConfidence> combinedWindDirectionMinClusterSizeTwo = getTrackedRace() .getWindWithConfidence(/* position */null, now); final double combinedDegreesNow = combinedWindDirectionMinClusterSizeTwo.getObject().getBearing().getDegrees(); assertTrue(combinedDegreesNow > 170 && combinedDegreesNow < 180); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnKielerWoche505Race2DataTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnKielerWoche505Race2DataTest.java index 4f43f57c522..f523e40d170 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnKielerWoche505Race2DataTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnKielerWoche505Race2DataTest.java @@ -23,7 +23,6 @@ import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixMoving; @@ -33,6 +32,7 @@ import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.impl.TrackBasedEstimationWindTrackImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tractracadapter.ReceiverType; +import com.sap.sse.common.Util; public class WindEstimationOnKielerWoche505Race2DataTest extends OnlineTracTracBasedTest { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnStoredTracksTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnStoredTracksTest.java index f21254c478d..97fce974f39 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnStoredTracksTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnStoredTracksTest.java @@ -20,13 +20,13 @@ 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.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.impl.GPSFixImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sse.common.Util; public class WindEstimationOnStoredTracksTest extends StoredTrackBasedTestWithTrackedRace { // ---------------- BearingWithConfidenceCluster ----------------- diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java index bf1c83dd34e..73265a21c64 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java @@ -37,8 +37,8 @@ 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.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.Wind; @@ -49,6 +49,7 @@ import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tracking.impl.WindTrackImpl; +import com.sap.sse.common.Util; public class WindTest { private static final int AVERAGING_INTERVAL_MILLIS = 30000 /* 30s averaging interval */; @@ -269,7 +270,7 @@ public class WindTest { new RaceDefinitionImpl("Test Race", new CourseImpl("Test Course", Arrays.asList(new Waypoint[] { w1, w2, w3 })), boatClass, Collections.singleton(competitor)), Collections. emptyList(), - EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 1000, + EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 1000, /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000); TimePoint start = MillisecondsTimePoint.now(); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java index 5ef2ec4c924..4b5aa7fcfc4 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java @@ -25,7 +25,6 @@ import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.markpassingcalculation.Candidate; import com.sap.sailing.domain.markpassingcalculation.CandidateChooser; @@ -40,6 +39,7 @@ import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tractracadapter.ReceiverType; +import com.sap.sse.common.Util; public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest { @@ -190,7 +190,7 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest { incorrect += incorrectPasses; skipped += wronglyNotComputed; extra += wronglyComputed; - assertTrue(accuracy >= 0.8); + assertTrue("Expected accuracy to be at least 0.8 but was "+accuracy, accuracy >= 0.8); } private void testMiddleOfRace(int waypoint) { @@ -202,46 +202,44 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest { for (Competitor c : getRace().getCompetitors()) { TimePoint startTime; TimePoint secondPass; - try { + if (givenPasses.get(c).get(start) != null && givenPasses.get(c).get(second) != null) { startTime = givenPasses.get(c).get(start).getTimePoint(); secondPass = givenPasses.get(c).get(second).getTimePoint(); - } catch (NullPointerException e) { - continue; - } - TimePoint delta = new MillisecondsTimePoint(startTime.plus(secondPass.asMillis()).asMillis() / 2); - List fixes = new ArrayList(); - try { - getTrackedRace().getTrack(c).lockForRead(); - for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) { - if (fix.getTimePoint().before(delta)) { - fixes.add(fix); + TimePoint delta = new MillisecondsTimePoint(startTime.plus(secondPass.asMillis()).asMillis() / 2); + List fixes = new ArrayList(); + try { + getTrackedRace().getTrack(c).lockForRead(); + for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) { + if (fix.getTimePoint().before(delta)) { + fixes.add(fix); + } + } + } finally { + getTrackedRace().getTrack(c).unlockAfterRead(); + } + Util.Pair, Iterable> f = finder.getCandidateDeltas(c, fixes); + chooser.calculateMarkPassDeltas(c, f); + boolean gotPassed = true; + boolean gotOther = false; + // System.out.println(c); + for (Waypoint w : getRace().getCourse().getWaypoints()) { + MarkPassing old = givenPasses.get(c).get(w); + MarkPassing newm = getTrackedRace().getMarkPassing(c, w); + // System.out.println(newm); + if (waypoints.indexOf(w) <= waypoint) { + if ((old == null) != (newm == null)) { + gotPassed = false; + } + } else { + if (newm != null) { + gotOther = true; + } } } - } finally { - getTrackedRace().getTrack(c).unlockAfterRead(); - } - Pair, Iterable> f = finder.getCandidateDeltas(c, fixes); - chooser.calculateMarkPassDeltas(c, f); - boolean gotPassed = true; - boolean gotOther = false; - // System.out.println(c); - for (Waypoint w : getRace().getCourse().getWaypoints()) { - MarkPassing old = givenPasses.get(c).get(w); - MarkPassing newm = getTrackedRace().getMarkPassing(c, w); - // System.out.println(newm); - if (waypoints.indexOf(w) <= waypoint) { - if ((old == null) != (newm == null)) { - gotPassed = false; - } - } else { - if (newm != null) { - gotOther = true; - } + if (!gotPassed || gotOther) { + mistakes++; } } - if (!gotPassed || gotOther) { - mistakes++; - } } Assert.assertTrue(mistakes == 0); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java index a31c44bb931..1c7bfd6c19c 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java @@ -1,92 +1,93 @@ -package com.sap.sailing.domain.test.markpassing; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.UUID; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.ControlPointWithTwoMarks; -import com.sap.sailing.domain.base.Course; -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.base.Sideline; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.CompetitorImpl; -import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; -import com.sap.sailing.domain.base.impl.CourseAreaImpl; -import com.sap.sailing.domain.base.impl.CourseImpl; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.MarkImpl; -import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.base.impl.WaypointImpl; -import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.TimePoint; -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.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.leaderboard.impl.HighPoint; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; -import com.sap.sailing.domain.tracking.impl.EmptyWindStore; -import com.sap.sailing.domain.tracking.impl.GPSFixImpl; -import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; -import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl; - -public class AbstractMockedRaceMarkPassingTest { - protected Competitor bob = new CompetitorImpl("Bob", "Bob", null, null, null); - protected Competitor joe = new CompetitorImpl("Joe", "Joe", null, null, null); - protected Competitor mike = new CompetitorImpl("Mike", "Mike", null, null, null); - - protected Mark m = new MarkImpl("black"); - protected Mark m2 = new MarkImpl("white 1"); - protected Mark m3 = new MarkImpl("white 2"); - - protected List waypoints = new ArrayList<>(); - protected DynamicTrackedRace trackedRace; - protected long time; - protected Random rnd = new Random(); - - public AbstractMockedRaceMarkPassingTest() { - ControlPointWithTwoMarks cp = new ControlPointWithTwoMarksImpl(m2, m3, "cp"); - Waypoint w1 = new WaypointImpl(cp, PassingInstruction.Line); - Waypoint w2 = new WaypointImpl(m, PassingInstruction.Port); - Waypoint w3 = new WaypointImpl(cp, PassingInstruction.Line); - Regatta r = new RegattaImpl("regatta", new BoatClassImpl("boat", true), Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")), - new ArrayList(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5))); - Course course = new CourseImpl("course", Arrays.asList(w1, w2, w3)); - RaceDefinition race = new RaceDefinitionImpl("Performance Race", course, new BoatClassImpl("boat", true), Arrays.asList(bob)); - trackedRace = new DynamicTrackedRaceImpl(new TrackedRegattaImpl(r), race, new ArrayList(), new EmptyWindStore(), 0, 10000, 10000); - trackedRace.setStartTimeReceived(new MillisecondsTimePoint(System.currentTimeMillis() - 120000)); - List tps = Arrays.asList(new MillisecondsTimePoint(System.currentTimeMillis()), new MillisecondsTimePoint(System.currentTimeMillis() - 30000), - new MillisecondsTimePoint(System.currentTimeMillis() + 30000)); - List> pos = Arrays.asList(new Pair(m, new DegreePosition(0, 0)), new Pair(m2, new DegreePosition(-0.001, -0.00005)), - new Pair(m3, new DegreePosition(-0.001, 0.00005))); - for (Pair pair : pos) { - for (TimePoint t : tps) { - trackedRace.recordFix(pair.getA(), new GPSFixImpl(pair.getB(), t)); - } - } - for (Waypoint w : trackedRace.getRace().getCourse().getWaypoints()) { - waypoints.add(w); - } - } - - protected GPSFixMoving rndFix() { - DegreePosition position = new DegreePosition(37.8878 + rnd.nextDouble() * 0.0019, -122.268 - rnd.nextDouble() * 0.012); - TimePoint p = new MillisecondsTimePoint((long) (System.currentTimeMillis() - 300000 + (Math.random() * (7800000)))); - SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360))); - - return new GPSFixMovingImpl(position, p, speed); - } -} +package com.sap.sailing.domain.test.markpassing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.UUID; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.ControlPointWithTwoMarks; +import com.sap.sailing.domain.base.Course; +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.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.CompetitorImpl; +import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.MarkImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.PassingInstruction; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.TimePoint; +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.MillisecondsTimePoint; +import com.sap.sailing.domain.leaderboard.impl.HighPoint; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; +import com.sap.sailing.domain.tracking.impl.GPSFixImpl; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl; +import com.sap.sse.common.Util; + +public class AbstractMockedRaceMarkPassingTest { + protected Competitor bob = new CompetitorImpl("Bob", "Bob", null, null, null); + protected Competitor joe = new CompetitorImpl("Joe", "Joe", null, null, null); + protected Competitor mike = new CompetitorImpl("Mike", "Mike", null, null, null); + + protected Mark m = new MarkImpl("black"); + protected Mark m2 = new MarkImpl("white 1"); + protected Mark m3 = new MarkImpl("white 2"); + + protected List waypoints = new ArrayList<>(); + protected DynamicTrackedRace trackedRace; + protected long time; + protected Random rnd = new Random(); + + public AbstractMockedRaceMarkPassingTest() { + ControlPointWithTwoMarks cp = new ControlPointWithTwoMarksImpl(m2, m3, "cp"); + Waypoint w1 = new WaypointImpl(cp, PassingInstruction.Line); + Waypoint w2 = new WaypointImpl(m, PassingInstruction.Port); + Waypoint w3 = new WaypointImpl(cp, PassingInstruction.Line); + Regatta r = new RegattaImpl("regatta", new BoatClassImpl("boat", true), Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")), + new ArrayList(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5))); + Course course = new CourseImpl("course", Arrays.asList(w1, w2, w3)); + RaceDefinition race = new RaceDefinitionImpl("Performance Race", course, new BoatClassImpl("boat", true), Arrays.asList(bob)); + trackedRace = new DynamicTrackedRaceImpl(new TrackedRegattaImpl(r), race, new ArrayList(), new EmptyWindStore(), EmptyGPSFixStore.INSTANCE, 0, 10000, 10000); + trackedRace.setStartTimeReceived(new MillisecondsTimePoint(System.currentTimeMillis() - 120000)); + List tps = Arrays.asList(new MillisecondsTimePoint(System.currentTimeMillis()), new MillisecondsTimePoint(System.currentTimeMillis() - 30000), + new MillisecondsTimePoint(System.currentTimeMillis() + 30000)); + List> pos = Arrays.asList(new Util.Pair(m, new DegreePosition(0, 0)), new Util.Pair(m2, new DegreePosition(-0.001, -0.00005)), + new Util.Pair(m3, new DegreePosition(-0.001, 0.00005))); + for (Util.Pair pair : pos) { + for (TimePoint t : tps) { + trackedRace.recordFix(pair.getA(), new GPSFixImpl(pair.getB(), t)); + } + } + for (Waypoint w : trackedRace.getRace().getCourse().getWaypoints()) { + waypoints.add(w); + } + } + + protected GPSFixMoving rndFix() { + DegreePosition position = new DegreePosition(37.8878 + rnd.nextDouble() * 0.0019, -122.268 - rnd.nextDouble() * 0.012); + TimePoint p = new MillisecondsTimePoint((long) (System.currentTimeMillis() - 300000 + (Math.random() * (7800000)))); + SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360))); + + return new GPSFixMovingImpl(position, p, speed); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/CandidateFinderTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/CandidateFinderTest.java index f0c4d9d399d..6380a98c7c2 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/CandidateFinderTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/CandidateFinderTest.java @@ -11,11 +11,11 @@ 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.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.markpassingcalculation.CandidateFinder; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sse.common.Util; public class CandidateFinderTest extends AbstractMockedRaceMarkPassingTest { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/FlorianopolisMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/FlorianopolisMarkPassingTest.java index 9432a1295a8..54e73723ee0 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/FlorianopolisMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/FlorianopolisMarkPassingTest.java @@ -1,147 +1,119 @@ -package com.sap.sailing.domain.test.markpassing; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.text.ParseException; - -import org.junit.Test; - -public class FlorianopolisMarkPassingTest extends AbstractMarkPassingTest { - - public FlorianopolisMarkPassingTest() throws MalformedURLException, URISyntaxException { - super(); - } - - @Test - public void testRace1() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("1"); - } - - @Test - public void testRace2() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("2"); - } - - @Test - public void testRace3() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("3"); - } - - @Test - public void testRace4() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("4"); - } - - @Test - public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("5"); - } - - @Test - public void testRace6() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("6"); - } - - @Test - public void testRace7() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("7"); - } - - @Test - public void testRace8() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("8"); - } - - @Test - public void testRace10() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("10"); - } - - @Test - public void testRace11() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("11"); - } - - @Test - public void testRace12() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("12"); - } - - @Test - public void testRace13() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("13"); - } - - @Test - public void testRace14() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("14"); - } - - @Test - public void testRace16() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("16"); - } - - @Test - public void testRace17() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("17"); - } - - @Test - public void testRace19() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("19"); - } - - @Test - public void testRace22() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("22"); - } - - @Test - public void testRace24() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("24"); - } - - /* - * Alinghi acting wierd - * - * @Test public void testRace23() throws IOException, InterruptedException, URISyntaxException { testRace(23); } - */ - - /* - * Wrong start from TracTrac fails start test. - * - * @Test public void testRace18() throws IOException, InterruptedException, URISyntaxException { testRace("18"); } - */ - - /* - * All mark data seems to be missing - * - * @Test public void testRace9() throws IOException, InterruptedException, URISyntaxException { testRace(9); } - * - * @Test public void testRace20() throws IOException, InterruptedException, URISyntaxException { testRace(20); } - */ - - /* - * Missing static mark info leads to skipping of all finishes. - * - * @Test public void testRace25() throws IOException, InterruptedException, URISyntaxException { testRace(25); } - * - * @Test public void testRace21() throws IOException, InterruptedException, URISyntaxException { testRace(21); } - * - * @Test public void testRace15() throws IOException, InterruptedException, URISyntaxException { testRace(15); } - * - * @Test public void testRace26() throws IOException, InterruptedException, URISyntaxException { testRace(26); } - */ - - @Override - protected String getExpectedEventName() { - return "ESS Florianopolis 2013"; - } - - @Override - protected String getFileName() { - return "event_20131112_ESSFlorian-Race_"; - } -} +package com.sap.sailing.domain.test.markpassing; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.text.ParseException; + +import org.junit.Test; + +public class FlorianopolisMarkPassingTest extends AbstractMarkPassingTest { + + public FlorianopolisMarkPassingTest() throws MalformedURLException, URISyntaxException { + super(); + } + + @Test + public void testRace3() throws IOException, InterruptedException, URISyntaxException, ParseException { + testRace("3"); + } + + @Test + public void testRace4() throws IOException, InterruptedException, URISyntaxException, ParseException { + testRace("4"); + } + + /* + * Test ignored on master branch to quicken the hudson build. + * + * @Test public void testRace1() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("1"); } + * + * @Test public void testRace2() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("2"); } + * + * @Test public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("5"); } + * + * @Test public void testRace6() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("6"); } + * + * @Test public void testRace7() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("7"); } + * + * @Test public void testRace8() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("8"); } + * + * @Test public void testRace10() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("10"); } + * + * @Test public void testRace11() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("11"); } + * + * @Test public void testRace12() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("12"); } + * + * @Test public void testRace13() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("13"); } + * + * @Test public void testRace14() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("14"); } + * + * @Test public void testRace16() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("16"); } + * + * @Test public void testRace17() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("17"); } + * + * @Test public void testRace19() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("19"); } + * + * @Test public void testRace22() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("22"); } + * + * @Test public void testRace24() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("24"); } + */ + + /* + * Alinghi acting wierd + * + * @Test public void testRace23() throws IOException, InterruptedException, URISyntaxException { testRace(23); } + */ + + /* + * Wrong start from TracTrac fails start test. + * + * @Test public void testRace18() throws IOException, InterruptedException, URISyntaxException { testRace("18"); } + */ + + /* + * All mark data seems to be missing + * + * @Test public void testRace9() throws IOException, InterruptedException, URISyntaxException { testRace(9); } + * + * @Test public void testRace20() throws IOException, InterruptedException, URISyntaxException { testRace(20); } + */ + + /* + * Missing static mark info leads to skipping of all finishes. + * + * @Test public void testRace25() throws IOException, InterruptedException, URISyntaxException { testRace(25); } + * + * @Test public void testRace21() throws IOException, InterruptedException, URISyntaxException { testRace(21); } + * + * @Test public void testRace15() throws IOException, InterruptedException, URISyntaxException { testRace(15); } + * + * @Test public void testRace26() throws IOException, InterruptedException, URISyntaxException { testRace(26); } + */ + + @Override + protected String getExpectedEventName() { + return "ESS Florianopolis 2013"; + } + + @Override + protected String getFileName() { + return "event_20131112_ESSFlorian-Race_"; + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/KW470MarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/KW470MarkPassingTest.java index 976f9709170..1a710b8e9f4 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/KW470MarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/KW470MarkPassingTest.java @@ -17,16 +17,16 @@ public class KW470MarkPassingTest extends AbstractMarkPassingTest { public void testRace4() throws IOException, InterruptedException, URISyntaxException, ParseException { testRace("4"); } - - @Test - public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("5"); - } - - @Test - public void testRace6() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("6"); - } + + /* + * Ignored on master branch + * + * @Test public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("5"); } + * + * @Test public void testRace6() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("6"); } + */ @Override protected String getFileName() { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java index 2e385cff10c..17a88657663 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java @@ -13,7 +13,6 @@ import org.junit.AfterClass; import org.junit.Test; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.markpassingcalculation.AbstractCandidateFinder; import com.sap.sailing.domain.markpassingcalculation.Candidate; import com.sap.sailing.domain.markpassingcalculation.CandidateChooser; @@ -23,20 +22,19 @@ import com.sap.sailing.domain.test.measurements.MeasurementCase; import com.sap.sailing.domain.test.measurements.MeasurementXMLFile; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sse.common.Util; public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMarkPassingTest { private static LinkedHashMap result = new LinkedHashMap<>(); - private static String className; public MarkPassingCalculatorPerformanceTest() { super(); - className = getClass().getName(); } @AfterClass public static void createXMLFile() throws IOException { - MeasurementXMLFile performanceReport = new MeasurementXMLFile("TEST-MarkPassingCaculatorPerformanceTest.xml", "MarkPassingCaculatorPerformanceTest", className); + MeasurementXMLFile performanceReport = new MeasurementXMLFile(MarkPassingCalculatorPerformanceTest.class); for (String key : result.keySet()) { MeasurementCase performanceReportCase = performanceReport.addCase(key); performanceReportCase.addMeasurement(new Measurement(key, result.get(key))); @@ -74,7 +72,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark } time = System.currentTimeMillis(); CandidateChooser c = new CandidateChooser(trackedRace); - c.calculateMarkPassDeltas(bob, new Pair, Iterable>(newCans, new ArrayList())); + c.calculateMarkPassDeltas(bob, new Util.Pair, Iterable>(newCans, new ArrayList())); time = System.currentTimeMillis() - time; ArrayList times = new ArrayList<>(); for (int i = 0; i < 100; i++) { @@ -84,7 +82,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark ne.add(randomCan()); } time = System.currentTimeMillis(); - c.calculateMarkPassDeltas(bob, new Pair, Iterable>(ne, old)); + c.calculateMarkPassDeltas(bob, new Util.Pair, Iterable>(ne, old)); times.add(System.currentTimeMillis() - time); old = ne; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/OBMR2012VRMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/OBMR2012VRMarkPassingTest.java index 8e82227a25b..6fb6a2763f0 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/OBMR2012VRMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/OBMR2012VRMarkPassingTest.java @@ -9,7 +9,7 @@ import org.junit.Test; public class OBMR2012VRMarkPassingTest extends AbstractMarkPassingTest { - public OBMR2012VRMarkPassingTest() throws MalformedURLException, URISyntaxException { + public OBMR2012VRMarkPassingTest() throws MalformedURLException, URISyntaxException { super(); } @@ -17,57 +17,43 @@ public class OBMR2012VRMarkPassingTest extends AbstractMarkPassingTest { public void testRaceFr11() throws IOException, InterruptedException, URISyntaxException, ParseException { testRace("Fr_Fleet_1_1"); } - + @Test public void testRaceFr12() throws IOException, InterruptedException, URISyntaxException, ParseException { testRace("Fr_Fleet_1_2"); } - - @Test - public void testRaceFr13() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("Fr_Fleet_1_3"); - } - - @Test - public void testRaceFr21() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("Fr_Fleet_2_1"); - } - - @Test - public void testRaceFr22() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("Fr_Fleet_2_2"); - } - - @Test - public void testRaceFr23() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("Fr_Fleet_2_3"); - } - - @Test - public void testRaceSaVm11() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("Sa-Vm_Fleet_1_1"); - } - - @Test - public void testRaceSaVm12() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("Sa-Vm_Fleet_1_2"); - } - - @Test - public void testRaceSaVm13() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("Sa-Vm_Fleet_1_3"); - } - - @Test - public void testRaceSaVm21() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("Sa-Vm_Fleet_2_1"); - } - - @Test - public void testRaceSaVm22() throws IOException, InterruptedException, URISyntaxException, ParseException { - testRace("Sa-Vm_Fleet_2_2"); - } - + + /* + * Ignored on master branch. + * + * @Test public void testRaceFr13() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("Fr_Fleet_1_3"); } + * + * @Test public void testRaceFr21() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("Fr_Fleet_2_1"); } + * + * @Test public void testRaceFr22() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("Fr_Fleet_2_2"); } + * + * @Test public void testRaceFr23() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("Fr_Fleet_2_3"); } + * + * @Test public void testRaceSaVm11() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("Sa-Vm_Fleet_1_1"); } + * + * @Test public void testRaceSaVm12() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("Sa-Vm_Fleet_1_2"); } + * + * @Test public void testRaceSaVm13() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("Sa-Vm_Fleet_1_3"); } + * + * @Test public void testRaceSaVm21() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("Sa-Vm_Fleet_2_1"); } + * + * @Test public void testRaceSaVm22() throws IOException, InterruptedException, URISyntaxException, ParseException { + * testRace("Sa-Vm_Fleet_2_2"); } + */ + @Override protected String getExpectedEventName() { return "OBMR 2012"; 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 072598f56af..f192eb1c1b0 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 @@ -8,7 +8,7 @@ import java.io.Writer; import java.util.HashSet; import java.util.Set; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sse.common.Util; /** * A utility for generating output files that contain an artificial test result with a <system-out> tag that has @@ -27,6 +27,10 @@ public class MeasurementXMLFile { private final Set cases; private final String testClassName; + public MeasurementXMLFile(Class testClass) { + this("TEST-"+testClass.getSimpleName()+".xml", testClass.getSimpleName(), testClass.getName()); + } + public MeasurementXMLFile(String reportFileName, String testSuiteName, String testClassName) { this.reportFileName = reportFileName; this.testSuiteName = testSuiteName; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index f22fd0d1a57..0f0f6598464 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -32,9 +32,9 @@ import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.tracking.CourseDesignChangedListener; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; @@ -57,6 +57,7 @@ import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.domain.tracking.impl.WindTrackImpl; +import com.sap.sse.common.Util; public class MockedTrackedRace implements DynamicTrackedRace { private static final long serialVersionUID = 5827912985564121181L; @@ -220,7 +221,8 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void addListener(RaceChangeListener listener, boolean notifyAboutWindFixesAlreadyLoaded) { + public void addListener(RaceChangeListener listener, boolean notifyAboutWindFixesAlreadyLoaded, + boolean notifyAboutGPSFixesAlreadyLoaded) { // TODO Auto-generated method stub } @@ -517,7 +519,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, - long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, + GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { // TODO Auto-generated method stub return null; @@ -616,7 +618,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, Iterable windSourcesToExclude) { // TODO Auto-generated method stub return null; @@ -629,7 +631,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { // TODO Auto-generated method stub return null; } @@ -665,13 +667,13 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Iterable>> getMarkPassingsTimes() { + public Iterable>> getMarkPassingsTimes() { // TODO Auto-generated method stub return null; } @Override - public Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { // TODO Auto-generated method stub return null; } @@ -736,14 +738,14 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Distance getAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatestAnalyses) + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatestAnalyses) throws NoWindException { // TODO Auto-generated method stub return null; } @Override - public void waitUntilWindLoadingComplete() throws InterruptedException { + public void waitUntilLoadingFromWindStoreComplete() throws InterruptedException { // TODO Auto-generated method stub } @@ -930,6 +932,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } + @Override + public GPSFixStore getGPSFixStore() { + // TODO Auto-generated method stub + return null; + } + @Override public Bearing getCrossingBearing(Waypoint w, TimePoint t) { // TODO Auto-generated method stub @@ -937,9 +945,29 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { + public Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { // TODO Auto-generated method stub return null; } + @Override + public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalysis) throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, + boolean upwindOnly, boolean waitForLatestAnalysis) throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void waitUntilLoadingFromGPSFixStoreComplete() throws InterruptedException { + // TODO Auto-generated method stub + + } + } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index bcf823ee25b..b30eef991e0 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -24,8 +24,8 @@ import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.tracking.CourseDesignChangedListener; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; @@ -44,6 +44,7 @@ import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sse.common.Util; /** * Simple mock for {@link TrackedRace} for leaderboard testing; the leaderboard only requests {@link #hasStarted(TimePoint)} and @@ -97,7 +98,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { } @Override - public Iterable>> getMarkPassingsTimes() { + public Iterable>> getMarkPassingsTimes() { // TODO Auto-generated method stub return null; } @@ -352,7 +353,8 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { } @Override - public void addListener(RaceChangeListener listener, boolean notifyAboutWindFixesAlreadyLoaded) { + public void addListener(RaceChangeListener listener, boolean notifyAboutWindFixesAlreadyLoaded, + boolean notifyAboutGPSFixesAlreadyLoaded) { // TODO Auto-generated method stub } @@ -377,7 +379,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { // TODO Auto-generated method stub return null; } @@ -389,7 +391,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, Iterable windSourcesToExclude) { // TODO Auto-generated method stub return null; @@ -408,7 +410,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { } @Override - public Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { // TODO Auto-generated method stub return null; } @@ -431,14 +433,14 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { } @Override - public Distance getAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatestAnalyses) + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatestAnalyses) throws NoWindException { // TODO Auto-generated method stub return null; } @Override - public void waitUntilWindLoadingComplete() { + public void waitUntilLoadingFromWindStoreComplete() { // TODO Auto-generated method stub } @@ -536,7 +538,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { return null; } - @Override + @Override public Distance getDistanceToStartLine(Competitor competitor, long millisecondsBeforeRaceStart) { // TODO Auto-generated method stub return null; @@ -547,14 +549,14 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { // TODO Auto-generated method stub return null; } - + public void addStartTimeChangedListener(StartTimeChangedListener listener) { // TODO Auto-generated method stub } @Override - public TimePoint getStartTimeReceived() { + public TimePoint getStartTimeReceived() { // TODO Auto-generated method stub return null; } @@ -595,4 +597,30 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { return null; } + @Override + public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalysis) throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, + boolean upwindOnly, boolean waitForLatestAnalysis) throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public GPSFixStore getGPSFixStore() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void waitUntilLoadingFromGPSFixStoreComplete() throws InterruptedException { + // TODO Auto-generated method stub + + } + } diff --git a/java/com.sap.sailing.domain.tractracadapter.persistence/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.tractracadapter.persistence/META-INF/MANIFEST.MF index 2661208dc39..05a553a24c9 100755 --- a/java/com.sap.sailing.domain.tractracadapter.persistence/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.tractracadapter.persistence/META-INF/MANIFEST.MF @@ -10,5 +10,6 @@ Require-Bundle: com.mongodb.driver;bundle-version="2.6.2", com.sap.sailing.mongodb, com.sap.sailing.domain.tractracadapter, com.sap.sailing.domain, - com.sap.sailing.domain.common + com.sap.sailing.domain.common, + com.sap.sse.common Export-Package: com.sap.sailing.domain.tractracadapter.persistence diff --git a/java/com.sap.sailing.domain.tractracadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.tractracadapter.test/META-INF/MANIFEST.MF index f56efec327a..9fa3a41775e 100755 --- a/java/com.sap.sailing.domain.tractracadapter.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.tractracadapter.test/META-INF/MANIFEST.MF @@ -7,4 +7,5 @@ Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Fragment-Host: com.sap.sailing.domain.tractracadapter Require-Bundle: org.junit4;bundle-version="4.8.2", - com.tractrac.clientmodule;bundle-version="2.0.6" + com.tractrac.clientmodule;bundle-version="2.0.6", + com.sap.sse.common diff --git a/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHP49erFX2013Test.java b/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHP49erFX2013Test.java index 19fc0858eaf..419bab76a45 100755 --- a/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHP49erFX2013Test.java +++ b/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHP49erFX2013Test.java @@ -8,12 +8,12 @@ import java.util.UUID; import org.junit.Before; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP.Competitor; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP.Event; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP.Race; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP.Route; +import com.sap.sse.common.Util; public class ClientParamsPHP49erFX2013Test extends AbstractClientParamsPHPTest { diff --git a/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPArenalTest.java b/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPArenalTest.java index 0a0221acb86..f49a9234783 100755 --- a/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPArenalTest.java +++ b/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPArenalTest.java @@ -11,11 +11,11 @@ import java.util.UUID; import org.junit.Before; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP.ControlPoint; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP.Route; import com.sap.sailing.domain.tractracadapter.impl.ControlPointAdapter; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.Event; import com.tractrac.clientmodule.setup.KeyValue; diff --git a/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPLahaina3Test.java b/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPLahaina3Test.java index 1096d3a8826..95b184d7b93 100755 --- a/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPLahaina3Test.java +++ b/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPLahaina3Test.java @@ -10,10 +10,10 @@ import java.util.UUID; import org.junit.Before; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP.ControlPoint; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP.Mark; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP.Route; +import com.sap.sse.common.Util; public class ClientParamsPHPLahaina3Test extends AbstractClientParamsPHPTest { diff --git a/java/com.sap.sailing.domain.tractracadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.tractracadapter/META-INF/MANIFEST.MF index 9ea31eead5f..5c4e1963b19 100755 --- a/java/com.sap.sailing.domain.tractracadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.tractracadapter/META-INF/MANIFEST.MF @@ -9,7 +9,8 @@ Require-Bundle: com.sap.sailing.domain, com.tractrac.clientmodule;bundle-version="2.0.0", com.googlecode.java-diff-utils;bundle-version="1.3.0", com.sap.sailing.domain.common, - com.sap.sailing.server.gateway.serialization;bundle-version="1.0.0" + com.sap.sailing.server.gateway.serialization;bundle-version="1.0.0", + com.sap.sse.common Export-Package: com.sap.sailing.domain.tractracadapter, com.sap.sailing.domain.tractracadapter.impl;x-friends:="com.sap.sailing.domain.test,com.sap.sailing.domain.swisstimingadapter.test,com.sap.sailing.server.test" Bundle-ActivationPolicy: lazy 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 7c8840df88f..2acff0ec20b 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 @@ -23,8 +23,8 @@ import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; @@ -41,6 +41,7 @@ import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl; import com.sap.sailing.domain.tractracadapter.impl.RaceCourseReceiver; import com.sap.sailing.domain.tractracadapter.impl.Simulator; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.Competitor; import com.tractrac.clientmodule.Event; import com.tractrac.clientmodule.Race; @@ -64,7 +65,7 @@ public interface DomainFactory { com.sap.sailing.domain.common.TimePoint createTimePoint(long timestamp); - Course createCourse(String name, Iterable> controlPoints); + Course createCourse(String name, Iterable> controlPoints); Sideline createSideline(String name, Iterable controlPoints); @@ -132,7 +133,7 @@ public interface DomainFactory { */ TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, - WindStore windStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, + WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, URISyntaxException; /** @@ -142,7 +143,7 @@ public interface DomainFactory { */ RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, - WindStore windStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, + WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, URISyntaxException; BoatClass getOrCreateBoatClass(String competitorClassName); @@ -231,7 +232,7 @@ public interface DomainFactory { * of waypoints. The waypoints are created from the control points and represent usages of the control points * in a course. A single control point may be used more than once in a course's list of waypoints. */ - void updateCourseWaypoints(Course courseToUpdate, Iterable> controlPoints) throws PatchFailedException; + void updateCourseWaypoints(Course courseToUpdate, Iterable> controlPoints) throws PatchFailedException; TracTracConfiguration createTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI, String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword); @@ -241,14 +242,14 @@ public interface DomainFactory { * timeoutInMilliseconds milliseconds have passed and the race definition is found not to have shown up * until then, null is returned. The unblocking may be deferred even beyond * timeoutInMilliseconds in case no modifications happen on the set of races cached by this factory. - * @param raceId TODO + * * @param timeoutInMilliseconds - * passing -1 means an infinite timeout; 0 means return immediately with null as result if no - * race definition is found for race. + * passing -1 means an infinite timeout; 0 means return immediately with null as result if + * no race definition is found for race. */ RaceDefinition getAndWaitForRaceDefinition(UUID raceId, long timeoutInMilliseconds); - Pair, BoatClass> getCompetitorsAndDominantBoatClass(Race race); + Util.Pair, BoatClass> getCompetitorsAndDominantBoatClass(Race race); RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/MetadataParser.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/MetadataParser.java index d01c6a244d8..b958c6a03b8 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/MetadataParser.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/MetadataParser.java @@ -4,10 +4,10 @@ import java.io.Serializable; import java.util.Map; import com.sap.sailing.domain.common.MarkType; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.tractracadapter.impl.ClientParamsPHP; import com.sap.sailing.domain.tractracadapter.impl.RaceCourseReceiver; +import com.sap.sse.common.Named; /** * TracTrac objects can be augmented by what TracTrac calls a "DataSheet." These optional data sheets can provide diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java index ab116e5149e..f4003b3cc3f 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java @@ -13,11 +13,11 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.tracking.RacesHandle; import com.sap.sailing.domain.tracking.TrackerManager; import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sse.common.Util; public interface TracTracAdapter { DomainFactory getTracTracDomainFactory(); @@ -105,7 +105,7 @@ public interface TracTracAdapter { * and {@link #addTracTracRace(TrackerManager, URL, URI, URI, URI, RaceLogStore, WindStore, long, String, String)} will * fetch the JSON and clientparams.php documents to work with up-to-date data. */ - Pair> getTracTracRaceRecords(URL jsonURL, boolean loadClientParams) throws IOException, + Util.Pair> getTracTracRaceRecords(URL jsonURL, boolean loadClientParams) throws IOException, ParseException, org.json.simple.parser.ParseException, URISyntaxException; RaceRecord getSingleTracTracRaceRecord(URL jsonURL, String raceId, boolean loadClientParams) throws Exception; diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracRaceTracker.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracRaceTracker.java index edc61b4717c..9b8a982bf7d 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracRaceTracker.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracRaceTracker.java @@ -3,13 +3,13 @@ package com.sap.sailing.domain.tractracadapter; import java.net.URI; import java.net.URL; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.tracking.RaceTracker; +import com.sap.sse.common.Util; public interface TracTracRaceTracker extends RaceTracker { /** * returns the paramURL, liveURI and storedURI for the TracTrac connection maintained by this tracker */ - Triple getID(); + Util.Triple getID(); } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/AbstractReceiverWithQueue.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/AbstractReceiverWithQueue.java index b3d2ceca9a9..9f1447a27c8 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/AbstractReceiverWithQueue.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/AbstractReceiverWithQueue.java @@ -5,7 +5,6 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.RaceTracker; @@ -13,6 +12,7 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.Receiver; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.Event; import com.tractrac.clientmodule.Race; @@ -27,7 +27,7 @@ import com.tractrac.clientmodule.Race; public abstract class AbstractReceiverWithQueue implements Runnable, Receiver { private static Logger logger = Logger.getLogger(AbstractReceiverWithQueue.class.getName()); - private final LinkedBlockingQueue> queue; + private final LinkedBlockingQueue> queue; private final DomainFactory domainFactory; private final com.tractrac.clientmodule.Event tractracEvent; private final DynamicTrackedRegatta trackedRegatta; @@ -47,7 +47,7 @@ public abstract class AbstractReceiverWithQueue implements Runnable, Re this.trackedRegatta = trackedRegatta; this.domainFactory = domainFactory; this.simulator = simulator; - this.queue = new LinkedBlockingQueue>(); + this.queue = new LinkedBlockingQueue>(); } protected synchronized void setAndStartThread(Thread thread) { @@ -75,7 +75,7 @@ public abstract class AbstractReceiverWithQueue implements Runnable, Re @Override public void stopAfterProcessingQueuedEvents() { - queue.add(new Triple(null, null, null)); + queue.add(new Util.Triple(null, null, null)); } protected Simulator getSimulator() { @@ -102,18 +102,18 @@ public abstract class AbstractReceiverWithQueue implements Runnable, Re }, timeoutInMilliseconds, TimeUnit.MILLISECONDS); } - protected void enqueue(Triple event) { + protected void enqueue(Util.Triple event) { queue.add(event); receivedEventDuringTimeout = true; } - private boolean isStopEvent(Triple event) { + private boolean isStopEvent(Util.Triple event) { return event.getA() == null && event.getB() == null && event.getC() == null; } @Override public void run() { - Triple event = null; + Util.Triple event = null; while (event == null || !isStopEvent(event)) { try { event = queue.take(); @@ -143,7 +143,7 @@ public abstract class AbstractReceiverWithQueue implements Runnable, Re } } - protected abstract void handleEvent(Triple event); + protected abstract void handleEvent(Util.Triple event); /** * Tries to find a {@link TrackedRace} for race in the {@link com.sap.sailing.domain.base.Regatta} diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/ClientParamsPHP.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/ClientParamsPHP.java index 5fdadcd1541..87422cce6c3 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/ClientParamsPHP.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/ClientParamsPHP.java @@ -25,8 +25,6 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; /** @@ -96,7 +94,7 @@ public class ClientParamsPHP { } private final static Pattern defaultPropertyNamePattern = Pattern.compile("([^0-9]*)(([0-9][0-9]*)(.*))?"); - private static Util.Pair getPropertyNamePrefixAndNumber(UUID uuid, String idPropertyName) { + private static com.sap.sse.common.Util.Pair getPropertyNamePrefixAndNumber(UUID uuid, String idPropertyName) { Matcher m = defaultPropertyNamePattern.matcher(idPropertyName); if (m.matches()) { final String propertyNamePrefix; @@ -107,7 +105,7 @@ public class ClientParamsPHP { } else { number = null; } - return new Util.Pair(propertyNamePrefix, number); + return new com.sap.sse.common.Util.Pair(propertyNamePrefix, number); } else { throw new RuntimeException("Unexpected ID property name "+idPropertyName+" that cannot be analyzed"); } @@ -128,7 +126,7 @@ public class ClientParamsPHP { this(uuid, getPropertyNamePrefixAndNumber(uuid, propertiesByID.get(uuid))); } - protected ObjectWithUUID(UUID uuid, Util.Pair propertyNamePrefixAndNumber) { + protected ObjectWithUUID(UUID uuid, com.sap.sse.common.Util.Pair propertyNamePrefixAndNumber) { this.uuid = uuid; propertyNamePrefix = propertyNamePrefixAndNumber.getA(); number = propertyNamePrefixAndNumber.getB(); @@ -171,7 +169,7 @@ public class ClientParamsPHP { public Event(UUID uuid) { // there is only one event in a document, and the race is not numbered; the property name // therefore doesn't follow the usual convention but simply has "Event" as its prefix - super(uuid, new Pair("Event", null)); + super(uuid, new com.sap.sse.common.Util.Pair("Event", null)); } public String getName() { @@ -209,7 +207,7 @@ public class ClientParamsPHP { public Race(UUID uuid) { // there is only one race in a document, and the race is not numbered; the property name // therefore doesn't follow the usual convention but simply has "Race" as its prefix - super(uuid, new Pair("Race", null)); + super(uuid, new com.sap.sse.common.Util.Pair("Race", null)); } public String getName() { diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index c355018b3eb..bc66232a010 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -49,10 +49,10 @@ import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; @@ -75,6 +75,7 @@ import com.sap.sailing.domain.tractracadapter.TracTracConfiguration; import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker; import com.sap.sailing.util.WeakIdentityHashMap; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.CompetitorClass; import com.tractrac.clientmodule.Race; import com.tractrac.clientmodule.RaceCompetitor; @@ -90,15 +91,15 @@ public class DomainFactoryImpl implements DomainFactory { private final Map controlPointCache = new HashMap(); - private final Map, DynamicPerson> personCache = new HashMap<>(); + private final Map, DynamicPerson> personCache = new HashMap<>(); private final Map teamCache = new HashMap<>(); /** * Caches regattas by their name and their boat class's name */ - private final Map, com.sap.sailing.domain.base.Regatta> regattaCache = - new HashMap, com.sap.sailing.domain.base.Regatta>(); + private final Map, com.sap.sailing.domain.base.Regatta> regattaCache = + new HashMap, com.sap.sailing.domain.base.Regatta>(); /** * A cache based on weak references to the TracTrac event, allowing for quick Event lookup as long as the @@ -150,11 +151,11 @@ public class DomainFactoryImpl implements DomainFactory { } @Override - public void updateCourseWaypoints(Course courseToUpdate, Iterable> controlPoints) throws PatchFailedException { - List> newDomainControlPoints = new ArrayList>(); - for (Pair tractracControlPoint : controlPoints) { + public void updateCourseWaypoints(Course courseToUpdate, Iterable> controlPoints) throws PatchFailedException { + List> newDomainControlPoints = new ArrayList>(); + for (com.sap.sse.common.Util.Pair tractracControlPoint : controlPoints) { com.sap.sailing.domain.base.ControlPoint newDomainControlPoint = getOrCreateControlPoint(tractracControlPoint.getA()); - newDomainControlPoints.add(new Pair(newDomainControlPoint, tractracControlPoint.getB())); + newDomainControlPoints.add(new com.sap.sse.common.Util.Pair(newDomainControlPoint, tractracControlPoint.getB())); } courseToUpdate.update(newDomainControlPoints, baseDomainFactory); } @@ -201,9 +202,9 @@ public class DomainFactoryImpl implements DomainFactory { } @Override - public Course createCourse(String name, Iterable> controlPoints) { + public Course createCourse(String name, Iterable> controlPoints) { List waypointList = new ArrayList(); - for (Pair controlPoint: controlPoints) { + for (com.sap.sse.common.Util.Pair controlPoint: controlPoints) { Waypoint waypoint = baseDomainFactory.createWaypoint(getOrCreateControlPoint(controlPoint.getA()), controlPoint.getB()); waypointList.add(waypoint); } @@ -282,7 +283,7 @@ public class DomainFactoryImpl implements DomainFactory { @Override public DynamicPerson getOrCreatePerson(String name, Nationality nationality, UUID competitorId) { synchronized (personCache) { - Pair key = new Pair(name, competitorId); + com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(name, competitorId); DynamicPerson result = personCache.get(key); if (result == null) { result = new PersonImpl(name, nationality, /* date of birth unknown */null, /* description */""); @@ -360,7 +361,7 @@ public class DomainFactoryImpl implements DomainFactory { competitorClassList.add(c.getCompetitorClass()); } BoatClass boatClass = getDominantBoatClass(competitorClassList); - Pair key = new Pair(event.getName(), boatClass == null ? null + com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(event.getName(), boatClass == null ? null : boatClass.getName()); result = regattaCache.get(key); // FIXME When a Regatta is removed from RacingEventService, it isn't removed here. We use a "stale" regatta here. @@ -449,7 +450,7 @@ public class DomainFactoryImpl implements DomainFactory { competitorClassList.add(c.getCompetitorClass()); } BoatClass boatClass = getDominantBoatClass(competitorClassList); - Pair key = new Pair(tractracEvent.getName(), boatClass == null ? null + com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(tractracEvent.getName(), boatClass == null ? null : boatClass.getName()); synchronized (regattaCache) { Regatta regatta = regattaCache.get(key); @@ -498,7 +499,7 @@ public class DomainFactoryImpl implements DomainFactory { // add to existing regatta only if boat class matches if (raceDefinition.getBoatClass() == trackedRegatta.getRegatta().getBoatClass()) { trackedRegatta.getRegatta().addRace(raceDefinition); - trackedRace = createTrackedRace(trackedRegatta, raceDefinition, sidelines, windStore, + trackedRace = createTrackedRace(trackedRegatta, raceDefinition, sidelines, windStore, EmptyGPSFixStore.INSTANCE, delayToLiveInMillis, millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate); logger.info("Added race " + raceDefinition + " to regatta " + trackedRegatta.getRegatta()); @@ -528,16 +529,16 @@ public class DomainFactoryImpl implements DomainFactory { } } - private DynamicTrackedRace createTrackedRace(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, WindStore windStore, + private DynamicTrackedRace createTrackedRace(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { return trackedRegatta.createTrackedRace(race, sidelines, - windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, + windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, /* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(), raceDefinitionSetToUpdate); } @Override - public Pair, BoatClass> getCompetitorsAndDominantBoatClass(Race race) { + public com.sap.sse.common.Util.Pair, BoatClass> getCompetitorsAndDominantBoatClass(Race race) { List competitorClasses = new ArrayList(); final List competitors = new ArrayList(); for (RaceCompetitor rc : race.getRaceCompetitorList()) { @@ -547,7 +548,7 @@ public class DomainFactoryImpl implements DomainFactory { competitorClasses.add(rc.getCompetitor().getCompetitorClass()); } BoatClass dominantBoatClass = getDominantBoatClass(competitorClasses); - Pair, BoatClass> competitorsAndDominantBoatClass = new Pair, BoatClass>( + com.sap.sse.common.Util.Pair, BoatClass> competitorsAndDominantBoatClass = new com.sap.sse.common.Util.Pair, BoatClass>( competitors, dominantBoatClass); return competitorsAndDominantBoatClass; } @@ -608,20 +609,20 @@ public class DomainFactoryImpl implements DomainFactory { @Override public TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, - RaceLogStore raceLogStore, WindStore windStore, String tracTracUsername, String tracTracPassword, String raceStatus, + RaceLogStore raceLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, URISyntaxException { return new TracTracRaceTrackerImpl(this, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, - simulateWithStartTimeNow, raceLogStore, windStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); + simulateWithStartTimeNow, raceLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); } @Override public RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, - boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, WindStore windStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) + boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, URISyntaxException { return new TracTracRaceTrackerImpl(regatta, this, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, - simulateWithStartTimeNow, raceLogStore, windStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); + simulateWithStartTimeNow, raceLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); } @Override diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPassingReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPassingReceiver.java index c8a2f09009e..c4181909c27 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPassingReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPassingReceiver.java @@ -14,12 +14,12 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tractracadapter.DomainFactory; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.ControlPoint; import com.tractrac.clientmodule.Race; import com.tractrac.clientmodule.RaceCompetitor; @@ -49,7 +49,7 @@ public class MarkPassingReceiver extends AbstractReceiverWithQueue() { @Override public void gotData(RaceCompetitor competitor, MarkPassingsData record, boolean isLiveData) { - enqueue(new Triple(competitor, record, isLiveData)); + enqueue(new Util.Triple(competitor, record, isLiveData)); } }); result.add(controlPointListener); @@ -58,7 +58,7 @@ public class MarkPassingReceiver extends AbstractReceiverWithQueue event) { + protected void handleEvent(Util.Triple event) { System.out.print("L"); // as in "Leg" DynamicTrackedRace trackedRace = getTrackedRace(event.getA().getRace()); if (trackedRace != null) { diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPositionReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPositionReceiver.java index 2de3dc1965c..3b5c1437760 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPositionReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPositionReceiver.java @@ -6,13 +6,12 @@ import java.util.List; import java.util.logging.Logger; import com.maptrack.client.io.TypeController; -import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Course; +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.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.GPSFixMoving; @@ -20,6 +19,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tractracadapter.DomainFactory; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.ControlPoint; import com.tractrac.clientmodule.Race; import com.tractrac.clientmodule.data.ControlPointPositionData; @@ -101,7 +101,7 @@ public class MarkPositionReceiver extends AbstractReceiverWithQueue() { @Override public void gotData(ControlPoint controlPoint, ControlPointPositionData record, boolean isLiveData) { - enqueue(new Triple(controlPoint, record, isLiveData)); + enqueue(new Util.Triple(controlPoint, record, isLiveData)); } }, fromTime, toTime); result.add(controlPointListener); @@ -110,7 +110,7 @@ public class MarkPositionReceiver extends AbstractReceiverWithQueue event) { + protected void handleEvent(Util.Triple event) { if (received++ % 1000 == 0) { System.out.print("M"); if ((received / 1000 + 1) % 80 == 0) { diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java index b00f7560cda..c3d400df8ad 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java @@ -17,10 +17,8 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; @@ -85,7 +83,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue() { @Override public void gotData(Route route, RouteData record, boolean isLiveData) { - enqueue(new Triple(route, record, race)); + enqueue(new com.sap.sse.common.Util.Triple(route, record, race)); } }); setAndStartThread(new Thread(this, getClass().getName())); @@ -95,7 +93,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue event) { + protected void handleEvent(com.sap.sse.common.Util.Triple event) { System.out.print("R"); final Route route = event.getA(); final String routeMetadataString = route.getMetadata() != null ? route.getMetadata().getText() : null; @@ -108,11 +106,11 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue courseWaypointPassingInstructions = getDomainFactory().getMetadataParser().parsePassingInstructionData(routeMetadataString, routeControlPoints); - List> ttControlPoints = new ArrayList<>(); + List> ttControlPoints = new ArrayList<>(); int i = 1; for (com.tractrac.clientmodule.ControlPoint cp : event.getB().getPoints()) { PassingInstruction passingInstructions = courseWaypointPassingInstructions.containsKey(i) ? courseWaypointPassingInstructions.get(i) : null; - ttControlPoints.add(new Pair(ttControlPointsForAllOriginalEventControlPoints.get(cp), passingInstructions)); + ttControlPoints.add(new com.sap.sse.common.Util.Pair(ttControlPointsForAllOriginalEventControlPoints.get(cp), passingInstructions)); i++; } @@ -141,7 +139,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue, BoatClass> competitorsAndDominantBoatClass = getDomainFactory().getCompetitorsAndDominantBoatClass(race); + com.sap.sse.common.Util.Pair, BoatClass> competitorsAndDominantBoatClass = getDomainFactory().getCompetitorsAndDominantBoatClass(race); DynamicTrackedRace trackedRace = getDomainFactory().getOrCreateRaceDefinitionAndTrackedRace( getTrackedRegatta(), race.getId(), race.getName(), competitorsAndDominantBoatClass.getA(), competitorsAndDominantBoatClass.getB(), course, sidelines, windStore, delayToLiveInMillis, @@ -155,7 +153,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue sidelines) { DynamicTrackedRace trackedRace = getTrackedRegatta().createTrackedRace(race, sidelines, - windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, + windStore, EmptyGPSFixStore.INSTANCE, delayToLiveInMillis, millisecondsOverWhichToAverageWind, /* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(), raceDefinitionSetToUpdate); diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceHandleImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceHandleImpl.java index e2c364d6901..8f6940fe5b6 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceHandleImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceHandleImpl.java @@ -52,7 +52,10 @@ public class RaceHandleImpl implements RacesHandle { public Set getRaces(long timeoutInMilliseconds) { Set result = new HashSet(); for (Race race : tractracEvent.getRaceList()) { - result.add(domainFactory.getAndWaitForRaceDefinition(race.getId(), timeoutInMilliseconds)); + final RaceDefinition raceDefinition = domainFactory.getAndWaitForRaceDefinition(race.getId(), timeoutInMilliseconds); + if (raceDefinition != null) { // may have time-outed + result.add(raceDefinition); + } } return result; } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceStartedAndFinishedReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceStartedAndFinishedReceiver.java index 9ea16d1a8d6..9acd1f33ee2 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceStartedAndFinishedReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceStartedAndFinishedReceiver.java @@ -11,10 +11,10 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tractracadapter.DomainFactory; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.Race; import com.tractrac.clientmodule.data.ICallbackData; import com.tractrac.clientmodule.data.StartStopTimesData; @@ -49,7 +49,7 @@ public class RaceStartedAndFinishedReceiver extends AbstractReceiverWithQueue() { @Override public void gotData(Race race, StartStopTimesData record, boolean isLiveData) { - enqueue(new Triple(race, record, isLiveData)); + enqueue(new Util.Triple(race, record, isLiveData)); } }); result.add(startStopListener); @@ -59,7 +59,7 @@ public class RaceStartedAndFinishedReceiver extends AbstractReceiverWithQueue event) { + protected void handleEvent(Util.Triple event) { System.out.print("StartStop"); DynamicTrackedRace trackedRace = getTrackedRace(event.getA()); if (trackedRace != null) { diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java index 5974079a7cd..d5d318d4372 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java @@ -8,8 +8,8 @@ import java.net.URL; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.tracking.RaceTracker; import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; @@ -51,23 +51,23 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne } @Override - public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) throws MalformedURLException, + public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, GPSFixStore gpsFixStore) throws MalformedURLException, FileNotFoundException, URISyntaxException { RaceTracker tracker = domainFactory.createRaceTracker(paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking, - endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); + endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); return tracker; } @Override - public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) + public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, GPSFixStore gpsFixStore) throws Exception { RaceTracker tracker = domainFactory.createRaceTracker(regatta, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking, - endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); + endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); return tracker; } @Override - public Util.Triple getTrackerID() { + public com.sap.sse.common.Util.Triple getTrackerID() { return TracTracRaceTrackerImpl.createID(paramURL, liveURI, storedURI); } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RawPositionReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RawPositionReceiver.java index 2bed078e47d..c33527cdb55 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RawPositionReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RawPositionReceiver.java @@ -6,12 +6,12 @@ import java.util.logging.Logger; import com.maptrack.client.io.TypeController; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.common.impl.Util.Triple; -import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.tractracadapter.DomainFactory; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.Race; import com.tractrac.clientmodule.RaceCompetitor; import com.tractrac.clientmodule.data.CompetitorPositionRawData; @@ -35,7 +35,7 @@ public class RawPositionReceiver extends AbstractReceiverWithQueue positionListener = new ICallbackData() { public void gotData(RaceCompetitor tracked, CompetitorPositionRawData record, boolean isLiveData) { - enqueue(new Triple(tracked, record, isLiveData)); + enqueue(new Util.Triple(tracked, record, isLiveData)); } }; List listeners = new ArrayList(); @@ -49,7 +49,7 @@ public class RawPositionReceiver extends AbstractReceiverWithQueue event) { + protected void handleEvent(Util.Triple event) { if (received++ % 1000 == 0) { System.out.print("P"); if ((received / 1000 + 1) % 80 == 0) { diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracAdapterImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracAdapterImpl.java index 9c068699ad2..462acca012e 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracAdapterImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracAdapterImpl.java @@ -11,7 +11,6 @@ import java.util.logging.Logger; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.tracking.RacesHandle; import com.sap.sailing.domain.tracking.TrackedRace; @@ -22,6 +21,7 @@ import com.sap.sailing.domain.tractracadapter.JSONService; import com.sap.sailing.domain.tractracadapter.RaceRecord; import com.sap.sailing.domain.tractracadapter.TracTracAdapter; import com.sap.sailing.domain.tractracadapter.TracTracConfiguration; +import com.sap.sse.common.Util; public class TracTracAdapterImpl implements TracTracAdapter { private final static Logger logger = Logger.getLogger(TracTracAdapter.class.getName()); @@ -73,12 +73,12 @@ public class TracTracAdapterImpl implements TracTracAdapter { } @Override - public Pair> getTracTracRaceRecords(URL jsonURL, boolean loadClientParams) + public Util.Pair> getTracTracRaceRecords(URL jsonURL, boolean loadClientParams) throws IOException, ParseException, org.json.simple.parser.ParseException, URISyntaxException { logger.info("Retrieving TracTrac race records from " + jsonURL); JSONService jsonService = getTracTracDomainFactory().parseJSONURLWithRaceRecords(jsonURL, loadClientParams); logger.info("OK retrieving TracTrac race records from " + jsonURL); - return new Pair>(jsonService.getEventName(), jsonService.getRaceRecords()); + return new Util.Pair>(jsonService.getEventName(), jsonService.getRaceRecords()); } @Override 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 3d0f211b1be..ece94a2622c 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 @@ -42,9 +42,8 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.tracking.AbstractRaceTrackerImpl; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; @@ -66,6 +65,7 @@ import com.sap.sailing.domain.tractracadapter.Receiver; import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker; +import com.sap.sse.common.Util; import com.tractrac.clientmodule.ControlPoint; import com.tractrac.clientmodule.Event; import com.tractrac.clientmodule.Race; @@ -198,15 +198,16 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements private final Set receivers; private final DomainFactory domainFactory; private final WindStore windStore; + private final GPSFixStore gpsFixStore; private final Set races; private final DynamicTrackedRegatta trackedRegatta; private TrackedRaceStatus lastStatus; - private HashMap, Pair> lastProgressPerID; + private HashMap, Util.Pair> lastProgressPerID; /** * paramURL, liveURI and storedURI for TracTrac connection */ - private final Triple urls; + private final Util.Triple urls; private final ScheduledFuture controlPointPositionPoller; /** @@ -249,18 +250,18 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements */ protected TracTracRaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, - boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, WindStore windStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) + boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException, MalformedURLException, FileNotFoundException { this(KeyValue.setup(paramURL), domainFactory, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, - delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); + delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); } private TracTracRaceTrackerImpl(Event tractracEvent, DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, - RaceLogStore raceLogStore, WindStore windStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) + RaceLogStore raceLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException, MalformedURLException, FileNotFoundException { this(tractracEvent, null, domainFactory, paramURL, liveURI, storedURI, courseDesignUpdateURI, - startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, + startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); } @@ -272,10 +273,10 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements */ protected TracTracRaceTrackerImpl(Regatta regatta, DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, - RaceLogStore raceLogStore, WindStore windStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) + RaceLogStore raceLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException, MalformedURLException, FileNotFoundException { this(KeyValue.setup(paramURL), regatta, domainFactory, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking, - endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, + endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, trackedRegattaRegistry); } @@ -293,21 +294,24 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements private TracTracRaceTrackerImpl(Event tractracEvent, final Regatta regatta, DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI, URI tracTracUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, - WindStore windStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) + WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException, MalformedURLException, FileNotFoundException { super(); this.tractracEvent = tractracEvent; urls = createID(paramURL, liveURI, storedURI); isLiveTracking = liveURI != null; this.races = new HashSet(); - this.windStore = windStore; + this.gpsFixStore = gpsFixStore; this.domainFactory = domainFactory; - this.lastProgressPerID = new HashMap, Pair>(); + this.lastProgressPerID = new HashMap, Util.Pair>(); final Simulator simulator; if (simulateWithStartTimeNow) { simulator = new Simulator(windStore); + // don't write the transformed wind fixes into the DB again... see also bug 1974 + this.windStore = EmptyWindStore.INSTANCE; } else { simulator = null; + this.windStore = windStore; } // can happen that TracTrac event is null (occurs when there is no Internet connection) // so lets raise some meaningful exception @@ -417,8 +421,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements return trackedRegatta; } - static Triple createID(URL paramURL, URI liveURI, URI storedURI) { - return new Triple(paramURL, liveURI, storedURI); + static Util.Triple createID(URL paramURL, URI liveURI, URI storedURI) { + return new Util.Triple(paramURL, liveURI, storedURI); } /** @@ -463,7 +467,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements try { clientParams = new ClientParamsPHP(paramURL, new InputStreamReader(paramURL.openStream())); if (clientParams.getRace() != null) { - List> newCourseControlPointsWithPassingInstruction = getControlPointsWithPassingInstruction( + List> newCourseControlPointsWithPassingInstruction = getControlPointsWithPassingInstruction( clientParams, new ControlPointProducer() { @Override public com.sap.sailing.domain.base.ControlPoint produceControlPoint( @@ -478,7 +482,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements + ". Creating RaceDefinition."); final Iterable competitors = getCompetitors(clientParams); final Iterable competitorsInClientParams = clientParams.getCompetitors(); - List> ttControlPointsAndPassingInstructions = getControlPointsWithPassingInstruction(clientParams, + List> ttControlPointsAndPassingInstructions = getControlPointsWithPassingInstruction(clientParams, new ControlPointProducer() { @Override public TracTracControlPoint produceControlPoint(TracTracControlPoint ttControlPoint) { @@ -566,11 +570,11 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements * If they differ, a {@link Course#update(Iterable, com.sap.sailing.domain.base.DomainFactory) course update} is triggered. */ private void compareAndUpdateCourseIfNecessary( - List> newCourseControlPointsWithPassingInstructions) { + List> newCourseControlPointsWithPassingInstructions) { assert getRaces() != null; // to check if a course update is required, compare to the existing course's control points: List newCourseControlPoints = new ArrayList<>(); - for (Pair controlPointAndPassingInstruction : newCourseControlPointsWithPassingInstructions) { + for (Util.Pair controlPointAndPassingInstruction : newCourseControlPointsWithPassingInstructions) { newCourseControlPoints.add(controlPointAndPassingInstruction.getA()); } List currentCourseControlPoints = new ArrayList<>(); @@ -596,8 +600,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements T produceControlPoint(TracTracControlPoint ttControlPoint); } - private List> getControlPointsWithPassingInstruction( final ClientParamsPHP clientParams, ControlPointProducer controlPointProducer) { - List> newCourseControlPointsWithPassingInstruction = new ArrayList<>(); + private List> getControlPointsWithPassingInstruction( final ClientParamsPHP clientParams, ControlPointProducer controlPointProducer) { + List> newCourseControlPointsWithPassingInstruction = new ArrayList<>(); final List newTracTracControlPoints = clientParams.getRace().getDefaultRoute().getControlPoints(); Map passingInstructionData = domainFactory.getMetadataParser().parsePassingInstructionData( clientParams.getRace().getDefaultRoute().getMetadata(), newTracTracControlPoints); @@ -605,7 +609,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements for (TracTracControlPoint newTracTracControlPoint : newTracTracControlPoints) { PassingInstruction passingInstructions = passingInstructionData.containsKey(i) ? passingInstructionData.get(i) : null; final T newControlPoint = controlPointProducer.produceControlPoint(newTracTracControlPoint); - newCourseControlPointsWithPassingInstruction.add(new Pair(newControlPoint, passingInstructions)); + newCourseControlPointsWithPassingInstruction.add(new Util.Pair(newControlPoint, passingInstructions)); i++; } return newCourseControlPointsWithPassingInstruction; @@ -644,7 +648,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements } @Override - public Triple getID() { + public Util.Triple getID() { return urls; } @@ -807,7 +811,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements return; } Integer counter = 0; - final Pair lastProgressPair = lastProgressPerID.get(getID()); + final Util.Pair lastProgressPair = lastProgressPerID.get(getID()); if (lastProgressPair != null) { Float lastProgress = lastProgressPair.getB(); counter = lastProgressPair.getA(); @@ -830,7 +834,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements } logger.info("Stored data progress in tracker "+getID()+" for race(s) "+getRaces()+": "+progress); lastStatus = new TrackedRaceStatusImpl(progress==1.0 ? TrackedRaceStatusEnum.TRACKING : TrackedRaceStatusEnum.LOADING, progress); - lastProgressPerID.put(getID(), new Pair(counter, progress)); + lastProgressPerID.put(getID(), new Util.Pair(counter, progress)); updateStatusOfTrackedRaces(); } @@ -850,4 +854,9 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements updateStatusOfTrackedRace(trackedRace); } + @Override + public GPSFixStore getGPSFixStore() { + return gpsFixStore; + } + } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java index e12246c80f7..75160c64634 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java @@ -90,6 +90,7 @@ public class UpdateHandler { } protected void checkAndLogUpdateResponse(HttpURLConnection connection) throws IOException, ParseException { + connection.setConnectTimeout(10000/*milliseconds*/); connection.connect(); BufferedReader reader = getResponseOnUpdateFromTracTrac(connection); Object responseBody = JSONValue.parseWithException(reader); diff --git a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF index b7b7130a7a6..e83121166d8 100755 --- a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF @@ -19,6 +19,9 @@ Export-Package: com.sap.sailing.domain.base, com.sap.sailing.domain.masterdataimport, com.sap.sailing.domain.racelog, com.sap.sailing.domain.racelog.impl, + com.sap.sailing.domain.racelog.tracking, + com.sap.sailing.domain.trackfiles, + com.sap.sailing.domain.trackimport, com.sap.sailing.domain.tracking, com.sap.sailing.domain.tracking.impl, com.sap.sailing.util, @@ -28,5 +31,6 @@ Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0", com.sap.sailing.domain.shared.android;bundle-version="1.0.0", com.sap.sailing.domain.common, org.json.simple;bundle-version="1.1.0", - com.sap.sailing.geocoding -Import-Package: org.apache.commons.collections.map + com.sap.sailing.geocoding, + com.sap.sse.datamining.shared;bundle-version="1.0.0", + com.sap.sse.common diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/BearingWithConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/BearingWithConfidence.java index 63f0e5852af..2ca2bc789f5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/BearingWithConfidence.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/BearingWithConfidence.java @@ -1,27 +1,9 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.base.BearingWithConfidence.DoublePair; import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.DoublePair; import com.sap.sailing.domain.confidence.HasConfidenceAndIsScalable; public interface BearingWithConfidence extends HasConfidenceAndIsScalable { - public static class DoublePair { - private final double a; - private final double b; - - public DoublePair(double a, double b) { - super(); - this.a = a; - this.b = b; - } - - public double getA() { - return a; - } - - public double getB() { - return b; - } - } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Course.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Course.java index 0099a896295..7394353594c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Course.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Course.java @@ -3,7 +3,7 @@ package com.sap.sailing.domain.base; import java.util.concurrent.locks.ReentrantReadWriteLock; import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; import difflib.PatchFailedException; @@ -40,5 +40,5 @@ public interface Course extends CourseBase { * to the registered {@link CourseListener}s as if {@link #addWaypoint(int, Waypoint)} and {@link #removeWaypoint(int)} * had been used. */ - void update(Iterable> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException; + void update(Iterable> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java index d7d4b9c614a..78d9e9a413f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java @@ -2,9 +2,7 @@ package com.sap.sailing.domain.base; import java.util.UUID; -import com.sap.sailing.domain.common.Named; -import com.sap.sailing.domain.common.Renamable; -import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; /** * An event is a group of {@link Regatta regattas} carried out at a common venue within a common time frame. For @@ -14,7 +12,7 @@ import com.sap.sailing.domain.common.WithID; * @author Axel Uhl (D043530) * */ -public interface Event extends Named, Renamable, WithID, EventBase { +public interface Event extends EventBase { Iterable getRegattas(); void addRegatta(Regatta regatta); @@ -25,4 +23,28 @@ public interface Event extends Named, Renamable, WithID, EventBase { * For events, the ID is always a UUID. */ UUID getId(); + + /** + * Returns a non-null live but unmodifiable collection of leaderboard groups that were previously + * {@link #addLeaderboardGroup(LeaderboardGroup) added} to this event, in the order of their addition. Therefore, to + * change the iteration order, {@link #removeLeaderboardGroup(LeaderboardGroup)} and + * {@link #addLeaderboardGroup(LeaderboardGroup)} need to be used. + */ + @Override + Iterable getLeaderboardGroups(); + + void addLeaderboardGroup(LeaderboardGroup leaderboardGroup); + + /** + * @return true if and only if a leaderboard group equal to leaderboardGroup was part of + * {@link #getLeaderboardGroups()} and therefore was actually removed + */ + boolean removeLeaderboardGroup(LeaderboardGroup leaderboardGroup); + + /** + * Replaces the {@link #getLeaderboardGroups() current contents of the leaderboard groups sequence} by the + * leaderboard groups in leaderboardGroups. + */ + void setLeaderboardGroups(Iterable leaderboardGroups); + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/LeaderboardSearchResult.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/LeaderboardSearchResult.java new file mode 100755 index 00000000000..a49b710723b --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/LeaderboardSearchResult.java @@ -0,0 +1,42 @@ +package com.sap.sailing.domain.base; + +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; + +/** + * This is what you get when you search a RacingEventService with its search(...) method. It + * delivers the "real things," meaning that the leaderboards and events etc. are those objects currently living in the + * server instance. This is richer than the de-serialized {@link LeaderboardSearchResultBase} objects you get when + * fetching search results from remote instances, such as those described by a {@link RemoteSailingServerReference}. + * However, for putting them into a DTO to transfer them to a web client, both are fine. + * + * @author Axel Uhl (D043530) + * + */ +public interface LeaderboardSearchResult extends LeaderboardSearchResultBase { + /** + * The leaderboard that matches the search query. + */ + Leaderboard getLeaderboard(); + + /** + * Returns the {@link RegattaLeaderboard#getRegatta() regatta leaderboard's regatta} if the + * {@link #getLeaderboard() leaderboard} is a regatta leaderboard, or null otherwise. + */ + Regatta getRegatta(); + + /** + * If the leaderboard is part of one or more leaderboard groups, the leaderboard groups; an empty but + * valid (non-null) collection otherwise. + */ + Iterable getLeaderboardGroups(); + + /** + * If there is a connection between an {@link Event} and the {@link #getLeaderboard() leaderboard}, e.g., because + * the {@link Event} has a {@link Event#getLeaderboardGroups() leaderboard group} that is part of {@link #getLeaderboardGroups()} or + * the event has a course area attached to its {@link Event#getVenue() venue} that is referenced by the leaderboard as + * its {@link Leaderboard#getDefaultCourseArea() default course area}, the event is returned; null otherwise. + */ + Event getEvent(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Moving.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Moving.java index 01993de2f32..dbc214bc63b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Moving.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Moving.java @@ -1,7 +1,9 @@ package com.sap.sailing.domain.base; import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sse.datamining.shared.annotations.Connector; public interface Moving { + @Connector SpeedWithBearing getSpeed(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/PositionWithConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/PositionWithConfidence.java index 4efc40f2b4b..c4b345fafa4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/PositionWithConfidence.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/PositionWithConfidence.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.base.impl.ScalablePosition; import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; import com.sap.sailing.domain.confidence.HasConfidenceAndIsScalable; /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java index 6f525493686..84e401e57e4 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java @@ -1,15 +1,14 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.racelog.RaceLogInformation; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.util.impl.RaceColumnListeners; +import com.sap.sse.common.Named; +import com.sap.sse.common.Util; /** * One or more races that would be noted together in a single column in a {@link Leaderboard}. If the number of @@ -126,7 +125,7 @@ public interface RaceColumn extends Named { /** * Constructs a key for maps storing corrections such as score corrections and max points reasons. */ - Pair getKey(Competitor competitor); + com.sap.sse.common.Util.Pair getKey(Competitor competitor); RaceColumnListeners getRaceColumnListeners(); 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 9699a1a7533..9cde39936a9 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 @@ -1,7 +1,7 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.WithID; +import com.sap.sse.common.Named; /** * Tells the {@link BoatClass boat class} and the {@link Course course} for a single race that is usually part of a @@ -21,5 +21,4 @@ public interface RaceDefinition extends Named, WithID { Course getCourse(); Iterable getCompetitors(); - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java index 85bf2e48e9d..ba32a6b4b28 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java @@ -1,13 +1,13 @@ package com.sap.sailing.domain.base; import com.sap.sailing.domain.base.configuration.RegattaConfiguration; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.WithID; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; +import com.sap.sse.common.Named; /** * The name shall be unique across all regattas tracked concurrently. In particular, if you want to diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RemoteSailingServerReference.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RemoteSailingServerReference.java new file mode 100644 index 00000000000..41d95d258f0 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RemoteSailingServerReference.java @@ -0,0 +1,15 @@ +package com.sap.sailing.domain.base; + +import java.net.URL; + +import com.sap.sse.common.Named; + +/** + * Represents a {@link URL}-based reference to a remote server. + * + * @author Frank + * + */ +public interface RemoteSailingServerReference extends Named { + URL getURL(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/ScalableInteger.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/ScalableInteger.java index eee9f30d6ec..0bde779cb1c 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/ScalableInteger.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/ScalableInteger.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.base; -import com.sap.sailing.domain.confidence.AbstractScalarValue; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.AbstractScalarValue; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; public class ScalableInteger implements AbstractScalarValue { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithBearingWithConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithBearingWithConfidence.java index 7653ed31731..0706d27a496 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithBearingWithConfidence.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithBearingWithConfidence.java @@ -2,10 +2,10 @@ package com.sap.sailing.domain.base; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.confidence.HasConfidence; +import com.sap.sse.common.Util; public interface SpeedWithBearingWithConfidence extends - HasConfidence, SpeedWithBearing, RelativeTo> { + HasConfidence, SpeedWithBearing, RelativeTo> { SpeedWithBearing getObject(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java index 901546fae10..c4cc1e61c93 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java @@ -10,7 +10,6 @@ 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.common.RaceIdentifier; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.racelog.RaceLogIdentifierTemplate; @@ -18,6 +17,7 @@ import com.sap.sailing.domain.racelog.RaceLogInformation; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.racelog.impl.RaceLogIdentifierImpl; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Util; public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implements RaceColumn { private static final long serialVersionUID = -7801617988982540470L; @@ -39,7 +39,6 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem this.trackedRaces = new TrackedRaces(); this.raceIdentifiers = new HashMap(); this.raceLogs = new HashMap(); - } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java index 770abfb6306..b1128e35ee4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java @@ -1,10 +1,11 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.BearingWithConfidence; -import com.sap.sailing.domain.base.BearingWithConfidence.DoublePair; import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.confidence.IsScalable; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.DoublePair; +import com.sap.sailing.domain.common.scalablevalue.IsScalable; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; public class BearingWithConfidenceImpl extends HasConfidenceImpl implements BearingWithConfidence, IsScalable { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java index 2f27b65719f..cb8032a102a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java @@ -23,10 +23,10 @@ import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.impl.NamedImpl; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.util.CourseAsWaypointList; import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sse.common.Util; import difflib.DiffUtils; import difflib.Patch; @@ -324,7 +324,7 @@ public class CourseImpl extends NamedImpl implements Course { } @Override - public void update(Iterable> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException { + public void update(Iterable> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException { Patch patch = null; synchronized (updateMonitor) { lockForRead(); @@ -344,7 +344,7 @@ public class CourseImpl extends NamedImpl implements Course { } wpl.add(waypoint); } - for (Pair newDomainControlPoint : newControlPoints) { + for (Util.Pair newDomainControlPoint : newControlPoints) { List waypoints = existingWaypointsByControlPoint.get(newDomainControlPoint.getA()); Waypoint waypoint; if (waypoints == null || waypoints.isEmpty()) { 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 c6490602736..d8e827a02ea 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 @@ -28,13 +28,14 @@ import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.dto.RaceStatusDTO; import com.sap.sailing.domain.common.dto.TrackedRaceDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.leaderboard.impl.HighPoint; import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall; import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie; import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets1LastBreaksTie; import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie; +import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFive; +import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSix; import com.sap.sailing.domain.leaderboard.impl.LowPoint; import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero; import com.sap.sailing.domain.tracking.GPSFix; @@ -44,6 +45,7 @@ import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; import com.sap.sailing.geocoding.ReverseGeocoder; +import com.sap.sse.common.Util; public class DomainFactoryImpl extends SharedDomainFactoryImpl implements DomainFactory { private static Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName()); @@ -86,6 +88,10 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain return new HighPointFirstGets10LastBreaksTie(); case LOW_POINT_WINNER_GETS_ZERO: return new LowPointWinnerGetsZero(); + case HIGH_POINT_WINNER_GETS_FIVE: + return new HighPointWinnerGetsFive(); + case HIGH_POINT_WINNER_GETS_SIX: + return new HighPointWinnerGetsSix(); } throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name()); } @@ -106,7 +112,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain // Optional: Getting the places of the race PlacemarkOrderDTO racePlaces = withGeoLocationData ? getRacePlaces(trackedRace) : null; TrackedRaceDTO trackedRaceDTO = createTrackedRaceDTO(trackedRace); - RaceDTO raceDTO = new RaceDTO(raceIdentifier, trackedRaceDTO, trackedRegattaRegistry.isRaceBeingTracked(trackedRace.getRace())); + RaceDTO raceDTO = new RaceDTO(raceIdentifier, trackedRaceDTO, trackedRegattaRegistry.isRaceBeingTracked( + trackedRace.getTrackedRegatta().getRegatta(), trackedRace.getRace())); raceDTO.places = racePlaces; updateRaceDTOWithTrackedRaceData(trackedRace, raceDTO); return raceDTO; @@ -135,7 +142,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain } private PlacemarkOrderDTO getRacePlaces(TrackedRace trackedRace) { - Pair startAndFinish = getStartFinishPlacemarksForTrackedRace(trackedRace); + Util.Pair startAndFinish = getStartFinishPlacemarksForTrackedRace(trackedRace); PlacemarkOrderDTO racePlaces = new PlacemarkOrderDTO(); if (startAndFinish.getA() != null) { racePlaces.getPlacemarks().add(convertToPlacemarkDTO(startAndFinish.getA())); @@ -149,7 +156,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain return racePlaces; } - private Pair getStartFinishPlacemarksForTrackedRace(TrackedRace race) { + private Util.Pair getStartFinishPlacemarksForTrackedRace(TrackedRace race) { double radiusCalculationFactor = 10.0; Placemark startBest = null; Placemark finishBest = null; @@ -201,7 +208,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain } } } - Pair placemarks = new Pair(startBest, finishBest); + 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/DouglasPeucker.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DouglasPeucker.java index a5d8d557d52..9b70bda3c90 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DouglasPeucker.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DouglasPeucker.java @@ -17,9 +17,9 @@ import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sse.common.Util; /** * Implements the (Ramer)-Douglas-Peucker algorithm on a segment of a {@link GPSFixTrack} with a configurable distance @@ -47,10 +47,10 @@ public class DouglasPeucker { this.executor = executor; } - private Pair getFixWithGreatestCrossTrackErrorInInterval(TimePoint from, TimePoint to) { + private Util.Pair getFixWithGreatestCrossTrackErrorInInterval(TimePoint from, TimePoint to) { Distance maxDistance = Distance.NULL; FixType firstFixAtOrAfter = track.getFirstFixAtOrAfter(from); - Pair result = null; + Util.Pair result = null; if (firstFixAtOrAfter != null) { final Position fromPosition = firstFixAtOrAfter.getPosition(); FixType toFix = track.getLastFixAtOrBefore(to); @@ -76,7 +76,7 @@ public class DouglasPeucker { fixFurthestAway = fix; } } - result = new Pair(fixFurthestAway, maxDistance); + result = new Util.Pair(fixFurthestAway, maxDistance); } } finally { track.unlockAfterRead(); @@ -86,29 +86,29 @@ public class DouglasPeucker { return result; } - private Pair getFixWithGreatestCrossTrackErrorUsingExecutor(TimePoint to, Distance maxDistance, + private Util.Pair getFixWithGreatestCrossTrackErrorUsingExecutor(TimePoint to, Distance maxDistance, final Position fromPosition, final Bearing bearing, Iterator fixIter) { FixType fixFurthestAway = null; - Pair result; - Collection>> crossTrackErrorFutures = new HashSet>>(); + Util.Pair result; + Collection>> crossTrackErrorFutures = new HashSet>>(); while (fixIter.hasNext()) { final FixType fix = fixIter.next(); if (fix.getTimePoint().compareTo(to) > 0) { break; } - RunnableFuture> crossTrackErrorFuture = new FutureTask>( - new Callable>() { + RunnableFuture> crossTrackErrorFuture = new FutureTask>( + new Callable>() { @Override - public Pair call() throws Exception { - return new Pair(fix, fix.getPosition().absoluteCrossTrackError( + public Util.Pair call() throws Exception { + return new Util.Pair(fix, fix.getPosition().absoluteCrossTrackError( fromPosition, bearing)); } }); executor.execute(crossTrackErrorFuture); crossTrackErrorFutures.add(crossTrackErrorFuture); } - for (Future> crossTrackErrorFuture : crossTrackErrorFutures) { - Pair fixAndCrossTrackError; + for (Future> crossTrackErrorFuture : crossTrackErrorFutures) { + Util.Pair fixAndCrossTrackError; try { fixAndCrossTrackError = crossTrackErrorFuture.get(); } catch (InterruptedException e) { @@ -121,7 +121,7 @@ public class DouglasPeucker { fixFurthestAway = fixAndCrossTrackError.getA(); } } - result = new Pair(fixFurthestAway, maxDistance); + result = new Util.Pair(fixFurthestAway, maxDistance); return result; } @@ -155,7 +155,7 @@ public class DouglasPeucker { */ private List approximateWithoutFirst(Distance maxDistance, TimePoint from, TimePoint to) { List result; - Pair fixAndDistance = getFixWithGreatestCrossTrackErrorInInterval(from, to); + Util.Pair fixAndDistance = getFixWithGreatestCrossTrackErrorInInterval(from, to); if (fixAndDistance == null || fixAndDistance.getB().compareTo(maxDistance) < 0) { // reached desired accuracy for interval from..to FixType lastFixAtOrBefore = track.getLastFixAtOrBefore(to); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java index 1ac75f15bb4..02b74d736fb 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java @@ -1,20 +1,27 @@ package com.sap.sailing.domain.base.impl; +import java.io.IOException; +import java.io.ObjectInputStream; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Venue; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sse.common.Util; public class EventImpl extends EventBaseImpl implements Event { private static final long serialVersionUID = 855135446595485715L; private final Set regattas; - + + private ConcurrentLinkedQueue leaderboardGroups; + public EventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) { this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id); } @@ -25,6 +32,14 @@ public class EventImpl extends EventBaseImpl implements Event { public EventImpl(String name, TimePoint startDate, TimePoint endDate, Venue venue, boolean isPublic, UUID id) { super(name, startDate, endDate, venue, isPublic, id); this.regattas = new HashSet(); + this.leaderboardGroups = new ConcurrentLinkedQueue<>(); + } + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + if (leaderboardGroups == null) { + leaderboardGroups = new ConcurrentLinkedQueue<>(); + } } @Override @@ -45,4 +60,25 @@ public class EventImpl extends EventBaseImpl implements Event { public String toString() { return getId() + " " + getName() + " " + getVenue().getName() + " " + isPublic(); } + + @Override + public Iterable getLeaderboardGroups() { + return Collections.unmodifiableCollection(leaderboardGroups); + } + + @Override + public void setLeaderboardGroups(Iterable leaderboardGroups) { + this.leaderboardGroups.clear(); + Util.addAll(leaderboardGroups, this.leaderboardGroups); + } + + @Override + public void addLeaderboardGroup(LeaderboardGroup leaderboardGroup) { + leaderboardGroups.add(leaderboardGroup); + } + + @Override + public boolean removeLeaderboardGroup(LeaderboardGroup leaderboardGroup) { + return leaderboardGroups.remove(leaderboardGroup); + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/LeaderboardBaseImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/LeaderboardBaseImpl.java new file mode 100755 index 00000000000..d288a301451 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/LeaderboardBaseImpl.java @@ -0,0 +1,26 @@ +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.base.LeaderboardBase; + +public class LeaderboardBaseImpl implements LeaderboardBase { + private static final long serialVersionUID = -5332972141344617372L; + private final String name; + private final String displayName; + + public LeaderboardBaseImpl(String name, String displayName) { + super(); + this.name = name; + this.displayName = displayName; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDisplayName() { + return displayName; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/LeaderboardSearchResultBaseImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/LeaderboardSearchResultBaseImpl.java new file mode 100755 index 00000000000..216e51850b3 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/LeaderboardSearchResultBaseImpl.java @@ -0,0 +1,60 @@ +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.base.LeaderboardBase; +import com.sap.sailing.domain.base.LeaderboardGroupBase; +import com.sap.sailing.domain.base.LeaderboardSearchResult; +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; +import com.sap.sailing.domain.leaderboard.Leaderboard; + +/** + * The simplified version of a {@link LeaderboardSearchResult}, after JSON deserialization on a remote server. In particular, + * the {@link Leaderboard} object is most certainly not available on the receiving end. {@link EventBase} and {@link LeaderboardGroupBase} + * objects are + * + * @author Axel Uhl (D043530) + * + */ +public class LeaderboardSearchResultBaseImpl implements LeaderboardSearchResultBase { + private final LeaderboardBase leaderboard; + private final String regattaName; + private final Iterable leaderboardGroups; + private final EventBase event; + private final String boatClassName; + + public LeaderboardSearchResultBaseImpl(LeaderboardBase leaderboard, String regattaName, + String boatClassName, Iterable leaderboardGroups, EventBase event) { + super(); + this.leaderboard = leaderboard; + this.regattaName = regattaName; + this.boatClassName = boatClassName; + this.leaderboardGroups = leaderboardGroups; + this.event = event; + } + + @Override + public LeaderboardBase getLeaderboard() { + return leaderboard; + } + + @Override + public String getRegattaName() { + return regattaName; + } + + @Override + public String getBoatClassName() { + return boatClassName; + } + + @Override + public Iterable getLeaderboardGroups() { + return leaderboardGroups; + } + + @Override + public EventBase getEvent() { + return event; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/LeaderboardSearchResultImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/LeaderboardSearchResultImpl.java new file mode 100755 index 00000000000..d619c8234f2 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/LeaderboardSearchResultImpl.java @@ -0,0 +1,83 @@ +package com.sap.sailing.domain.base.impl; + +import java.util.Collections; +import java.util.Set; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.LeaderboardSearchResult; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.tracking.TrackedRace; + +public class LeaderboardSearchResultImpl implements LeaderboardSearchResult { + private final Leaderboard leaderboard; + private final Event event; + private final Set leaderboardGroups; + + public LeaderboardSearchResultImpl(Leaderboard leaderboard, Event event, Set leaderboardGroups) { + this.leaderboard = leaderboard; + if (leaderboardGroups == null) { + this.leaderboardGroups = Collections.emptySet(); + } else { + this.leaderboardGroups = leaderboardGroups; + } + this.event = event; + } + + @Override + public Regatta getRegatta() { + final Regatta regatta; + if (leaderboard instanceof RegattaLeaderboard) { + regatta = ((RegattaLeaderboard) leaderboard).getRegatta(); + } else { + regatta = null; + } + return regatta; + } + + @Override + public String getRegattaName() { + return getRegatta() != null ? getRegatta().getName() : null; + } + + @Override + public Leaderboard getLeaderboard() { + return leaderboard; + } + + @Override + public Event getEvent() { + return event; + } + + @Override + public Iterable getLeaderboardGroups() { + return leaderboardGroups; + } + + @Override + public String getBoatClassName() { + BoatClass boatClass = getBoatClass(); + return boatClass == null ? null : boatClass.getName(); + } + + public BoatClass getBoatClass() { + final BoatClass boatClass; + if (getRegatta() != null) { + boatClass = getRegatta().getBoatClass(); + } else { + boatClass = getBoatClassFromTrackedRaces(); + } + return boatClass; + } + + private BoatClass getBoatClassFromTrackedRaces() { + for (TrackedRace trackedRace : getLeaderboard().getTrackedRaces()) { + return trackedRace.getRace().getBoatClass(); + } + return null; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/MasterDataImportInformation.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/MasterDataImportInformation.java new file mode 100644 index 00000000000..d68a956fccb --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/MasterDataImportInformation.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.racelog.RaceLogStore; + +public class MasterDataImportInformation { + + private final RaceLogStore raceLogStore; + + public MasterDataImportInformation(RaceLogStore raceLogStore) { + this.raceLogStore = raceLogStore; + } + + public RaceLogStore getRaceLogStore() { + return raceLogStore; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/PositionWithConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/PositionWithConfidenceImpl.java index 922f33f6339..0f8e5fc228f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/PositionWithConfidenceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/PositionWithConfidenceImpl.java @@ -2,7 +2,8 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.PositionWithConfidence; import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.confidence.IsScalable; +import com.sap.sailing.domain.common.scalablevalue.IsScalable; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; public class PositionWithConfidenceImpl extends HasConfidenceImpl implements PositionWithConfidence, IsScalable { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java index 2f343bdf068..67f0f9dc36f 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java @@ -19,6 +19,14 @@ import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; public class RaceColumnRaceLogReplicator implements RaceLogEventVisitor, Serializable { private static final long serialVersionUID = 4752330236765192592L; @@ -102,4 +110,44 @@ public class RaceColumnRaceLogReplicator implements RaceLogEventVisitor, Seriali public void visit(RaceLogWindFixEvent event) { notifyOnAdd(event); } + + @Override + public void visit(DeviceCompetitorMappingEvent event) { + notifyOnAdd(event); + } + + @Override + public void visit(DeviceMarkMappingEvent event) { + notifyOnAdd(event); + } + + @Override + public void visit(DenoteForTrackingEvent event) { + notifyOnAdd(event); + } + + @Override + public void visit(StartTrackingEvent event) { + notifyOnAdd(event); + } + + @Override + public void visit(RevokeEvent event) { + notifyOnAdd(event); + } + + @Override + public void visit(RegisterCompetitorEvent event) { + notifyOnAdd(event); + } + + @Override + public void visit(DefineMarkEvent event) { + notifyOnAdd(event); + } + + @Override + public void visit(CloseOpenEndedDeviceMappingEvent event) { + notifyOnAdd(event); + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index 614d517328a..3f5c4694b98 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -29,11 +29,11 @@ import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.impl.NamedImpl; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.domain.racelog.RaceLogInformation; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; import com.sap.sailing.domain.racelog.impl.RaceLogInformationImpl; @@ -42,8 +42,24 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.util.impl.RaceColumnListeners; +import com.sap.sse.common.Util; public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListener { + + /** + * Used during master data import to handle connection to correct RaceLogStore + */ + private static transient ThreadLocal ongoingMasterDataImportInformation = new ThreadLocal() { + @Override + protected MasterDataImportInformation initialValue() { + return null; + }; + }; + + public static void setOngoingMasterDataImport(MasterDataImportInformation information) { + ongoingMasterDataImportInformation.set(information); + } + private static final Logger logger = Logger.getLogger(RegattaImpl.class.getName()); private static final long serialVersionUID = 6509564189552478869L; private final Set races; @@ -159,12 +175,29 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene /** * 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. + * {@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; regattaListeners = new HashSet(); + MasterDataImportInformation masterDataImportInformation = ongoingMasterDataImportInformation.get(); + if (masterDataImportInformation != null) { + raceLogStore = masterDataImportInformation.getRaceLogStore(); + } else { + raceLogStore = EmptyRaceLogStore.INSTANCE; + } + for (Series series : getSeries()) { + linkToRegattaAndConnectRaceLogsAndAddListeners(series); + if (series.getRaceColumns() != null) { + for (RaceColumnInSeries column : series.getRaceColumns()) { + column.setRaceLogInformation(new RaceLogInformationImpl(raceLogStore, + new RaceLogOnRegattaIdentifier(this, column.getName()))); + } + } else { + logger.warning("Race Columns were null during deserialization. This should not happen."); + } + } } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RemoteSailingServerReferenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RemoteSailingServerReferenceImpl.java new file mode 100644 index 00000000000..2e72132ed97 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RemoteSailingServerReferenceImpl.java @@ -0,0 +1,23 @@ +package com.sap.sailing.domain.base.impl; + +import java.net.URL; + +import com.sap.sailing.domain.base.RemoteSailingServerReference; +import com.sap.sailing.domain.common.impl.NamedImpl; + +public class RemoteSailingServerReferenceImpl extends NamedImpl implements RemoteSailingServerReference { + private static final long serialVersionUID = 3561284977118738878L; + + /** the URL of the server */ + private final URL url; + + public RemoteSailingServerReferenceImpl(String name, URL url) { + super(name); + this.url = url; + } + + @Override + public URL getURL() { + return url; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java index 5123fb24ac1..58e9272751a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java @@ -15,7 +15,6 @@ 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.common.impl.RenamableImpl; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; import com.sap.sailing.domain.racelog.RaceLogEvent; @@ -24,6 +23,7 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.util.impl.RaceColumnListeners; +import com.sap.sse.common.Util; public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListener { private static final long serialVersionUID = -1640404303144907381L; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SimpleAbstractRaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SimpleAbstractRaceColumn.java index 0c26ba4c170..df31a14f4ae 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SimpleAbstractRaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SimpleAbstractRaceColumn.java @@ -5,10 +5,9 @@ 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.RaceDefinition; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.util.impl.RaceColumnListeners; +import com.sap.sse.common.Util; public abstract class SimpleAbstractRaceColumn implements RaceColumn { private static final long serialVersionUID = -3590156714385187908L; @@ -32,8 +31,8 @@ public abstract class SimpleAbstractRaceColumn implements RaceColumn { } @Override - public Pair getKey(Competitor competitor) { - return new Pair(competitor, this); + public com.sap.sse.common.Util.Pair getKey(Competitor competitor) { + return new com.sap.sse.common.Util.Pair(competitor, this); } public RaceColumnListeners getRaceColumnListeners() { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithBearingWithConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithBearingWithConfidenceImpl.java index 0714327dd6c..6b714c6bd72 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithBearingWithConfidenceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithBearingWithConfidenceImpl.java @@ -3,13 +3,14 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.impl.Util.Triple; -import com.sap.sailing.domain.confidence.IsScalable; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.IsScalable; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalableSpeedWithBearing; +import com.sap.sse.common.Util; public class SpeedWithBearingWithConfidenceImpl extends - HasConfidenceImpl, SpeedWithBearing, RelativeTo> implements - SpeedWithBearingWithConfidence, IsScalable, SpeedWithBearing> { + HasConfidenceImpl, SpeedWithBearing, RelativeTo> implements + SpeedWithBearingWithConfidence, IsScalable, SpeedWithBearing> { private static final long serialVersionUID = -4811576094614673625L; public SpeedWithBearingWithConfidenceImpl(SpeedWithBearing speedWithBearing, double confidence, RelativeTo relativeTo) { @@ -22,7 +23,7 @@ public class SpeedWithBearingWithConfidenceImpl extends * angle. */ @Override - public ScalableValue, SpeedWithBearing> getScalableValue() { + public ScalableValue, SpeedWithBearing> getScalableValue() { return new ScalableSpeedWithBearing(getObject()); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java index 1e4d7815607..979049aa222 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java @@ -2,7 +2,8 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalableSpeed; public class SpeedWithConfidenceImpl extends HasConfidenceImpl implements SpeedWithConfidence { private static final long serialVersionUID = -3122580369653833748L; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidence.java index c4703252a18..b488c013080 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidence.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidence.java @@ -4,7 +4,7 @@ import java.io.Serializable; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; /** * Some values, particularly those obtained from real-world measurements, are not always accurate. Some values are @@ -72,7 +72,7 @@ public interface HasConfidence extends Serializ /** * The confidence attached to a value is usually relative to some reference point, such as a time point or - * a position. For example, when a {@link GPSFixTrack} is asked to deliver an estimation for the tracked item's + * a position. For example, when a GPSFixTrack is asked to deliver an estimation for the tracked item's * {@link Position} at some given {@link TimePoint}, the track computes some average from a number of GPS fixes. The resulting * position has a certain confidence, depending on the time differences between the fixes and the time point for which * the position estimation was requested. The result therefore carries this reference time point for which the estimation diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidenceAndIsScalable.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidenceAndIsScalable.java index 373bd7c8e6a..cdc5edbbdab 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidenceAndIsScalable.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/HasConfidenceAndIsScalable.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.confidence; +import com.sap.sailing.domain.common.scalablevalue.IsScalable; + public interface HasConfidenceAndIsScalable extends IsScalable, HasConfidence { } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java index 7ce80252ab9..cd1996313e5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java @@ -1,12 +1,12 @@ package com.sap.sailing.domain.confidence.impl; import com.sap.sailing.domain.base.impl.HasConfidenceImpl; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; import com.sap.sailing.domain.confidence.HasConfidence; import com.sap.sailing.domain.confidence.HasConfidenceAndIsScalable; -import com.sap.sailing.domain.confidence.ScalableValue; import com.sap.sailing.domain.confidence.Weigher; +import com.sap.sse.common.Util; /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedWindAveragerImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedWindAveragerImpl.java index 03bb74693c8..47483cb4949 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedWindAveragerImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedWindAveragerImpl.java @@ -4,7 +4,6 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; import com.sap.sailing.domain.confidence.HasConfidenceAndIsScalable; import com.sap.sailing.domain.confidence.Weigher; @@ -13,6 +12,7 @@ import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.domain.tracking.impl.ScalableWind; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl; +import com.sap.sse.common.Util; /** * In order to enable the aggregation of {@link Wind} objects whose {@link WindSource}'s {@link WindSourceType} suggests diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/PositionAndTimePointWeigher.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/PositionAndTimePointWeigher.java index c585b828aad..93091ca7c84 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/PositionAndTimePointWeigher.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/PositionAndTimePointWeigher.java @@ -2,9 +2,9 @@ package com.sap.sailing.domain.confidence.impl; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.confidence.ConfidenceFactory; import com.sap.sailing.domain.confidence.Weigher; +import com.sap.sse.common.Util; /** * A weigher that uses a {@link Position} and a {@link TimePoint} to compute a confidence based on @@ -15,7 +15,7 @@ import com.sap.sailing.domain.confidence.Weigher; * @author Axel Uhl (d043530) * */ -public class PositionAndTimePointWeigher implements Weigher> { +public class PositionAndTimePointWeigher implements Weigher> { private static final long serialVersionUID = -262428237738496818L; private final Weigher weigher; @@ -24,7 +24,7 @@ public class PositionAndTimePointWeigher implements Weigher fix, Pair request) { + public double getConfidence(Util.Pair fix, Util.Pair request) { return weigher.getConfidence(fix.getB(), request.getB()); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDouble.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDouble.java index 3727713e958..ae6ab5eb3a7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDouble.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ScalableDouble.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.confidence.impl; -import com.sap.sailing.domain.confidence.AbstractScalarValue; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.AbstractScalarValue; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; public class ScalableDouble implements AbstractScalarValue { private final double value; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/EventResolver.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/EventResolver.java new file mode 100644 index 00000000000..06e90a5881f --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/EventResolver.java @@ -0,0 +1,22 @@ +package com.sap.sailing.domain.leaderboard; + +import java.io.Serializable; + +import com.sap.sailing.domain.base.Event; + +/** + * Manages a set of events and can resolve one by the event's ID + * + * @author Axel Uhl (d043530) + * + */ +public interface EventResolver { + /** + * Returns the event with given id. When no event is found, null is returned. + * + * @param id + * The id of the event. + * @return The event with given id. + */ + Event getEvent(Serializable id); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java index bbeae7174cb..42942f6ecd7 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java @@ -9,23 +9,24 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.LeaderboardBase; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnListener; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Duration; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.dto.LeaderboardDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache; import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sse.common.Util; /** * A leaderboard is used to display the results of one or more {@link TrackedRace races}. It manages the competitors' @@ -42,7 +43,7 @@ import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; * @author Axel Uhl (d043530) * */ -public interface Leaderboard extends Named { +public interface Leaderboard extends LeaderboardBase { /** * If the leaderboard is a "matrix" with the cells being defined by a competitor / race "coordinate," * then this interface defines the structure of the "cells." @@ -70,8 +71,9 @@ public interface Leaderboard extends Named { } LeaderboardDTO computeDTO(final TimePoint timePoint, - final Collection namesOfRaceColumnsForWhichToLoadLegDetails, final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) - throws NoWindException; + final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, + DomainFactory baseDomainFactory) throws NoWindException; /** * Obtains the unique set of {@link Competitor} objects from all {@link TrackedRace}s currently linked to this @@ -254,7 +256,7 @@ public interface Leaderboard extends Named { * Note that in order to get the {@link #getTotalPoints(Competitor, TimePoint) total points} for a competitor * for the entire leaderboard, the {@link #getCarriedPoints(Competitor) carried-over points} need to be added. */ - Map, Entry> getContent(TimePoint timePoint) throws NoWindException; + Map, Entry> getContent(TimePoint timePoint) throws NoWindException; /** * Retrieves all race columns that were added, either by {@link #addRace(TrackedRace, String, boolean)} or @@ -298,12 +300,6 @@ public interface Leaderboard extends Named { void setDisplayName(Competitor competitor, String displayName); - /** - * If a display name for the leaderboard has been defined, - * this method returns it; otherwise, null is returned. - */ - String getDisplayName(); - void setDisplayName(String displayName); /** @@ -370,7 +366,7 @@ public interface Leaderboard extends Named { * or the competitor hasn't started sailing a single race at timePoint for any of the tracked * races attached to this leaderboard; the fix where the maximum speed was achieved, and the speed value */ - Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint); + Util.Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint); /** * @return null if no tracked race is available in this leaderboard, the competitor hasn't started @@ -386,7 +382,7 @@ public interface Leaderboard extends Named { * or the competitor hasn't started sailing a single downwind leg at timePoint for any of the tracked * races attached to this leaderboard */ - Long getTotalTimeSailedInLegTypeInMilliseconds(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException; + Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException; /** * Starts counting when the gun goes off, not when the competitor passed the line. @@ -395,7 +391,7 @@ public interface Leaderboard extends Named { * or the competitor hasn't started sailing a single race at timePoint for any of the tracked * races attached to this leaderboard */ - Long getTotalTimeSailedInMilliseconds(Competitor competitor, TimePoint timePoint); + Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint); /** * Computes the distance the competitor has sailed in the tracked races in this leaderboard, starting @@ -451,6 +447,7 @@ public interface Leaderboard extends Named { * point at which to evaluate the leaderboard status * @param namesOfRaceColumnsForWhichToLoadLegDetails * the names of the race columns of which to expand the details in the result + * @param addOverallDetails TODO * @param trackedRegattaRegistry * used to determine which of the races are still being tracked and which ones are not * @param baseDomainFactory @@ -458,7 +455,7 @@ public interface Leaderboard extends Named { */ LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, Collection namesOfRaceColumnsForWhichToLoadLegDetails, - TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, + boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, InterruptedException, ExecutionException; NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java index 4e92d3e1f1c..a9db6d4d6a9 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java @@ -7,11 +7,15 @@ import java.util.Set; import java.util.WeakHashMap; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CompetitorChangeListener; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.Nationality; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnListener; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.WithNationality; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; @@ -39,7 +43,8 @@ import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; */ public class LeaderboardCacheManager { private final WeakHashMap scoreCorrectionListeners; - private final NamedReentrantReadWriteLock scoreCorrectionListenersLock; + private final NamedReentrantReadWriteLock scoreCorrectionAndCompetitorChangeListenersLock; + private final WeakHashMap competitorChangeListeners; private final WeakHashMap>> invalidationListenersPerLeaderboard; private final WeakHashMap raceColumnListeners; @@ -113,6 +118,64 @@ public class LeaderboardCacheManager { } } + private class CacheInvalidationUponCompetitorChangeListener implements CompetitorChangeListener { + private static final long serialVersionUID = 8308312509904366143L; + private final Leaderboard leaderboard; + private final Set observedCompetitors; + + /** + * Registers this listener as listener for all of the leaderboard's current + * {@link Leaderboard#getCompetitors() competitors} + */ + public CacheInvalidationUponCompetitorChangeListener(Leaderboard leaderboard) { + this.leaderboard = leaderboard; + observedCompetitors = new HashSet<>(); + updateCompetitorListeners(); + } + + @Override + public void sailIdChanged(String oldSailId, String newSailId) { + removeFromCache(leaderboard); + } + + @Override + public void nationalityChanged(WithNationality what, Nationality oldNationality, Nationality newNationality) { + removeFromCache(leaderboard); + } + + @Override + public void colorChanged(Color oldColor, Color newColor) { + removeFromCache(leaderboard); + } + + @Override + public void nameChanged(String oldName, String newName) { + removeFromCache(leaderboard); + } + + public synchronized void updateCompetitorListeners() { + Set competitorsToStopObserving = new HashSet<>(observedCompetitors); + for (Competitor competitor : leaderboard.getCompetitors()) { + if (!observedCompetitors.contains(competitor)) { + competitor.addCompetitorChangeListener(this); // start observing competitor + } else { + competitorsToStopObserving.remove(competitor); // keep observing competitor + } + } + for (Competitor competitorToStopObserving : competitorsToStopObserving) { + competitorToStopObserving.removeCompetitorChangeListener(this); + observedCompetitors.remove(competitorToStopObserving); + } + } + + public synchronized void removeFromAllCompetitors() { + for (Competitor competitor : observedCompetitors) { + competitor.removeCompetitorChangeListener(this); + } + } + + } + private class CacheInvalidationUponScoreCorrectionListener implements ScoreCorrectionListener { private final Leaderboard leaderboard; @@ -146,10 +209,12 @@ public class LeaderboardCacheManager { public LeaderboardCacheManager(LeaderboardCache leaderboardCache) { this.leaderboardCache = leaderboardCache; - this.invalidationListenersPerLeaderboard = new WeakHashMap>>(); + this.invalidationListenersPerLeaderboard = new WeakHashMap<>(); this.raceColumnListeners = new WeakHashMap(); - this.scoreCorrectionListeners = new WeakHashMap(); - this.scoreCorrectionListenersLock = new NamedReentrantReadWriteLock("Score correction listeners", /* fair */ false); + this.scoreCorrectionListeners = new WeakHashMap<>(); + this.scoreCorrectionAndCompetitorChangeListenersLock = new NamedReentrantReadWriteLock( + "Score correction and competitor change listeners", /* fair */false); + this.competitorChangeListeners = new WeakHashMap<>(); } private void removeFromCache(Leaderboard leaderboard) { @@ -167,14 +232,21 @@ public class LeaderboardCacheManager { synchronized (raceColumnListeners) { leaderboard.removeRaceColumnListener(raceColumnListeners.remove(leaderboard)); } - LockUtil.lockForWrite(scoreCorrectionListenersLock); + LockUtil.lockForWrite(scoreCorrectionAndCompetitorChangeListenersLock); final CacheInvalidationUponScoreCorrectionListener removedScoreCorrectionListener; + final CacheInvalidationUponCompetitorChangeListener removedCompetitorChangeListener; try { removedScoreCorrectionListener = scoreCorrectionListeners.remove(leaderboard); + removedCompetitorChangeListener = competitorChangeListeners.remove(leaderboard); } finally { - LockUtil.unlockAfterWrite(scoreCorrectionListenersLock); + LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorChangeListenersLock); + } + if (removedScoreCorrectionListener != null) { + leaderboard.getScoreCorrection().removeScoreCorrectionListener(removedScoreCorrectionListener); + } + if (removedCompetitorChangeListener != null) { + removedCompetitorChangeListener.removeFromAllCompetitors(); } - leaderboard.getScoreCorrection().removeScoreCorrectionListener(removedScoreCorrectionListener); // invalidate after the listeners have been removed; this is important because invalidation may trigger a // re-calculation which then in turn may call add(leaderboard) asynchronously again which may be executed // before the listener removal happens here. This could lead to a race condition where listeners are @@ -195,22 +267,25 @@ public class LeaderboardCacheManager { private void registerAsListener(final Leaderboard leaderboard) { // only add as listener again if not yet added final boolean containsKey; - LockUtil.lockForRead(scoreCorrectionListenersLock); + LockUtil.lockForRead(scoreCorrectionAndCompetitorChangeListenersLock); try { containsKey = scoreCorrectionListeners.containsKey(leaderboard); } finally { - LockUtil.unlockAfterRead(scoreCorrectionListenersLock); + LockUtil.unlockAfterRead(scoreCorrectionAndCompetitorChangeListenersLock); } if (!containsKey) { - LockUtil.lockForWrite(scoreCorrectionListenersLock); + LockUtil.lockForWrite(scoreCorrectionAndCompetitorChangeListenersLock); try { for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) { registerListener(leaderboard, trackedRace); } final CacheInvalidationUponScoreCorrectionListener scoreCorrectionListener = new CacheInvalidationUponScoreCorrectionListener( leaderboard); + final CacheInvalidationUponCompetitorChangeListener competitorChangeListener = new CacheInvalidationUponCompetitorChangeListener( + leaderboard); leaderboard.getScoreCorrection().addScoreCorrectionListener(scoreCorrectionListener); scoreCorrectionListeners.put(leaderboard, scoreCorrectionListener); + competitorChangeListeners.put(leaderboard, competitorChangeListener); final RaceColumnListener raceColumnListener = new RaceColumnListener() { private static final long serialVersionUID = 8165124797028386317L; @@ -312,7 +387,7 @@ public class LeaderboardCacheManager { raceColumnListeners.put(leaderboard, raceColumnListener); } } finally { - LockUtil.unlockAfterWrite(scoreCorrectionListenersLock); + LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorChangeListenersLock); } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardGroup.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardGroup.java index a9c6c412d4c..84472272866 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardGroup.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardGroup.java @@ -1,22 +1,33 @@ package com.sap.sailing.domain.leaderboard; import java.util.Collection; +import java.util.UUID; -import com.sap.sailing.domain.common.Renamable; +import com.sap.sailing.domain.base.LeaderboardGroupBase; +import com.sap.sailing.domain.common.WithID; /** - * A leaderboard group is used to group one or more {@link Leaderboard}s. + * A leaderboard group is used to group one or more {@link Leaderboard}s. If can be used to represent all or part of the + * regattas of an event or to establish a collection of regattas or leaderboards otherwise belonging together according + * to some context. This could, for example, be a leaderboard group of all the interesting races in a particular boat + * class or races at a particular event over time.

+ * + * The leaderboards grouped by this object have an iteration order that equals their insertion order. We have seen + * cases where a user interface shall display the leaderboards in reverse insertion order. This behavior is described + * by {@link #isDisplayGroupsInReverseOrder()}.

+ * + * Optionally, a leaderboard group can specify an "Overall Leaderboard." The typical use case for this is a regatta series + * such as the Extreme Sailing Series or the German Sailing League ("Bundesliga") where several regatta results are + * combined into a series score. The overall leaderboard, if defined, has its own scoring scheme and obtains its + * "tracked results" from the results of the respective leaderboard. Each leaderboard in this group then represents + * one race column in the overall leaderboard. + * * @author Frank Mittag (c5163874) */ -public interface LeaderboardGroup extends Renamable { +public interface LeaderboardGroup extends LeaderboardGroupBase { void addLeaderboardGroupListener(LeaderboardGroupListener listener); void removeLeaderboardGroupListener(LeaderboardGroupListener listener); - - String getDescription(); - void setDescriptiom(String description); - boolean isDisplayGroupsInReverseOrder(); - Iterable getLeaderboards(); int getIndexOf(Leaderboard leaderboard); void addLeaderboard(Leaderboard leaderboard); @@ -30,4 +41,9 @@ public interface LeaderboardGroup extends Renamable { */ Leaderboard getOverallLeaderboard(); void setOverallLeaderboard(Leaderboard leaderboard); + + /** + * Specializes the {@link WithID#getId()} method regarding its return type. + */ + UUID getId(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardGroupResolver.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardGroupResolver.java new file mode 100755 index 00000000000..857f3b9cfcd --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardGroupResolver.java @@ -0,0 +1,15 @@ +package com.sap.sailing.domain.leaderboard; + +import java.util.UUID; + +public interface LeaderboardGroupResolver { + /** + * @param leaderboardGroupName + * The name of the requested leaderboard group + * @return The leaderboard group with the name leaderboardGroupName, or null if there is + * no such group within this resolver + */ + LeaderboardGroup getLeaderboardGroupByName(String leaderboardGroupName); + + LeaderboardGroup getLeaderboardGroupByID(UUID leaderboardGroupID); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java index 1e4b9cc90a2..1b5a5d8cccc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java @@ -12,8 +12,8 @@ import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; +import com.sap.sse.common.Util; /** * A leaderboard has a scoring scheme that decides how race ranks map to scores, how penalties are to be scored, @@ -73,8 +73,8 @@ public interface ScoringScheme extends Serializable { * @param competitor1Scores scores of the first competitor, in the order of race columns in the leaderboard * @param competitor2Scores scores of the second competitor, in the order of race columns in the leaderboard */ - int compareByBetterScore(List> competitor1Scores, - List> competitor2Scores, boolean nullScoresAreBetter); + int compareByBetterScore(List> competitor1Scores, + List> competitor2Scores, boolean nullScoresAreBetter); /** * In case two competitors scored in different numbers of races, this scoring scheme decides whether this @@ -88,7 +88,7 @@ public interface ScoringScheme extends Serializable { /** * Usually, when all other sorting criteria end up in a tie, the last race sailed is used to decide. */ - int compareByLastRace(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter); + int compareByLastRace(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter); /** * Under certain circumstances, a scoring scheme may decide that the scores of a column are not (yet) to be used diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java index af722f75216..69775b22a9b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java @@ -19,8 +19,6 @@ import com.sap.sailing.domain.base.RaceColumnListener; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.dto.LeaderboardDTO; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardCacheManager; import com.sap.sailing.domain.leaderboard.ScoreCorrection; @@ -30,6 +28,7 @@ import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; import com.sap.sailing.util.impl.ThreadFactoryWithPriority; +import com.sap.sse.common.Util; /** * Caches the expensive to compute {@link LeaderboardDTO} results of a @@ -47,13 +46,15 @@ public class LeaderboardDTOCache implements LeaderboardCache { private static final Logger logger = Logger.getLogger(LeaderboardDTOCache.class.getName()); /** - * In live operations, {@link #getLeaderboardByName(Date, Collection, DomainFactory, TrackedRegattaRegistry)} is the application's + * In live operations, {@link #getLeaderboardByName(Date, Collection, boolean, DomainFactory, TrackedRegattaRegistry)} is the application's * bottleneck. When two clients ask the same data for the same leaderboard with their * waitForLatestAnalyses parameters set to false, expansion state and (quantized) time * stamp, no two computations should be spawned for the two clients. Instead, if the computation is still running, - * all clients asking the same wait for the single result. Results are cached in this LRU-based evicting cache. + * all clients asking the same wait for the single result. Results are cached in this LRU-based evicting cache. The key is + * the time point for the query, the names of the races for which details are requested and the flag telling + * whether overall details for the leaderboard are requested. */ - private final Map>, FutureTask> leaderboardCache; + private final Map, Boolean>, FutureTask> leaderboardCache; private final NamedReentrantReadWriteLock leaderboardCacheLock; private int leaderboardByNameCacheHitCount; private int leaderboardByNameCacheMissCount; @@ -83,10 +84,10 @@ public class LeaderboardDTOCache implements LeaderboardCache { this.leaderboard = leaderboard; this.waitForLatestAnalyses = waitForLatestAnalyses; // Note: don't use access-based ordering as it turns the get(...) call into a "write" access - this.leaderboardCache = new LinkedHashMap>, FutureTask>(16, 0.75f) { + this.leaderboardCache = new LinkedHashMap, Boolean>, FutureTask>(16, 0.75f) { private static final long serialVersionUID = 7287916997229815039L; @Override - protected boolean removeEldestEntry(Map.Entry>, FutureTask> e) { + protected boolean removeEldestEntry(Map.Entry, Boolean>, FutureTask> e) { return size() > 10; // remember 10 LeaderboardDTOs per leaderborad } }; @@ -118,10 +119,14 @@ public class LeaderboardDTOCache implements LeaderboardCache { * The {@link #waitForLatestAnalyses} field is passed on to * {@link SailingServiceImpl#computeLeaderboardByName(Leaderboard, TimePoint, Collection, boolean)} if a new cache * entry needs to be computed. Caching distinguished between + * + * @param addOverallDetails + * tells whether the columns containing the overall details shall be added */ - public LeaderboardDTO getLeaderboardByName(final TimePoint timePoint, final Collection namesOfRaceColumnsForWhichToLoadLegDetails, - final DomainFactory baseDomainFactory, final TrackedRegattaRegistry trackedRegattaRegistry) throws NoWindException, InterruptedException, - ExecutionException { + public LeaderboardDTO getLeaderboardByName(final TimePoint timePoint, + final Collection namesOfRaceColumnsForWhichToLoadLegDetails, final boolean addOverallDetails, + final DomainFactory baseDomainFactory, final TrackedRegattaRegistry trackedRegattaRegistry) + throws NoWindException, InterruptedException, ExecutionException { long startOfRequestHandling = System.currentTimeMillis(); final TimePoint adjustedTimePoint; TimePoint timePointOfLastModification = leaderboard.getTimePointOfLatestModification(); @@ -131,8 +136,8 @@ public class LeaderboardDTOCache implements LeaderboardCache { } else { adjustedTimePoint = timePoint; } - Util.Pair> key = new Util.Pair>(adjustedTimePoint, - namesOfRaceColumnsForWhichToLoadLegDetails); + Util.Triple, Boolean> key = new Util.Triple, Boolean>(adjustedTimePoint, + namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); FutureTask future = null; boolean cacheHit = false; /* @@ -161,8 +166,8 @@ public class LeaderboardDTOCache implements LeaderboardCache { LockUtil.propagateLockSetFrom(callerThread); try { LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint, - namesOfRaceColumnsForWhichToLoadLegDetails, waitForLatestAnalyses, - trackedRegattaRegistry, baseDomainFactory); + namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, + waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory); return result; } finally { LockUtil.unpropagateLockSetFrom(callerThread); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java index 266aa1093a7..298876d4a76 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java @@ -71,11 +71,13 @@ public class LiveLeaderboardUpdater implements Runnable { */ private final Set columnNamesForWhichCurrentLiveLeaderboardHasTheDetails; + private boolean currentLiveLeaderboardHasOverallDetails; + private boolean running; /** * For each String from namesOfRaceColumnsForWhichToLoadLegDetails passed to - * {@link #getLiveLeaderboard(Collection)}, records the validity time point of the last request here when the + * {@link #getLiveLeaderboard(Collection, boolean)}, records the validity time point of the last request here when the * {@link #currentLiveLeaderboard} has been updated with results containing this column details. This updater will * stop computing the details for that column if the validity time for the update calculation is more than * {@link #UPDATE_TIMEOUT_IN_MILLIS} milliseconds after the time point recorded here. @@ -84,7 +86,12 @@ public class LiveLeaderboardUpdater implements Runnable { private final Map timePointOfLastRequestForColumnDetails; /** - * As soon as the first {@link #getLiveLeaderboard(Collection)} request has been received, this field tells the + * Tells when {@link #updateRequestTimes(Collection, boolean)} was last called with the request for overall details + */ + private TimePoint timePointOfLastRequestForOverallDetails; + + /** + * As soon as the first {@link #getLiveLeaderboard(Collection, boolean)} request has been received, this field tells the * "validity time point" (as opposed to the request time point; so not when the request was received but for which * time point the request was asking the data) for which the last general request asked the leaderboard contents, * regardless the combination of column details requested. This is used to decide when to stop a thread running this @@ -117,14 +124,15 @@ public class LiveLeaderboardUpdater implements Runnable { return leaderboard; } - public LeaderboardDTO getLiveLeaderboard(Collection namesOfRaceColumnsForWhichToLoadLegDetails) throws NoWindException { + public LeaderboardDTO getLiveLeaderboard(Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails) throws NoWindException { LeaderboardDTO result = null; - updateRequestTimes(namesOfRaceColumnsForWhichToLoadLegDetails); + updateRequestTimes(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); ensureRunning(); synchronized (this) { if (running && columnNamesForWhichCurrentLiveLeaderboardHasTheDetails - .containsAll(namesOfRaceColumnsForWhichToLoadLegDetails)) { + .containsAll(namesOfRaceColumnsForWhichToLoadLegDetails) + && (!addOverallDetails || currentLiveLeaderboardHasOverallDetails)) { result = currentLiveLeaderboard; if (result != null) { cacheHitCount++; @@ -140,12 +148,13 @@ public class LiveLeaderboardUpdater implements Runnable { cacheMissCount++; synchronized (this) { while (result == null) { - if (columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails)) { + if (columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails) && + (!addOverallDetails || currentLiveLeaderboardHasOverallDetails)) { result = currentLiveLeaderboard; } if (result == null) { if (logger.isLoggable(Level.FINEST)) { - logger.finest("waiting for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails); + logger.finest("waiting for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" and addOverallDetails="+addOverallDetails); } ensureRunning(); try { @@ -153,26 +162,27 @@ public class LiveLeaderboardUpdater implements Runnable { } catch (InterruptedException e) { logger.log(Level.INFO, "interrupted while waiting for LiveLeaderboardCache update", e); } - if (columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails)) { + if (columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.containsAll(namesOfRaceColumnsForWhichToLoadLegDetails) && + (!addOverallDetails || currentLiveLeaderboardHasOverallDetails)) { result = currentLiveLeaderboard; if (logger.isLoggable(Level.FINEST)) { - logger.finest("successfully waited for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails); + logger.finest("successfully waited for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" and addOverallDetails="+addOverallDetails); } } else { if (logger.isLoggable(Level.FINEST)) { - logger.finest("waiting for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" unsuccessful. Need to try again..."); + logger.finest("waiting for leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" and addOverallDetails="+addOverallDetails+" unsuccessful. Need to try again..."); } } } else { if (logger.isLoggable(Level.FINEST)) { - logger.finest("leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" was provided in the meantime"); + logger.finest("leaderboard for "+namesOfRaceColumnsForWhichToLoadLegDetails+" and addOverallDetails="+addOverallDetails+" was provided in the meantime"); } } // now we either have a result (good, we're done), or the thread stopped running (then we need to renew the request) // or the thread still runs but the result may have expired and therefore be null (renew the request) if (result == null) { // need to renew the request - updateRequestTimes(namesOfRaceColumnsForWhichToLoadLegDetails); + updateRequestTimes(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); ensureRunning(); } } @@ -205,8 +215,11 @@ public class LiveLeaderboardUpdater implements Runnable { * * This method assumes that namesOfRaceColumnsForWhichToLoadLegDetails tells the column names for which * result has the details. + * + * @param addOverallDetails tells whether overall details are requested for the resulting leaderboard; see also + * {@link Leaderboard#computeDTO(TimePoint, Collection, boolean, boolean, TrackedRegattaRegistry, DomainFactory)}. */ - private synchronized void updateRequestTimes(Collection namesOfRaceColumnsForWhichToLoadLegDetails) { + private synchronized void updateRequestTimes(Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails) { lastRequest = getLeaderboard().getNowMinusDelay(); for (String nameOfRaceColumn : namesOfRaceColumnsForWhichToLoadLegDetails) { if (!timePointOfLastRequestForColumnDetails.containsKey(nameOfRaceColumn) || @@ -214,22 +227,28 @@ public class LiveLeaderboardUpdater implements Runnable { timePointOfLastRequestForColumnDetails.put(nameOfRaceColumn, lastRequest); } } + if (addOverallDetails && (timePointOfLastRequestForOverallDetails == null || lastRequest.after(timePointOfLastRequestForOverallDetails))) { + timePointOfLastRequestForOverallDetails = lastRequest; + } } /** * Updates the cache contents and notifies all waiters on this object. */ private synchronized void updateCacheContents(Collection namesOfRaceColumnsForWhichToLoadLegDetails, - LeaderboardDTO result) { + boolean addOverallDetails, LeaderboardDTO result) { columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.clear(); columnNamesForWhichCurrentLiveLeaderboardHasTheDetails.addAll(namesOfRaceColumnsForWhichToLoadLegDetails); currentLiveLeaderboard = result; + currentLiveLeaderboardHasOverallDetails = addOverallDetails; notifyAll(); } /** - * Keeps computing the leaderboard for the column names that appear as key in {@link #timePointOfLastRequestForColumnDetails} - * until no request has happened + * Keeps computing the leaderboard for the column names that appear as key in + * {@link #timePointOfLastRequestForColumnDetails} and for the overall details as noted in + * {@link #timePointOfLastRequestForOverallDetails} until no request has happened during the + * {@link #UPDATE_TIMEOUT_IN_MILLIS timeout period}. */ @Override public void run() { @@ -253,10 +272,11 @@ public class LiveLeaderboardUpdater implements Runnable { TimePoint timeLastUpdateWasStarted = now; try { final Set namesOfRaceColumnsForWhichToLoadLegDetails = getColumnNamesForWhichToFetchDetails(timePoint); + final boolean addOverallDetails = getOverallDetails(timePoint); LeaderboardDTO newCacheValue = leaderboard.computeDTO(timePoint, - namesOfRaceColumnsForWhichToLoadLegDetails, /* waitForLatestAnalyses */false, - trackedRegattaRegistry, baseDomainFactory); - updateCacheContents(namesOfRaceColumnsForWhichToLoadLegDetails, newCacheValue); + namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, + /* waitForLatestAnalyses */false, trackedRegattaRegistry, baseDomainFactory); + updateCacheContents(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, newCacheValue); } catch (NoWindException e) { logger.info("Unable to update cached leaderboard results for leaderboard " + leaderboard.getName() + ": " + e.getMessage()); @@ -290,6 +310,17 @@ public class LiveLeaderboardUpdater implements Runnable { } } + /** + * Determines based on {@link #timePointOfLastRequestForOverallDetails} and the timePoint parameter + * if the duration between then is less than {@link #UPDATE_TIMEOUT_IN_MILLIS} in which case overall details are to + * be loaded. If {@link #timePointOfLastRequestForOverallDetails} is null, meaning that overall details + * have never been requested for the leaderboard before, false is returned immediately. + */ + private boolean getOverallDetails(TimePoint timePoint) { + return timePointOfLastRequestForOverallDetails != null && + !timePoint.after(timePointOfLastRequestForOverallDetails.plus(UPDATE_TIMEOUT_IN_MILLIS)); + } + /** * Determines those column names from {@link #timePointOfLastRequestForColumnDetails}'s keys for which the last * request is less then {@link #UPDATE_TIMEOUT_IN_MILLIS} milliseconds before timePoint diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java index a980a6f90f4..ea9eb79de19 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java @@ -14,11 +14,10 @@ import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Util; public abstract class AbstractScoringSchemeImpl implements ScoringScheme { private static final long serialVersionUID = 6830414905539642446L; @@ -128,16 +127,16 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { * Assuming both competitors scored in the same number of races, compares the sorted scores. */ @Override - public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { + public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { assert o1Scores.size() == o2Scores.size(); final Comparator pureScoreComparator = getScoreComparator(nullScoresAreBetter); // needs to compare net points; therefore, divide the total points by the column factor for comparison: List o1NetScores = new ArrayList<>(); - for (Pair o1ColumnAndScore : o1Scores) { + for (com.sap.sse.common.Util.Pair o1ColumnAndScore : o1Scores) { o1NetScores.add(o1ColumnAndScore.getB()/o1ColumnAndScore.getA().getFactor()); } List o2NetScores = new ArrayList<>(); - for (Pair o2ColumnAndScore : o2Scores) { + for (com.sap.sse.common.Util.Pair o2ColumnAndScore : o2Scores) { o2NetScores.add(o2ColumnAndScore.getB()/o2ColumnAndScore.getA().getFactor()); } Collections.sort(o1NetScores, pureScoreComparator); @@ -162,7 +161,7 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { } @Override - public int compareByLastRace(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { + public int compareByLastRace(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { int result = 0; if (!o1Scores.isEmpty() && !o2Scores.isEmpty()) { result = getScoreComparator(nullScoresAreBetter).compare(o1Scores.get(o1Scores.size()-1).getB(), 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 072f7ed20a3..e2a07613b7a 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 @@ -39,6 +39,7 @@ import com.sap.sailing.domain.base.RaceColumn; 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.Duration; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.MaxPointsReason; @@ -56,11 +57,8 @@ 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.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; @@ -88,6 +86,7 @@ import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; import com.sap.sailing.util.impl.RaceColumnListeners; import com.sap.sailing.util.impl.ThreadFactoryWithPriority; +import com.sap.sse.common.Util; /** * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events @@ -134,7 +133,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race private final Set suppressedCompetitors; private final NamedReentrantReadWriteLock suppressedCompetitorsLock; - private transient Map, RunnableFuture> raceDetailsAtEndOfTrackingCache; + private transient Map, RunnableFuture> raceDetailsAtEndOfTrackingCache; /** * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor} @@ -278,7 +277,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race private void invalidateCacheAndRemoveThisListenerFromTrackedRace() { synchronized (raceDetailsAtEndOfTrackingCache) { - raceDetailsAtEndOfTrackingCache.remove(new Pair(trackedRace, competitor)); + raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair(trackedRace, competitor)); removeFromTrackedRace(); } } @@ -356,7 +355,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race this.suppressedCompetitors = new HashSet(); this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); this.raceColumnListeners = new RaceColumnListeners(); - this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); + this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); initTransientFields(); } @@ -375,7 +374,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } private void initTransientFields() { - this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); + 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 @@ -548,11 +547,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race */ @Override public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { - final Map> netPointsAndFleet = new HashMap>(); + final Map> netPointsAndFleet = new HashMap>(); for (Competitor competitor : getCompetitors()) { Double netPoints = getNetPoints(competitor, raceColumn, timePoint); if (netPoints != null) { - netPointsAndFleet.put(competitor, new Pair(netPoints, raceColumn.getFleetOfCompetitor(competitor))); + netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(netPoints, raceColumn.getFleetOfCompetitor(competitor))); } } List result = new ArrayList(netPointsAndFleet.keySet()); @@ -692,8 +691,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public Map, Entry> getContent(final TimePoint timePoint) throws NoWindException { - Map, Entry> result = new HashMap, Entry>(); + 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()) { @@ -717,7 +716,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race discarded ? DOUBLE_0 : (correctedScore==null?null: Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(), discarded, raceColumn.getFleetOfCompetitor(competitor)); - result.put(new Pair(competitor, raceColumn), entry); + result.put(new com.sap.sse.common.Util.Pair(competitor, raceColumn), entry); } } return result; @@ -852,8 +851,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) { - Pair result = null; + 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)) { @@ -868,7 +867,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } else { to = timePoint; } - Pair maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to); + 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; @@ -911,8 +910,12 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public Long getTotalTimeSailedInLegTypeInMilliseconds(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException { - Long result = null; + 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()) { @@ -920,26 +923,33 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race trackedRace.getRace().getCourse().lockForRead(); try { for (Leg leg : trackedRace.getRace().getCourse().getLegs()) { - TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg); - if (trackedLeg.hasStartedLeg(timePoint)) { + TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg); + if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) { // find out leg type at the time the competitor started the leg try { - LegType trackedLegType = trackedRace.getTrackedLeg(leg).getLegType( - trackedLeg.getStartTime()); + 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) { - Long millisecondsSpendOnDownwind = trackedLeg.getTimeInMilliSeconds(timePoint); - if (millisecondsSpendOnDownwind != null) { + Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint); + if (timeSpentOnDownwind != null) { if (result == null) { - result = millisecondsSpendOnDownwind; + result = timeSpentOnDownwind; } else { - result += millisecondsSpendOnDownwind; + 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. + // 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; } @@ -960,8 +970,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public Long getTotalTimeSailedInMilliseconds(Competitor competitor, TimePoint timePoint) { - Long result = null; + 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); @@ -984,11 +994,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race to = timePoint; } } - long timeSpent = to.asMillis() - from.asMillis(); + Duration timeSpent = from.until(to); if (result == null) { result = timeSpent; } else { - result += timeSpent; + result=result.plus(timeSpent); } } } @@ -1091,43 +1101,67 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race @Override public LeaderboardDTO computeDTO(final TimePoint timePoint, - final Collection namesOfRaceColumnsForWhichToLoadLegDetails, final boolean waitForLatestAnalyses, - TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory) + final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory) throws NoWindException { long startOfRequestHandling = System.currentTimeMillis(); - LeaderboardDTO result = null; - result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null + final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(), this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), this - .getScoringScheme().isHigherBetter(), new UUIDGenerator()); + .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); } - for (RaceColumn raceColumn : this.getRaceColumns()) { - RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), + // 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(), this.getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint)); 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(), fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race); } - result.setCompetitorsFromBestToWorst(raceColumnDTO, - baseDomainFactory.getCompetitorDTOList(this.getCompetitorsFromBestToWorst(raceColumn, timePoint))); + FutureTask> task = new FutureTask>(new Callable>() { + @Override + public List call() throws Exception { + return 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(); @@ -1139,8 +1173,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race result.discardThresholds = null; } // competitor, leading to square effort. We therefore need to compute the leg ranks for those race where leg - // details - // are requested only once and pass them into getLeaderboardEntryDTO + // 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 @@ -1168,7 +1201,9 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race row.fieldsByRaceColumnName = new HashMap(); row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null; row.totalPoints = this.getTotalPoints(competitor, timePoint); - addOverallDetailsToRow(timePoint, competitor, row); + if (addOverallDetails) { + addOverallDetailsToRow(timePoint, competitor, row); + } result.competitors.add(competitorDTO); Map> futuresForColumnName = new HashMap>(); for (final RaceColumn raceColumn : this.getRaceColumns()) { @@ -1177,17 +1212,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race @Override public LeaderboardEntryDTO call() { try { - Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, - timePoint); - return getLeaderboardEntryDTO( - entry, - raceColumn, - competitor, - timePoint, + Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint); + return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint, namesOfRaceColumnsForWhichToLoadLegDetails != null && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn - .getName()), waitForLatestAnalyses, legRanksCache, - baseDomainFactory); + .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory); } catch (NoWindException e) { logger.info("Exception trying to compute leaderboard entry for competitor " + competitor.getName() + " in race column " + raceColumn.getName() + ": " @@ -1212,12 +1241,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race // 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. + // this helps robustness from a user's perspective. logger.log( Level.SEVERE, AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " - + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails + + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails + "): exception during computing leaderboard entry for competitor " + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey() + ". Leaving empty.", e); @@ -1229,26 +1257,28 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race result.competitorDisplayNames.put(competitorDTO, displayName); } } - logger.info("computeLeaderboardByName("+this.getName()+", "+timePoint+", "+namesOfRaceColumnsForWhichToLoadLegDetails+") took "+ - (System.currentTimeMillis()-startOfRequestHandling)+"ms"); + 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 Pair maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint); + 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(); } - final Long totalTimeSailedDownwindInMilliseconds = this.getTotalTimeSailedInLegTypeInMilliseconds(competitor, LegType.DOWNWIND, timePoint); - row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwindInMilliseconds==null?null:1./1000.*totalTimeSailedDownwindInMilliseconds; - final Long totalTimeSailedUpwindInMilliseconds = this.getTotalTimeSailedInLegTypeInMilliseconds(competitor, LegType.UPWIND, timePoint); - row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwindInMilliseconds==null?null:1./1000.*totalTimeSailedUpwindInMilliseconds; - final Long totalTimeSailedReachingInMilliseconds = this.getTotalTimeSailedInLegTypeInMilliseconds(competitor, LegType.REACHING, timePoint); - row.totalTimeSailedReachingInSeconds = totalTimeSailedReachingInMilliseconds==null?null:1./1000.*totalTimeSailedReachingInMilliseconds; - final Long totalTimeSailedInMilliseconds = this.getTotalTimeSailedInMilliseconds(competitor, timePoint); - row.totalTimeSailedInSeconds = totalTimeSailedInMilliseconds==null?null:1./1000.*totalTimeSailedInMilliseconds; + 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(); } @@ -1271,9 +1301,12 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race 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); - entryDTO.averageSamplingInterval = trackedRace.getTrack(competitor).getAverageIntervalBetweenFixes(); + if (track != null) { + entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenFixes(); + } if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) { long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime(); entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0; @@ -1287,8 +1320,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race entryDTO.legDetails = raceDetails.getLegDetails(); entryDTO.windwardDistanceToOverallLeaderInMeters = raceDetails.getWindwardDistanceToOverallLeader() == null ? null : raceDetails.getWindwardDistanceToOverallLeader().getMeters(); - entryDTO.averageCrossTrackErrorInMeters = raceDetails.getAverageCrossTrackError() == null ? null - : raceDetails.getAverageCrossTrackError().getMeters(); + entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null + : raceDetails.getAverageAbsoluteCrossTrackError().getMeters(); + entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null + : raceDetails.getAverageSignedCrossTrackError().getMeters(); final TimePoint startOfRace = trackedRace.getStartOfRace(); if (startOfRace != null) { Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); @@ -1308,12 +1343,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race competitor, startOfRace); entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null : distanceToStartLineAtStartOfRace.getMeters(); - Speed speedAtStartTime = trackedRace.getTrack(competitor) - .getEstimatedSpeed(startOfRace); + Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace); entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null : speedAtStartTime.getKnots(); TimePoint competitorStartTime = firstMarkPassing.getTimePoint(); - Speed competitorSpeedWhenPassingStart = trackedRace.getTrack(competitor) + Speed competitorSpeedWhenPassingStart = track == null ? null : track .getEstimatedSpeed(competitorStartTime); entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null : competitorSpeedWhenPassingStart.getKnots(); @@ -1365,13 +1399,15 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race private static class RaceDetails { private final List legDetails; private final Distance windwardDistanceToOverallLeader; - private final Distance averageCrossTrackError; + private final Distance averageAbsoluteCrossTrackError; + private final Distance averageSignedCrossTrackError; public RaceDetails(List legDetails, Distance windwardDistanceToOverallLeader, - Distance averageCrossTrackError) { + Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError) { super(); this.legDetails = legDetails; this.windwardDistanceToOverallLeader = windwardDistanceToOverallLeader; - this.averageCrossTrackError = averageCrossTrackError; + this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError; + this.averageSignedCrossTrackError = averageSignedCrossTrackError; } public List getLegDetails() { return legDetails; @@ -1379,8 +1415,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race public Distance getWindwardDistanceToOverallLeader() { return windwardDistanceToOverallLeader; } - public Distance getAverageCrossTrackError() { - return averageCrossTrackError; + public Distance getAverageAbsoluteCrossTrackError() { + return averageAbsoluteCrossTrackError; + } + public Distance getAverageSignedCrossTrackError() { + return averageSignedCrossTrackError; } } @@ -1409,7 +1448,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace, final Competitor competitor, final Map> legRanksCache) throws NoWindException, InterruptedException, ExecutionException { - final Pair key = new Pair(trackedRace, competitor); + 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); @@ -1460,9 +1499,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } final Distance windwardDistanceToOverallLeader = trackedRace == null ? null : trackedRace .getWindwardDistanceToOverallLeader(competitor, timePoint); - final Distance averageCrossTrackError = trackedRace == null ? null : trackedRace.getAverageCrossTrackError( + final Distance averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError( competitor, timePoint, waitForLatestAnalyses); - return new RaceDetails(legDetails, windwardDistanceToOverallLeader, averageCrossTrackError); + final Distance averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError( + competitor, timePoint, waitForLatestAnalyses); + return new RaceDetails(legDetails, windwardDistanceToOverallLeader, averageAbsoluteCrossTrackError, averageSignedCrossTrackError); } finally { course.unlockAfterRead(); } @@ -1478,8 +1519,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint); final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint); result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); - final Distance averageCrossTrackError = trackedLeg.getAverageCrossTrackError(timePoint, waitForLatestAnalyses); - result.averageCrossTrackErrorInMeters = averageCrossTrackError == null ? null : averageCrossTrackError.getMeters(); + final Distance averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses); + result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters(); + final Distance averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses); + result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters(); Double speedOverGroundInKnots; if (trackedLeg.hasFinishedLeg(timePoint)) { speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); @@ -1491,7 +1534,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); result.estimatedTimeToNextWaypointInSeconds = trackedLeg.getEstimatedTimeToNextMarkInSeconds(timePoint); - result.timeInMilliseconds = trackedLeg.getTimeInMilliSeconds(timePoint); + result.timeInMilliseconds = trackedLeg.getTime(timePoint).asMillis(); result.finished = trackedLeg.hasFinishedLeg(timePoint); result.gapToLeaderInSeconds = trackedLeg.getGapToLeaderInSeconds(timePoint, legRanksCache.get(trackedLeg.getLeg()).entrySet().iterator().next().getKey()); @@ -1624,10 +1667,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } private LeaderboardDTO getLiveLeaderboard(Collection namesOfRaceColumnsForWhichToLoadLegDetails, - TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException { + boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException { LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry, baseDomainFactory); - return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails); + return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); } private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry, @@ -1664,8 +1707,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, Collection namesOfRaceColumnsForWhichToLoadLegDetails, - TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, InterruptedException, ExecutionException { + public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, + Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, + InterruptedException, ExecutionException { LeaderboardDTO result = null; if (timePoint == null) { // date==null means live mode; however, if we're after the end of all races and after all score @@ -1681,7 +1726,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } 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, trackedRegattaRegistry, baseDomainFactory); + result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory); } } if (timePoint != null) { @@ -1689,7 +1734,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race // which is OK because the cache is invalidated whenever any of the tracked races attached to the // leaderboard changes. result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, - baseDomainFactory, trackedRegattaRegistry); + addOverallDetails, baseDomainFactory, trackedRegattaRegistry); } return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/EventLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/EventLeaderboardImpl.java deleted file mode 100644 index 027d95ed590..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/EventLeaderboardImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.domain.leaderboard.impl; - -import java.util.List; - -import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.leaderboard.EventLeaderboardGroup; -import com.sap.sailing.domain.leaderboard.Leaderboard; - -public class EventLeaderboardImpl extends LeaderboardGroupImpl implements EventLeaderboardGroup { - private static final long serialVersionUID = -8933204023833087837L; - private Event event; - - public EventLeaderboardImpl(Event event, String name, String description, boolean displayGroupsInReverseOrder, List leaderboards) { - super(name, description, displayGroupsInReverseOrder, leaderboards); - this.event = event; - } - - @Override - public Event getEvent() { - return event; - } - - public String toString() { - return super.toString() + " || " + getEvent().toString(); - } -} 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 a61fe685c5b..487998135e4 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 @@ -19,6 +19,7 @@ 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.RaceLogInformation; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; import com.sap.sailing.domain.racelog.impl.RaceLogInformationImpl; @@ -67,11 +68,16 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements /** * 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. + * {@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(new RaceLogInformationImpl(raceLogStore, new RaceLogOnLeaderboardIdentifier( + this, column.getName()))); + } } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointExtremeSailingSeriesOverall.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointExtremeSailingSeriesOverall.java index ee7ecdf16b5..d42b8a7a1f4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointExtremeSailingSeriesOverall.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointExtremeSailingSeriesOverall.java @@ -10,11 +10,10 @@ import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.MetaLeaderboard; import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; +import com.sap.sse.common.Util; /** * A variant of the {@link HighPoint} scoring scheme which breaks ties differently and which assigns a score of 10 to @@ -52,7 +51,7 @@ public class HighPointExtremeSailingSeriesOverall extends HighPoint { * Implements rule 13.5 of the Extreme Sailing Series notice of race as of August 2012. */ @Override - public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { + public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { assert o1Scores.size() == o2Scores.size(); int o1Wins = getWins(o1Scores); int o2Wins = getWins(o2Scores); @@ -67,9 +66,9 @@ public class HighPointExtremeSailingSeriesOverall extends HighPoint { * Counts a competitor's wins by comparing the scores to {@link #MAX_POINTS} which is the score attributed to a race * won */ - private int getWins(List> scores) { + private int getWins(List> scores) { int wins = 0; - for (Pair score : scores) { + for (com.sap.sse.common.Util.Pair score : scores) { if (Math.abs(score.getB() - MAX_POINTS * score.getA().getFactor()) < 0.0000001) { wins++; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java index e4abd11e21d..41dbe043fcf 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java @@ -4,7 +4,7 @@ import java.util.List; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public class HighPointFirstGets10LastBreaksTie extends HighPointFirstGetsFixedScore { private static final long serialVersionUID = 1L; @@ -19,7 +19,7 @@ public class HighPointFirstGets10LastBreaksTie extends HighPointFirstGetsFixedSc } @Override - public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { + public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { return 0; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java index 7e545cb0d2b..db01a6a8748 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java @@ -6,8 +6,8 @@ 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.common.ScoringSchemeType; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; +import com.sap.sse.common.Util; public class HighPointFirstGets1LastBreaksTie extends HighPointFirstGetsFixedScore { private static final long serialVersionUID = 1L; @@ -28,7 +28,7 @@ public class HighPointFirstGets1LastBreaksTie extends HighPointFirstGetsFixedSco } @Override - public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { + public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { return 0; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGetsFixedScore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGetsFixedScore.java index 247e7ae14b1..5742b5211ad 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGetsFixedScore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGetsFixedScore.java @@ -21,14 +21,39 @@ public abstract class HighPointFirstGetsFixedScore extends HighPoint { private final double scoreForRaceWinner; + private final double minimumScoreFromRank; + public HighPointFirstGetsFixedScore(double scoreForRaceWinner) { + this(scoreForRaceWinner, /* minimumScoreFromRank */ 1.0); + } + + protected HighPointFirstGetsFixedScore(double scoreForRaceWinner, double minimumScoreFromRank) { this.scoreForRaceWinner = scoreForRaceWinner; + this.minimumScoreFromRank = minimumScoreFromRank; + } + + protected double getScoreForRaceWinner() { + return scoreForRaceWinner; + } + + private double getMinimumScoreFromRank() { + return minimumScoreFromRank; } @Override public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Callable numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) { - final int effectiveRank; + final int effectiveRank = getEffectiveRank(raceColumn, competitor, rank); final Double result; + if (effectiveRank == 0) { + result = null; + } else { + result = Math.max(getMinimumScoreFromRank(), (double) (getScoreForRaceWinner() - effectiveRank + 1)); + } + return result; + } + + protected int getEffectiveRank(RaceColumn raceColumn, Competitor competitor, int rank) { + final int effectiveRank; int competitorFleetOrdering; if (rank == 0) { effectiveRank = 0; @@ -41,11 +66,6 @@ public abstract class HighPointFirstGetsFixedScore extends HighPoint { effectiveRank = rank; } } - if (effectiveRank == 0) { - result = null; - } else { - result = Math.max(1.0, (double) (scoreForRaceWinner - effectiveRank + 1)); - } - return result; + return effectiveRank; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java index 6219d68ae36..05ae9f463a4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java @@ -4,7 +4,7 @@ import java.util.List; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; /** * In this variant of the {@link HighPoint} scoring scheme, scoring by "better race" (sometimes referred to as a @@ -18,7 +18,7 @@ public class HighPointLastBreaksTie extends HighPoint { private static final long serialVersionUID = -5338636946886101669L; @Override - public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { + public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { return 0; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsFive.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsFive.java new file mode 100644 index 00000000000..2f1a17f85f4 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsFive.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.leaderboard.impl; + +import com.sap.sailing.domain.common.ScoringSchemeType; + +public class HighPointWinnerGetsFive extends HighPointFirstGetsFixedScore { + private static final long serialVersionUID = 1367473387431601943L; + + public HighPointWinnerGetsFive() { + super(/* scoreForRaceWinner */ 5, /* minimumScoreFromRank */ 0.0); + } + + @Override + public ScoringSchemeType getType() { + return ScoringSchemeType.HIGH_POINT_WINNER_GETS_FIVE; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsSix.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsSix.java new file mode 100755 index 00000000000..bf116c8f4a0 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsSix.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.leaderboard.impl; + +import com.sap.sailing.domain.common.ScoringSchemeType; + +public class HighPointWinnerGetsSix extends HighPointFirstGetsFixedScore { + private static final long serialVersionUID = -4116295702464748719L; + + public HighPointWinnerGetsSix() { + super(/* scoreForRaceWinner */ 6); + } + + @Override + public ScoringSchemeType getType() { + return ScoringSchemeType.HIGH_POINT_WINNER_GETS_SIX; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardGroupImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardGroupImpl.java index ad13aa39857..0133d018fe9 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardGroupImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardGroupImpl.java @@ -5,16 +5,16 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; +import com.sap.sailing.domain.base.impl.LeaderboardGroupBaseImpl; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener; -public class LeaderboardGroupImpl implements LeaderboardGroup { +public class LeaderboardGroupImpl extends LeaderboardGroupBaseImpl implements LeaderboardGroup { private static final long serialVersionUID = 2035927369446736934L; - private String name; - private String description; private boolean displayGroupsInReverseOrder; private final List leaderboards; private final Set listeners; @@ -26,14 +26,23 @@ public class LeaderboardGroupImpl implements LeaderboardGroup { */ private Leaderboard overallLeaderboard; + /** + * Creates a new leaderboard group with a new UUID as its ID. + */ public LeaderboardGroupImpl(String name, String description, boolean displayGroupsInReverseOrder, List leaderboards) { - this.name = name; - this.description = description; + this(UUID.randomUUID(), name, description, displayGroupsInReverseOrder, leaderboards); + } + + /** + * Use this constructor when loading or deserializing a leaderboard group and the ID is known and is provided to the constructor. + */ + public LeaderboardGroupImpl(UUID id, String name, String description, boolean displayGroupsInReverseOrder, List leaderboards) { + super(id, name, description); this.displayGroupsInReverseOrder = displayGroupsInReverseOrder; this.leaderboards = new ArrayList(leaderboards); this.listeners = new HashSet(); } - + @Override public void addLeaderboardGroupListener(LeaderboardGroupListener listener) { synchronized (listeners) { @@ -66,34 +75,21 @@ public class LeaderboardGroupImpl implements LeaderboardGroup { } } + @Override public Leaderboard getOverallLeaderboard() { return overallLeaderboard; } + @Override + public boolean hasOverallLeaderboard() { + return getOverallLeaderboard() != null; + } + + @Override public void setOverallLeaderboard(Leaderboard overallLeaderboard) { this.overallLeaderboard = overallLeaderboard; } - @Override - public String getName() { - return name; - } - - @Override - public void setName(String name) { - this.name = name; - } - - @Override - public String getDescription() { - return description; - } - - @Override - public void setDescriptiom(String description) { - this.description = description; - } - @Override public synchronized Iterable getLeaderboards() { return new ArrayList(leaderboards); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java index ab3b4771a3f..d055c7f8133 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java @@ -13,9 +13,9 @@ import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.NoWindError; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sse.common.Util; /** * Compares two competitors that occur in a {@link Leaderboard#getCompetitors()} set in the context of the @@ -49,7 +49,7 @@ import com.sap.sailing.domain.leaderboard.ScoringScheme; public class LeaderboardTotalRankComparator implements Comparator { private final Leaderboard leaderboard; private final ScoringScheme scoringScheme; - private final Map, Double> totalPointsCache; + private final Map, Double> totalPointsCache; private final boolean nullScoresAreBetter; private final TimePoint timePoint; @@ -79,10 +79,10 @@ public class LeaderboardTotalRankComparator implements Comparator { this.timePoint = timePoint; this.scoringScheme = scoringScheme; this.nullScoresAreBetter = nullScoresAreBetter; - totalPointsCache = new HashMap, Double>(); + totalPointsCache = new HashMap, Double>(); for (Competitor competitor : leaderboard.getCompetitors()) { for (RaceColumn raceColumn : raceColumnsToConsider) { - totalPointsCache.put(new Pair(competitor, raceColumn), + totalPointsCache.put(new Util.Pair(competitor, raceColumn), leaderboard.getTotalPoints(competitor, raceColumn, raceColumnsToConsider, timePoint)); } } @@ -94,8 +94,8 @@ public class LeaderboardTotalRankComparator implements Comparator { @Override public int compare(Competitor o1, Competitor o2) { - List> o1Scores = new ArrayList>(); - List> o2Scores = new ArrayList>(); + List> o1Scores = new ArrayList>(); + List> o2Scores = new ArrayList>(); double o1ScoreSum = getLeaderboard().getCarriedPoints(o1); double o2ScoreSum = getLeaderboard().getCarriedPoints(o2); Double o1MedalRaceScore = 0.0; @@ -109,18 +109,18 @@ public class LeaderboardTotalRankComparator implements Comparator { needToResetO2ScoreUponNextValidResult = raceColumn.isStartsWithZeroScore(); if (getLeaderboard().getScoringScheme().isValidInTotalScore(getLeaderboard(), raceColumn, timePoint)) { int preemptiveColumnResult = 0; - final Double o1Score = totalPointsCache.get(new Pair(o1, raceColumn)); + final Double o1Score = totalPointsCache.get(new Util.Pair(o1, raceColumn)); if (o1Score != null) { - o1Scores.add(new Pair(raceColumn, o1Score)); + o1Scores.add(new Util.Pair(raceColumn, o1Score)); if (needToResetO1ScoreUponNextValidResult) { o1ScoreSum = 0; needToResetO1ScoreUponNextValidResult = false; } o1ScoreSum += o1Score; } - final Double o2Score = totalPointsCache.get(new Pair(o2, raceColumn)); + final Double o2Score = totalPointsCache.get(new Util.Pair(o2, raceColumn)); if (o2Score != null) { - o2Scores.add(new Pair(raceColumn, o2Score)); + o2Scores.add(new Util.Pair(raceColumn, o2Score)); if (needToResetO2ScoreUponNextValidResult) { o2ScoreSum = 0; needToResetO2ScoreUponNextValidResult = false; @@ -264,7 +264,7 @@ public class LeaderboardTotalRankComparator implements Comparator { * * @see ScoringScheme#compareByBetterScore(List, List, boolean) */ - protected int compareByBetterScore(List> o1Scores, List> o2Scores) { + protected int compareByBetterScore(List> o1Scores, List> o2Scores) { return scoringScheme.compareByBetterScore(o1Scores, o2Scores, nullScoresAreBetter); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/PerSeriesResultDiscardingRuleImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/PerSeriesResultDiscardingRuleImpl.java index 5b3c2522762..250dd101182 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/PerSeriesResultDiscardingRuleImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/PerSeriesResultDiscardingRuleImpl.java @@ -8,9 +8,9 @@ import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; +import com.sap.sse.common.Util; /** * A result discarding rule that is used when one or more of a {@link Regatta}'s {@link Series} define their own diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java index 3b8fce93ac2..df5e68f3544 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java @@ -13,8 +13,6 @@ 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.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; import com.sap.sailing.domain.leaderboard.ScoreCorrectionListener; @@ -22,6 +20,7 @@ import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Util; /** * Implements the basic logic of assigning a maximum score to a competitor in a race if that competitor was @@ -38,12 +37,12 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { /** * If no max point reason is provided for a competitor/race, {@link MaxPointsReason#NONE} should be the default. */ - private final Map, MaxPointsReason> maxPointsReasons; + private final Map, MaxPointsReason> maxPointsReasons; /** * If no score correction is provided here, the uncorrected points are the default. */ - private final Map, Double> correctedScores; + private final Map, Double> correctedScores; /** * If null, despite a non-null {@link #timePointOfLastCorrectionsValidity} value the @@ -65,8 +64,8 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { public ScoreCorrectionImpl(Leaderboard leaderboard) { this.leaderboard = leaderboard; - this.maxPointsReasons = new HashMap, MaxPointsReason>(); - this.correctedScores = new HashMap, Double>(); + this.maxPointsReasons = new HashMap, MaxPointsReason>(); + this.correctedScores = new HashMap, Double>(); this.scoreCorrectionListeners = new HashSet(); } @@ -124,7 +123,7 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { @Override public void setMaxPointsReason(Competitor competitor, RaceColumn raceColumn, MaxPointsReason reason) { - Pair key = raceColumn.getKey(competitor); + com.sap.sse.common.Util.Pair key = raceColumn.getKey(competitor); MaxPointsReason oldMaxPointsReason; if (reason == null) { oldMaxPointsReason = maxPointsReasons.remove(key); @@ -142,7 +141,7 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { @Override public boolean isScoreCorrected(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - Pair key = raceColumn.getKey(competitor); + com.sap.sse.common.Util.Pair key = raceColumn.getKey(competitor); return (correctedScores.containsKey(key) && !isCertainlyBeforeRaceFinish(timePoint, raceColumn, competitor)) || (maxPointsReasons.containsKey(key) && isMaxPointsReasonApplicable(maxPointsReasons.get(key), timePoint, raceColumn, competitor)); } @@ -454,12 +453,12 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { @Override public boolean hasCorrectionFor(RaceColumn raceInLeaderboard) { - for (Pair correctedScoresKey : correctedScores.keySet()) { + for (com.sap.sse.common.Util.Pair correctedScoresKey : correctedScores.keySet()) { if (correctedScoresKey.getB() == raceInLeaderboard) { return true; } } - for (Pair maxPointsReasonsKey : maxPointsReasons.keySet()) { + for (com.sap.sse.common.Util.Pair maxPointsReasonsKey : maxPointsReasons.keySet()) { if (maxPointsReasonsKey.getB() == raceInLeaderboard) { return true; } @@ -494,10 +493,10 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { @Override public Iterable getRaceColumnsThatHaveCorrections() { Set result = new HashSet<>(); - for (Pair correctedScoresKey : correctedScores.keySet()) { + for (com.sap.sse.common.Util.Pair correctedScoresKey : correctedScores.keySet()) { result.add(correctedScoresKey.getB()); } - for (Pair maxPointsReasonsKey : maxPointsReasons.keySet()) { + for (com.sap.sse.common.Util.Pair maxPointsReasonsKey : maxPointsReasons.keySet()) { result.add(maxPointsReasonsKey.getB()); } return result; @@ -506,12 +505,12 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { @Override public Iterable getCompetitorsThatHaveCorrectionsIn(RaceColumn raceColumn) { Set result = new HashSet<>(); - for (Pair correctedScoresKey : correctedScores.keySet()) { + for (com.sap.sse.common.Util.Pair correctedScoresKey : correctedScores.keySet()) { if (raceColumn == correctedScoresKey.getB()) { result.add(correctedScoresKey.getA()); } } - for (Pair maxPointsReasonsKey : maxPointsReasons.keySet()) { + for (com.sap.sse.common.Util.Pair maxPointsReasonsKey : maxPointsReasons.keySet()) { if (raceColumn == maxPointsReasonsKey.getB()) { result.add(maxPointsReasonsKey.getA()); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/AbstractMetaLeaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/AbstractMetaLeaderboard.java index 3908026462b..dba8eb2aeb1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/AbstractMetaLeaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/AbstractMetaLeaderboard.java @@ -19,7 +19,6 @@ import com.sap.sailing.domain.base.impl.FleetImpl; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.MetaLeaderboard; import com.sap.sailing.domain.leaderboard.ScoreCorrectionListener; @@ -28,6 +27,7 @@ import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; import com.sap.sailing.domain.leaderboard.impl.AbstractSimpleLeaderboardImpl; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Util; /** * A leaderboard whose columns are defined by leaderboards. This can be useful for a regatta series where many regattas 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 7f3e7ca0fb2..ca5b157dbf2 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 @@ -4,6 +4,7 @@ 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; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardScoreCorrection.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardScoreCorrection.java index 8109435276c..4e1e4cd2a94 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardScoreCorrection.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardScoreCorrection.java @@ -4,11 +4,11 @@ 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.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.MetaLeaderboard; import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl; +import com.sap.sse.common.Util; public class MetaLeaderboardScoreCorrection extends ScoreCorrectionImpl { private static final long serialVersionUID = 3773423384260065869L; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateChooser.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateChooser.java index a329c502dd6..f936d476f83 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateChooser.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateChooser.java @@ -4,8 +4,8 @@ import java.util.LinkedHashMap; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sse.common.Util; /** * Takes the {@link Candidate}s from an {@link AbstractCandidateFinder} and calculates any new {@link MarkPassing}s. The @@ -17,7 +17,7 @@ import com.sap.sailing.domain.tracking.MarkPassing; public interface AbstractCandidateChooser { - public void calculateMarkPassDeltas(Competitor c, Pair, Iterable> candidateDeltas); + public void calculateMarkPassDeltas(Competitor c, Util.Pair, Iterable> candidateDeltas); public LinkedHashMap> getAllPasses(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateFinder.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateFinder.java index fbed6a4019c..6db9daf73bc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateFinder.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateFinder.java @@ -5,8 +5,8 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sse.common.Util; /** * A CandidateFinder converts the incoming GPSFixes of competitors and marks into {@link Candidate}s for each @@ -31,7 +31,7 @@ public interface AbstractCandidateFinder { * @param c * @return */ - Pair, Iterable> getCandidateDeltas(Competitor c, Iterable fixes); + Util.Pair, Iterable> getCandidateDeltas(Competitor c, Iterable fixes); /** * When initializing the calculator, the whole race until now is evaluated. For that purpose all of the Candidates @@ -41,5 +41,5 @@ public interface AbstractCandidateFinder { * the competitor whose {@link Candidate}s should be returned * @return all of the Candidates for the specified competitor */ - Pair, Iterable> getAllCandidates(Competitor c); + Util.Pair, Iterable> getAllCandidates(Competitor c); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java index 88f5cc28d66..041869b02c6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java @@ -6,27 +6,26 @@ import com.sap.sailing.domain.common.TimePoint; /** * A possible passing of a {@link Waypoint}. It contains the {@link Waypoint} that it might be passing, a * {@link TimePoint}, the probability that this candidate is a passing (e.g. based on the distance to the - * {@link Waypoint}) and the one-based(!) ID of this Waypoint. The ID is one based because the standard implemantation + * {@link Waypoint}) and the one-based(!) ID of this Waypoint. The ID is one based because the standard implementation * of {@link AbstractCandidateChooser} (see {@link CandidateChooser}) uses a proxy Candidates at the end and the - * beginning of the race, the one at the beginning recieves the ID 0. Candidates are created in an + * beginning of the race, the one at the beginning receives the ID 0. Candidates are created in an * {@link AbstractCandidateFinder}, which determines where a passing could be and assigns them * * @author Nicolas Klose * */ - public class Candidate implements Comparable { - private Waypoint w; - private TimePoint p; + private Waypoint waypoint; + private TimePoint timePoint; private double distanceProbability; private Integer id; private boolean rightSide; private boolean rightDirection; private String type; - public Candidate(int id, TimePoint p, double distanceProbability, Waypoint w, boolean rightSide, boolean rightDirection, String type) { - this.w = w; - this.p = p; + public Candidate(int id, TimePoint timePoint, double distanceProbability, Waypoint w, boolean rightSide, boolean rightDirection, String type) { + this.waypoint = w; + this.timePoint = timePoint; this.distanceProbability = distanceProbability; this.id = id; this.rightSide = rightSide; @@ -39,7 +38,7 @@ public class Candidate implements Comparable { } public TimePoint getTimePoint() { - return p; + return timePoint; } public Double getProbability() { @@ -49,15 +48,15 @@ public class Candidate implements Comparable { } public Waypoint getWaypoint() { - return w; + return waypoint; } public String toString() { - return type + "-Candidate for " + id + " with cost " + getProbability() + "and Timepoint " + p; + return type + "-Candidate for " + id + " with cost " + getProbability() + "and Timepoint " + timePoint; } @Override public int compareTo(Candidate arg0) { - return id != arg0.getID() ? id.compareTo(arg0.getID()) : p != arg0.getTimePoint() ? p.compareTo(arg0.getTimePoint()) : getProbability().compareTo(arg0.getProbability()); + return id != arg0.getID() ? id.compareTo(arg0.getID()) : timePoint != arg0.getTimePoint() ? timePoint.compareTo(arg0.getTimePoint()) : getProbability().compareTo(arg0.getProbability()); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java index 5a97cf70f40..261a20eccfa 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java @@ -16,11 +16,11 @@ import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MeterDistance; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; +import com.sap.sse.common.Util; /** * The standard implementation of {@link AbstractCandidateChooser}. A graph is created, with each {@link Candidate} as a @@ -67,8 +67,8 @@ public class CandidateChooser implements AbstractCandidateChooser { } @Override - public void calculateMarkPassDeltas(Competitor c, Pair, Iterable> candidateDeltas) { - if (race.getStartOfRace()!=null&&!race.getStartOfRace().minus(2000).equals(raceStartTime)) { + public void calculateMarkPassDeltas(Competitor c, Util.Pair, Iterable> candidateDeltas) { + if (race.getStartOfRace() != null && (raceStartTime == null || !race.getStartOfRace().minus(2000).equals(raceStartTime))) { raceStartTime = race.getStartOfRace().minus(2000); for (Competitor com : allEdges.keySet()) { removeCandidates(Arrays.asList(start), com); @@ -116,23 +116,23 @@ public class CandidateChooser implements AbstractCandidateChooser { for (Edge e : allEdges.get(co)) { all.add(e); } - LinkedHashMap> candidateWithParent = new LinkedHashMap<>(); - candidateWithParent.put(start, new Pair(null, 0.0)); - Pair currentMostLikelyEdge = null; + LinkedHashMap> candidateWithParent = new LinkedHashMap<>(); + candidateWithParent.put(start, new Util.Pair(null, 0.0)); + Util.Pair currentMostLikelyEdge = null; while (!candidateWithParent.containsKey(end)) { currentMostLikelyEdge = null; for (Edge e : all) { if (candidateWithParent.containsKey(e.getStart())) { Double cost = candidateWithParent.get(e.getStart()).getB() + e.getProbability(); if (currentMostLikelyEdge == null) { - currentMostLikelyEdge = new Pair(e, cost); + currentMostLikelyEdge = new Util.Pair(e, cost); } else if (cost < currentMostLikelyEdge.getB()) { - currentMostLikelyEdge = new Pair(e, cost); + currentMostLikelyEdge = new Util.Pair(e, cost); } } } if (!candidateWithParent.containsKey(currentMostLikelyEdge.getA().getEnd())) { - candidateWithParent.put(currentMostLikelyEdge.getA().getEnd(), new Pair(currentMostLikelyEdge.getA().getStart(), currentMostLikelyEdge.getB())); + candidateWithParent.put(currentMostLikelyEdge.getA().getEnd(), new Util.Pair(currentMostLikelyEdge.getA().getStart(), currentMostLikelyEdge.getB())); } all.remove(currentMostLikelyEdge.getA()); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java index e076f616758..a6a31d751f9 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java @@ -22,9 +22,9 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.MeterDistance; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sse.common.Util; /** * The standard implemantation of {@link AbstractCandidateFinder}. There are two ways {@link Candidate}s can be created. @@ -39,7 +39,7 @@ public class CandidateFinder implements AbstractCandidateFinder { private static final Logger logger = Logger.getLogger(CandidateFinder.class.getName()); - private LinkedHashMap>>> crossTrackErrors = new LinkedHashMap<>(); + private LinkedHashMap>>> crossTrackErrors = new LinkedHashMap<>(); private LinkedHashMap>> distances = new LinkedHashMap<>(); private LinkedHashMap, Candidate>>> cteCandidates = new LinkedHashMap<>(); private LinkedHashMap>> distanceCandidates = new LinkedHashMap<>(); @@ -58,7 +58,7 @@ public class CandidateFinder implements AbstractCandidateFinder { public CandidateFinder(DynamicTrackedRace race) { this.race = race; for (Competitor c : race.getRace().getCompetitors()) { - crossTrackErrors.put(c, new TreeMap>>(comp)); + crossTrackErrors.put(c, new TreeMap>>(comp)); distances.put(c, new TreeMap>(comp)); cteCandidates.put(c, new LinkedHashMap, Candidate>>()); distanceCandidates.put(c, new LinkedHashMap>()); @@ -94,7 +94,7 @@ public class CandidateFinder implements AbstractCandidateFinder { } @Override - public Pair, Iterable> getAllCandidates(Competitor c) { + public Util.Pair, Iterable> getAllCandidates(Competitor c) { Set fixes = new TreeSet(comp); try { race.getTrack(c).lockForRead(); @@ -142,14 +142,14 @@ public class CandidateFinder implements AbstractCandidateFinder { } @Override - public Pair, Iterable> getCandidateDeltas(Competitor c, Iterable fixes) { + public Util.Pair, Iterable> getCandidateDeltas(Competitor c, Iterable fixes) { if (logger.getLevel() == Level.FINEST) { logger.finest("Reevaluating MarkPasses for" + c); } Set newCans = new TreeSet<>(); Set wrongCans = new TreeSet<>(); - LinkedHashMap, List>> distanceCandidates = checkForDistanceCandidateChanges(c, fixes); - LinkedHashMap, List>> cteCandidates = checkForCTECandidatesChanges(c, fixes); + LinkedHashMap, List>> distanceCandidates = checkForDistanceCandidateChanges(c, fixes); + LinkedHashMap, List>> cteCandidates = checkForCTECandidatesChanges(c, fixes); for (Waypoint w : passingInstructions.keySet()) { newCans.addAll(cteCandidates.get(w).getA()); newCans.addAll(distanceCandidates.get(w).getA()); @@ -159,14 +159,14 @@ public class CandidateFinder implements AbstractCandidateFinder { if (newCans.size() != 0 || wrongCans.size() != 0) { logger.fine(newCans.size() + " new Candidates and " + wrongCans.size() + " removed Candidates for " + c); } - return new Pair, Iterable>(newCans, wrongCans); + return new Util.Pair, Iterable>(newCans, wrongCans); } - private LinkedHashMap, List>> checkForDistanceCandidateChanges(Competitor c, Iterable fixes) { - LinkedHashMap, List>> result = new LinkedHashMap<>(); + private LinkedHashMap, List>> checkForDistanceCandidateChanges(Competitor c, Iterable fixes) { + LinkedHashMap, List>> result = new LinkedHashMap<>(); calculatesDistances(c, fixes); for (Waypoint w : passingInstructions.keySet()) { - result.put(w, new Pair, List>(new ArrayList(), new ArrayList())); + result.put(w, new Util.Pair, List>(new ArrayList(), new ArrayList())); } Set affectedFixes = new TreeSet(comp); for (GPSFix fix : fixes) { @@ -243,11 +243,11 @@ public class CandidateFinder implements AbstractCandidateFinder { } } - private LinkedHashMap, List>> checkForCTECandidatesChanges(Competitor c, Iterable fixes) { + private LinkedHashMap, List>> checkForCTECandidatesChanges(Competitor c, Iterable fixes) { calculateCrossTrackErrors(c, fixes); - LinkedHashMap, List>> result = new LinkedHashMap<>(); + LinkedHashMap, List>> result = new LinkedHashMap<>(); for (Waypoint w : passingInstructions.keySet()) { - result.put(w, new Pair, List>(new ArrayList(), new ArrayList())); + result.put(w, new Util.Pair, List>(new ArrayList(), new ArrayList())); } for (GPSFix fix : fixes) { GPSFix fixBefore = crossTrackErrors.get(c).lowerKey(fix); @@ -307,14 +307,14 @@ public class CandidateFinder implements AbstractCandidateFinder { private void calculateCrossTrackErrors(Competitor c, Iterable fixes) { for (GPSFix fix : fixes) { - crossTrackErrors.get(c).put(fix, new LinkedHashMap>()); + crossTrackErrors.get(c).put(fix, new LinkedHashMap>()); for (Waypoint w : passingInstructions.keySet()) { Iterator it = w.getMarks().iterator(); List marks = new ArrayList(); while (it.hasNext()) { marks.add(it.next()); } - Pair ctes; + Util.Pair ctes; Position fixPos = fix.getPosition(); Position markPos = race.getOrCreateTrack(marks.get(0)).getEstimatedPosition(fix.getTimePoint(), false); Bearing crossingBearing = race.getCrossingBearing(w, fix.getTimePoint()); @@ -327,7 +327,7 @@ public class CandidateFinder implements AbstractCandidateFinder { } } - ctes = new Pair(cte1, cte2); + ctes = new Util.Pair(cte1, cte2); crossTrackErrors.get(c).get(fix).put(w, ctes); } } @@ -432,9 +432,9 @@ public class CandidateFinder implements AbstractCandidateFinder { Position p2 = null; if (instruction == PassingInstruction.Line) { - Pair pos = race.getPortAndStarboardMarks(t, w); - p1 = race.getOrCreateTrack(pos.getA()).getEstimatedPosition(t, false); - p2 = race.getOrCreateTrack(pos.getB()).getEstimatedPosition(t, false); + Util.Pair pos = race.getPortAndStarboardMarks(t, w); + p1 = pos.getA() == null ? null : race.getOrCreateTrack(pos.getA()).getEstimatedPosition(t, false); + p2 = pos.getB() == null ? null : race.getOrCreateTrack(pos.getB()).getEstimatedPosition(t, false); } else { int i = 1; for (Mark m : w.getMarks()) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java index 323f7fc5e7b..e91b39f0f37 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java @@ -20,41 +20,41 @@ import java.util.logging.Logger; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.util.impl.ThreadFactoryWithPriority; +import com.sap.sse.common.Util; /** * Calculates the {@link MarkPassing}s for a {@link DynamicTrackedRace} using a {@link AbstractCandidateFinder} and a - * {@link AbstractCandidateChooser}. The finder evaluates the fixes and finds possible MarkPassings as {@link Candidate} - * s. The chooser than finds the most likely sequence of {@link Candidate}s and creates the {@link MarkPassing}s for + * {@link AbstractCandidateChooser}. The finder evaluates the fixes and finds possible MarkPassings as {@link Candidate}s. + * The chooser then finds the most likely sequence of {@link Candidate}s and creates the {@link MarkPassing}s for * this sequence. If listen is true, a {@link MarkPassingUpdateListener} is initialized which puts new - * fixes into a queue. Additionally a new Thread is started, which takes the fixes out of the queue and sorts them so + * fixes into a queue. Additionally a new thread is started, which takes the fixes out of the queue and sorts them so * that each object (Competitor or Mark) has a list of Fixes in combinedFixes. If suspended is * false, the new Fixes are handed to the executor as FutureTasks, first to calculate the affected Fixes * and then the actual MarkPassings (See {@link CandidateFinder} and {@link CandidateChooser}). Then the listening * process begins again with the queue being emptied. This continues until the end Object is put in the - * queue by the {@link MarkPassingUpdateListener}, signalising that the race is over. + * queue by the {@link MarkPassingUpdateListener}, signaling that the race is over. * * @author Nicolas Klose * */ - public class MarkPassingCalculator { - private AbstractCandidateFinder finder; - private AbstractCandidateChooser chooser; private static final Logger logger = Logger.getLogger(MarkPassingCalculator.class.getName()); - private MarkPassingUpdateListener listener; - private final static Pair end = new Pair(null, null); - private boolean suspended = false; + private final static Util.Pair end = new Util.Pair(null, null); private final static ExecutorService executor = new ThreadPoolExecutor(/* corePoolSize */Math.max(Runtime.getRuntime() .availableProcessors() - 1, 3), /* maximumPoolSize */Math.max(Runtime.getRuntime().availableProcessors() - 1, 3), /* keepAliveTime */60, TimeUnit.SECONDS, /* workQueue */new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY - 1)); + private MarkPassingUpdateListener listener; + private AbstractCandidateFinder finder; + private AbstractCandidateChooser chooser; + private boolean suspended = false; + public MarkPassingCalculator(DynamicTrackedRace race, boolean listen) { if (listen) { listener = new MarkPassingUpdateListener(race, end); @@ -70,14 +70,13 @@ public class MarkPassingCalculator { } private class Listen implements Runnable { - @Override public void run() { logger.fine("MarkPassingCalculator is listening for new Fixes."); boolean finished = false; LinkedHashMap> combinedFixes = new LinkedHashMap<>(); while (!finished) { - List> allNewFixes = new ArrayList>(); + List> allNewFixes = new ArrayList>(); try { allNewFixes.add(listener.getQueue().take()); } catch (InterruptedException e) { @@ -85,7 +84,7 @@ public class MarkPassingCalculator { + " while waiting for new GPSFixes"); } listener.getQueue().drainTo(allNewFixes); - for (Pair fix : allNewFixes) { + for (Util.Pair fix : allNewFixes) { if (fix == end) { finished = true; continue; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java index 78b778d28b1..86d441482f8 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java @@ -5,12 +5,12 @@ import java.util.concurrent.LinkedBlockingQueue; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; +import com.sap.sse.common.Util; /** * Listens for changes that might affect the MarkPassingCalculator: new Fixes of a Competitor or a Mark and when the @@ -21,33 +21,31 @@ import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; * */ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { - private LinkedBlockingQueue> queue; - private final Pair end; - int i = 0; + private final LinkedBlockingQueue> queue; + private final Util.Pair end; - public MarkPassingUpdateListener(TrackedRace race, Pair end) { + public MarkPassingUpdateListener(TrackedRace race, Util.Pair end) { race.addListener(this); queue = new LinkedBlockingQueue<>(); this.end = end; } - public LinkedBlockingQueue> getQueue() { + public LinkedBlockingQueue> getQueue() { return queue; } @Override public void competitorPositionChanged(GPSFixMoving fix, Competitor item) { - queue.add(new Pair(item, fix)); + queue.add(new Util.Pair(item, fix)); } @Override public void markPositionChanged(GPSFix fix, Mark mark) { - queue.add(new Pair(mark, fix)); + queue.add(new Util.Pair(mark, fix)); } @Override public void statusChanged(TrackedRaceStatus newStatus) { - if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) { queue.add(end); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java index 1463c9d4ef8..61080855d20 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java @@ -7,6 +7,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.UUID; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Event; @@ -166,4 +167,26 @@ public class TopLevelMasterData implements Serializable { return eventForLeaderboardGroup; } + public Iterable getAllEvents() { + Map allEventsInMasterData = new HashMap<>(); + for (Set events : eventForLeaderboardGroup.values()) { + for (Event e : events) { + allEventsInMasterData.put(e.getId(), e); + } + } + return allEventsInMasterData.values(); + } + + public Iterable getAllRegattas() { + Set regattas = new HashSet<>(); + for (LeaderboardGroup leaderboardGroup : leaderboardGroups) { + for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { + if (leaderboard instanceof RegattaLeaderboard) { + RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; + regattas.add(regattaLeaderboard.getRegatta()); + } + } + } + return regattas; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifier.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifier.java index b20e8651d9a..8e3dbaf1469 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifier.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifier.java @@ -2,7 +2,7 @@ package com.sap.sailing.domain.racelog; import java.io.Serializable; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; public interface RaceLogIdentifier extends Serializable { @@ -10,7 +10,7 @@ public interface RaceLogIdentifier extends Serializable { String getFleetName(); - Triple getIdentifier(); + Util.Triple getIdentifier(); String getDeprecatedIdentifier(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifierTemplate.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifierTemplate.java index 35d705f030e..0cd245195ab 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifierTemplate.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifierTemplate.java @@ -3,6 +3,10 @@ package com.sap.sailing.domain.racelog; import java.io.Serializable; 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.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.Leaderboard; /** * This interfaces serves as a template for the actual RaceLogIdentifier that is compound as follows: diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifierTemplateResolver.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifierTemplateResolver.java index 2c6eb8c43ab..1424a8eb2de 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifierTemplateResolver.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogIdentifierTemplateResolver.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.racelog; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.racelog.impl.RaceLogOnLeaderboardIdentifier; import com.sap.sailing.domain.racelog.impl.RaceLogOnRegattaIdentifier; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogInformation.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogInformation.java index 97314278f40..a1445b29d9e 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogInformation.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/RaceLogInformation.java @@ -2,6 +2,8 @@ package com.sap.sailing.domain.racelog; 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.leaderboard.FlexibleLeaderboard; /** * This interface holds the information for a {@link RaceColumn} needed to have access to its {@link RaceLog}s. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/RaceLogIdentifierImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/RaceLogIdentifierImpl.java index e80b0bc4e01..59ca01b7270 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/RaceLogIdentifierImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/RaceLogIdentifierImpl.java @@ -1,8 +1,6 @@ package com.sap.sailing.domain.racelog.impl; import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.racelog.RaceLogIdentifierTemplate; @@ -21,8 +19,8 @@ public class RaceLogIdentifierImpl implements RaceLogIdentifier { } @Override - public Triple getIdentifier() { - return new Util.Triple(template.getParentObjectName(), raceColumnName, fleetName); + public com.sap.sse.common.Util.Triple getIdentifier() { + return new com.sap.sse.common.Util.Triple(template.getParentObjectName(), raceColumnName, fleetName); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java new file mode 100644 index 00000000000..17cb25c8fe2 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java @@ -0,0 +1,58 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.TimeRange; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; + +public enum EmptyGPSFixStore implements GPSFixStore { + INSTANCE; + + @Override + public void storeFix(DeviceIdentifier device, GPSFix fix) { + } + + @Override + public void loadCompetitorTrack(DynamicGPSFixTrack track, + RaceLog raceLog, Competitor competitor) { + } + + @Override + public void loadMarkTrack(DynamicGPSFixTrack track, + RaceLog raceLog, Mark mark) { + } + + @Override + public void addListener(GPSFixReceivedListener listener, DeviceIdentifier device) { + } + + @Override + public void removeListener(GPSFixReceivedListener listener) { + } + + @Override + public void loadCompetitorTrack( + DynamicGPSFixTrack track, + DeviceMapping mapping) { + + } + + @Override + public void loadMarkTrack(DynamicGPSFixTrack track, + DeviceMapping mapping) { + + } + + @Override + public TimeRange getTimeRangeCoveredByFixes(DeviceIdentifier device) { + return null; + } + + @Override + public long getNumberOfFixes(DeviceIdentifier device) { + return 0; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixReceivedListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixReceivedListener.java new file mode 100644 index 00000000000..f29eeefee6c --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixReceivedListener.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.tracking.GPSFix; + +public interface GPSFixReceivedListener { + void fixReceived(DeviceIdentifier device, GPSFix fix); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java new file mode 100644 index 00000000000..4ca34d160a6 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java @@ -0,0 +1,56 @@ +package com.sap.sailing.domain.racelog.tracking; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.TimeRange; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; + + +public interface GPSFixStore { + /** + * Load all fixes that correspond to the {@link DeviceCompetitorMappingEvent}s found in the {@code raceLog}. + */ + void loadCompetitorTrack(DynamicGPSFixTrack track, RaceLog raceLog, + Competitor competitor) throws TransformationException; + + /** + * Load all fixes that correspond to the {@link DeviceMarkMappingEvent}s found in the {@code raceLog}. + */ + void loadMarkTrack(DynamicGPSFixTrack track, RaceLog raceLog, Mark mark) throws TransformationException, + NoCorrespondingServiceRegisteredException; + + /** + * Load all fixes that correspond to the {@code mapping}. + */ + void loadCompetitorTrack(DynamicGPSFixTrack track, DeviceMapping mapping) + throws TransformationException, NoCorrespondingServiceRegisteredException; + + /** + * Load all fixes that correspond to the {@code mapping}. + */ + void loadMarkTrack(DynamicGPSFixTrack track, DeviceMapping mapping) throws TransformationException, + NoCorrespondingServiceRegisteredException; + + void storeFix(DeviceIdentifier device, GPSFix fix) throws TransformationException, NoCorrespondingServiceRegisteredException; + + /** + * Listeners are notified, whenever a {@link GPSFix} submitted by the {@code device} + * is stored through the {@link #storeFix(DeviceIdentifier, GPSFix)} method. + */ + void addListener(GPSFixReceivedListener listener, DeviceIdentifier device); + + /** + * Remove the registrations of the listener for all devices. + */ + void removeListener(GPSFixReceivedListener listener); + + TimeRange getTimeRangeCoveredByFixes(DeviceIdentifier device) throws TransformationException, + NoCorrespondingServiceRegisteredException; + + long getNumberOfFixes(DeviceIdentifier device) throws TransformationException, NoCorrespondingServiceRegisteredException; +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifier.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifier.java new file mode 100644 index 00000000000..1c55f76dca8 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifier.java @@ -0,0 +1,22 @@ +package com.sap.sailing.domain.trackfiles; + +import java.util.UUID; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; + +/** + * Identifies a device that generated fixes imported from a track file, such as a GPX file. + * In addition to containing a unique {@link UUID} for identification purposes, the identifier + * preserves the original file name, and (if present), also the name of the track for that fix. + * + * @author Fredrik Teschke + * + */ +public interface TrackFileImportDeviceIdentifier extends DeviceIdentifier { + public static final String TYPE = "FILE"; + UUID getId(); + String getFileName(); + String getTrackName(); + TimePoint getUploadedAt(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifierImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifierImpl.java new file mode 100644 index 00000000000..8073ace5fbd --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifierImpl.java @@ -0,0 +1,95 @@ +package com.sap.sailing.domain.trackfiles; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; + +public class TrackFileImportDeviceIdentifierImpl implements TrackFileImportDeviceIdentifier { + private static final long serialVersionUID = 552465264341485161L; + private final UUID id; + private final String fileName; + private final String trackName; + private final TimePoint timePoint; + + private static final Map cache = new ConcurrentHashMap<>(); + + public static TrackFileImportDeviceIdentifier getOrCreate(UUID uuid) { + if (cache.containsKey(uuid)) { + return cache.get(uuid); + } + return new TrackFileImportDeviceIdentifierImpl(uuid, null, null, null); + } + + public TrackFileImportDeviceIdentifierImpl(String fileName, String trackName) { + this(UUID.randomUUID(), fileName, trackName, MillisecondsTimePoint.now()); + } + + public TrackFileImportDeviceIdentifierImpl(UUID id, String fileName, String trackName, TimePoint timePoint) { + this.id = id; + this.fileName = fileName; + this.trackName = trackName; + this.timePoint = timePoint; + cache.put(id, this); + } + + @Override + public String getIdentifierType() { + return TYPE; + } + + @Override + public UUID getId() { + return id; + } + + @Override + public String toString() { + return id.toString(); + } + + @Override + public String getStringRepresentation() { + return String.format("%s: %s\n @%s(uploaded %s)", + id.toString(), trackName, fileName, timePoint); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TrackFileImportDeviceIdentifier) { + return id.equals(((TrackFileImportDeviceIdentifier) obj).getId()); + } + return false; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String getFileName() { + return fileName; + } + + @Override + public String getTrackName() { + return trackName; + } + + @Override + public TimePoint getUploadedAt() { + return timePoint; + } + + public static TrackFileImportDeviceIdentifier cast(DeviceIdentifier deviceIdentifier) throws TransformationException { + if (! (deviceIdentifier instanceof TrackFileImportDeviceIdentifier)) { + throw new TransformationException("Expected TrackFileImportDeviceIdentifier, but got " + deviceIdentifier.getClass()); + } + return (TrackFileImportDeviceIdentifier) deviceIdentifier; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/FormatNotSupportedException.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/FormatNotSupportedException.java new file mode 100644 index 00000000000..20816cc2b1b --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/FormatNotSupportedException.java @@ -0,0 +1,18 @@ +package com.sap.sailing.domain.trackimport; + +public class FormatNotSupportedException extends Exception { + + private static final long serialVersionUID = 9018109746837015286L; + + public FormatNotSupportedException(String msg) { + super(msg); + } + + public FormatNotSupportedException(String msg, Throwable cause) { + super(msg, cause); + } + + public FormatNotSupportedException() { + super(); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/GPSFixImporter.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/GPSFixImporter.java new file mode 100644 index 00000000000..a82a85535b2 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/GPSFixImporter.java @@ -0,0 +1,71 @@ +package com.sap.sailing.domain.trackimport; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.tracking.GPSFix; + +/** + * Importer for extracting GPS fixes from an {@link InputStream}. Importers are picked up via the OSGi service registry, + * so make sure to register them (the {@link GPSFixImporterRegistration} class can help with this). + *

+ * + * Implementers need to {@link #getType() provide a unique type name} for the importer and may + * {@link #getSupportedFileExtensions() specify the file name extensions} for the file types supported. + * The actual work is done by the implementation of {@link #importFixes(InputStream, Callback, boolean)} which + * receives the input stream that the importer shall analyze. When the start of a track is found, the importer must call the + * {@link Callback#startTrack(String, Map)} method. From that point onwards, all fixes that the importer recognizes in the + * stream and that it passes to {@link Callback#addFix(GPSFix)} are then assigned to that track which means that + * for each track an artificial {@link DeviceIdentifier} is created which can later be used to associate the track + * with a tracked object such as a competitor or a mark. Note that this means that all fixes of a single track need + * to be passed to the callback before fixes of any other track can be passed to the callback.

+ * + * If the data source to import from does not deliver course and speed over ground (COG/SOG) but only time-stamped + * latitude and longitude values, consider using BaseGPSFixImporterImpl which infers COG and SOG values + * from the positions and time stamps. + * + * @author Fredrik Teschke + * + */ +public interface GPSFixImporter { + String FILE_EXTENSION_PROPERTY = "fileExt"; + + /** + * Callback through which fixes found in the source stream are passed back. + * @author Fredrik Teschke + * + */ + interface Callback { + void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device); + } + + /** + * Retrieves the fixes from the {@code inputStream}, and calls the + * {@code callback} with every new fix. + * @param inferSpeedAndBearing Should speed and bearing be inferred by looking + * at the previous fix, if that data is not directly present within the file? + * @throws FormatNotSupportedException If the input format cannot be read. The import process + * might then decide to try attempt importing fixes using the next suitable importer. + * + * @param sourceName some name that identifies the source, e.g. the file name if a file + */ + void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, + String sourceName) + throws FormatNotSupportedException, IOException; + + /** + * Return the file extensions supported by this importer. If the importer is not intended + * for file-based input, this may return an empty iterable. Expects only the extension without + * any leading period, e.g., "gpx" or "kml". + */ + Iterable getSupportedFileExtensions(); + + /** + * Return a unique type name, that should be human readable. + * @return + */ + String getType(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java index ba318aa43e6..ac65dcfec50 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java @@ -7,7 +7,7 @@ import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public interface DynamicTrackedRace extends TrackedRace { void recordFix(Competitor competitor, GPSFixMoving fix); @@ -103,5 +103,5 @@ public interface DynamicTrackedRace extends TrackedRace { Bearing getCrossingBearing(Waypoint w, TimePoint t); - Pair getPortAndStarboardMarks(TimePoint t, Waypoint w); + Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRegatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRegatta.java index 0f7f3ba28bf..8f77a4fdeb9 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRegatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRegatta.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.tracking; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; public interface DynamicTrackedRegatta extends TrackedRegatta { @@ -17,10 +18,9 @@ public interface DynamicTrackedRegatta extends TrackedRegatta { * @param raceDefinitionSetToUpdate * may be null which means that no update will be fired to any * {@link DynamicRaceDefinitionSet}. - * @param polarDataService */ DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, - WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, + WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java index 9b0d1613652..dae2bb26eba 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java @@ -9,8 +9,8 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.confidence.Weigher; +import com.sap.sse.common.Util; /** * A track records the {@link GPSFix}es received for an object of type @@ -75,7 +75,7 @@ public interface GPSFixTrack extends Tracknull if from is before to */ - Pair getMaximumSpeedOverGround(TimePoint from, TimePoint to); + Util.Pair getMaximumSpeedOverGround(TimePoint from, TimePoint to); /** * Using an averaging / smoothening algorithm, computes the estimated speed determined @@ -118,7 +118,7 @@ public interface GPSFixTrack extends Tracknew MillisecondsTimePoint(0)). If no fix after fix is found, the second * component is the end of time (new MillisecondsTimePoint(Long.MAX_VALUE)). */ - Pair getEstimatedPositionTimePeriodAffectedBy(GPSFix fix); + Util.Pair getEstimatedPositionTimePeriodAffectedBy(GPSFix fix); /** * Same as {@link #getEstimatedPosition(TimePoint, boolean)}, but produces an iterator for all {@link Timed} objects diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java index f119d623963..dd0671903b2 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java @@ -1,14 +1,14 @@ -package com.sap.sailing.domain.tracking; - -import java.io.Serializable; - -public interface GPSTrackListener extends Serializable { - void gpsFixReceived(FixType fix, ItemType item); - - void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage); - - /** - * Listeners can use this to skip their serialization. - */ - boolean isTransient(); -} +package com.sap.sailing.domain.tracking; + +import java.io.Serializable; + +public interface GPSTrackListener extends Serializable { + void gpsFixReceived(FixType fix, ItemType item); + + void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage); + + /** + * Listeners can use this to skip their serialization. + */ + boolean isTransient(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTracker.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTracker.java index 0886240e765..da9b6df6f40 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTracker.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTracker.java @@ -5,9 +5,10 @@ import java.net.MalformedURLException; import java.util.Map; import java.util.Set; -import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; /** * Centerpiece of a tracking adapter. A tracker is responsible for receiving tracking data for one or more @@ -62,6 +63,8 @@ public interface RaceTracker { DynamicTrackedRegatta getTrackedRegatta(); WindStore getWindStore(); + + GPSFixStore getGPSFixStore(); /** * returns a unique key for this tracker which can, e.g., be used as a key in a {@link Map} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingConnectivityParameters.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingConnectivityParameters.java index db26b99d6ad..091a285fe9e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingConnectivityParameters.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingConnectivityParameters.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.tracking; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; @@ -20,7 +21,7 @@ public interface RaceTrackingConnectivityParameters { /** * Starts a {@link RaceTracker} using the connectivity parameters provided by this object. */ - RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) throws Exception; + RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, GPSFixStore gpsFixStore) throws Exception; /** * Starts a {@link RaceTracker}, associating the resulting races with the {@link Regatta} passed as argument @@ -28,7 +29,7 @@ public interface RaceTrackingConnectivityParameters { * parameters. This is particularly useful if a predefined regatta with {@link Series} and {@link Fleet}s * is to be used. */ - RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) throws Exception; + RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, GPSFixStore gpsFixStore) throws Exception; /** * Deliver an ID object equal to that of the {@link RaceTracker#getID()} delivered by the {@link RaceTracker} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RacesHandle.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RacesHandle.java index 50ff432189a..78da6d0bc50 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RacesHandle.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RacesHandle.java @@ -2,8 +2,9 @@ package com.sap.sailing.domain.tracking; import java.util.Set; -import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; /** * Gives clients a way to refer to a specific set of races even though the {@link RaceDefinition} objects may not have @@ -27,7 +28,7 @@ public interface RacesHandle { * Fetch the race definition. If the race definition represented by this handle hasn't been created yet, the call * blocks until such a definition is provided by another call, usually by the {@link RaceCourseReceiver}. If * timeoutInMilliseconds milliseconds have passed and the race definition is found not to have - * shown up until then, null is returned. The unblocking may be deferred even beyond + * shown up until then, a valid but empty set is returned. The unblocking may be deferred even beyond * timeoutInMilliseconds in case no modifications happen on the {@link DomainFactory}'s * set of races during that time. */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java index 2778b93b749..438599a9814 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java @@ -45,7 +45,18 @@ public interface TrackedLeg extends Serializable { Bearing getLegBearing(TimePoint at); - Distance getCrossTrackError(Position p, TimePoint timePoint); + /** + * Returns the positive (absolute) distance of p to this leg's course middle line at timePoint, + * based on the position of the waypoints delimiting this leg at that time. + */ + Distance getAbsoluteCrossTrackError(Position p, TimePoint timePoint); + + /** + * Returns the (signed) distance of p to this leg's course middle line at timePoint, + * based on the position of the waypoints delimiting this leg at that time. Negative distances mean "left" of the + * course middle line looking in the direction of the leg, positive distances mean "right." + */ + Distance getSignedCrossTrackError(Position p, TimePoint timePoint); /** * Must be called when the start and finish waypoint of this leg may have changed. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java index 7976545e2e2..e6ba98ebee5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java @@ -7,12 +7,13 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Duration; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public interface TrackedLegOfCompetitor extends Serializable { Leg getLeg(); @@ -26,7 +27,7 @@ public interface TrackedLegOfCompetitor extends Serializable { * competitor to complete the leg is returned. If the competitor didn't finish the leg before the end of tracking, * null is returned because this indicates that the tracker stopped sending valid data. */ - Long getTimeInMilliSeconds(TimePoint timePoint); + Duration getTime(TimePoint timePoint); /** * The distance over ground traveled by the competitor in this leg up to timePoint. If @@ -74,7 +75,7 @@ public interface TrackedLegOfCompetitor extends Serializable { * achieved and the speed value. In case you provide timepoint that is greater than the time point of the * end of this leg the provided value will be ignored and replaced by the timepoint of the end of the leg. */ - Pair getMaximumSpeedOverGround(TimePoint timePoint); + Util.Pair getMaximumSpeedOverGround(TimePoint timePoint); /** * Infers the maneuvers of the competitor up to timePoint on this leg. If the competitor hasn't started @@ -176,11 +177,21 @@ public interface TrackedLegOfCompetitor extends Serializable { Distance getWindwardDistanceToOverallLeader(TimePoint timePoint) throws NoWindException; /** - * Computes the average cross track error for this leg. If you provide this method with a {@link TimePoint} greater - * than the time the mark passing of the leg end mark has occurred then the time point of the mark passing - * of the leg end mark will be taken into account. + * Computes the average absolute cross track error for this leg. The cross track error for each fix is taken to be a + * positive number, thereby ignoring whether the competitor was left or right of the course middle line. If you + * provide this method with a {@link TimePoint} greater than the time the mark passing of the leg end mark has + * occurred then the time point of the mark passing of the leg end mark will be taken into account. */ - Distance getAverageCrossTrackError(TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException; + Distance getAverageAbsoluteCrossTrackError(TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException; + + /** + * Computes the average signed cross track error for this leg. The cross track error for each fix is taken to be a + * positive number in case the competitor was on the right side of the course middle line (looking in the direction + * of this leg), and a negative number in case the competitor was on the left side of the course middle line. If you + * provide this method with a {@link TimePoint} greater than the time the mark passing of the leg end mark has + * occurred then the time point of the mark passing of the leg end mark will be taken into account. + */ + Distance getAverageSignedCrossTrackError(TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException; /** * Computes the maneuver loss as the distance projected onto the average course between entering and exiting the diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index 7e46831eefd..e5e02abe005 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -27,8 +27,9 @@ import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.dto.TrackedRaceDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sse.common.Util; /** * Live tracking data of a single race. The race follows a defined {@link Course} with a sequence of {@link Leg}s. The @@ -86,7 +87,7 @@ public interface TrackedRace extends Serializable { * Returns a list of the first and last mark passing times of all course waypoints. Callers wanting to iterate over * the result must synchronize on the result. */ - Iterable>> getMarkPassingsTimes(); + Iterable>> getMarkPassingsTimes(); /** * Shorthand for {@link #getStart()}.{@link TimePoint#compareTo(TimePoint) compareTo(at)} <= 0 @@ -397,7 +398,8 @@ public interface TrackedRace extends Serializable { /** * Uses a {@link DouglasPeucker Douglas-Peucker} algorithm to approximate this track's fixes starting at time * from until time point to such that the maximum distance between the track's fixes and - * the approximation is at most maxDistance. + * the approximation is at most maxDistance. The approximation's fixes are original fixes from + * the competitor's {@link GPSFixTrack track}. */ List approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to); @@ -442,9 +444,12 @@ public interface TrackedRace extends Serializable { * tracked race. This runs synchronized with the otherwise asynchronous loading of wind tracks, triggered by the * constructor of the {@link TrackedRace} implementation classes. This procedure guarantees that eventually the * listener will have received a notification for all wind fixes, regardless of whether they were already loaded at - * the time the listener is registered or they are loaded after the registration has completed. + * the time the listener is registered or they are loaded after the registration has completed.

+ * + * The same is true for the GPS fixes for marks and competitors. */ - void addListener(RaceChangeListener listener, boolean notifyAboutWindFixesAlreadyLoaded); + void addListener(RaceChangeListener listener, boolean notifyAboutWindFixesAlreadyLoaded, + boolean notifyAboutGPSFixesAlreadyLoaded); void removeListener(RaceChangeListener listener); @@ -460,7 +465,7 @@ public interface TrackedRace extends Serializable { * Calls {@link #getWindWithConfidence(Position, TimePoint, Iterable)} and excludes those wind sources listed in * {@link #getWindSourcesToExclude}. */ - WindWithConfidence> getWindWithConfidence(Position p, TimePoint at); + WindWithConfidence> getWindWithConfidence(Position p, TimePoint at); /** * Lists those wind sources which by default are not considered in {@link #getWind(Position, TimePoint)} and @@ -474,7 +479,7 @@ public interface TrackedRace extends Serializable { * by each wind source are used during computing the averaged result across the wind sources. The result has the * averaged confidence attached. */ - WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, + WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, Iterable windSourcesToExclude); /** @@ -496,9 +501,18 @@ public interface TrackedRace extends Serializable { * if true and any cache update is currently going on, wait for the update to complete and * then fetch the updated value; otherwise, serve this requests from whatever is currently in the cache */ - Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) + Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException; + Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, + boolean waitForLatestAnalyses) throws NoWindException; + + Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) + throws NoWindException; + + Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, + boolean waitForLatestAnalysis) throws NoWindException; + WindStore getWindStore(); Competitor getOverallLeader(TimePoint timePoint) throws NoWindException; @@ -509,16 +523,19 @@ public interface TrackedRace extends Serializable { */ List getCompetitorsFromBestToWorst(TimePoint timePoint) throws NoWindException; - Distance getAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, - boolean waitForLatestAnalyses) throws NoWindException; + /** + * When provided with a {@link WindStore} during construction, the tracked race will + * asynchronously load the wind data for this tracked race from the wind store and the GPS store in a + * background thread and update this tracked race with the results. Clients that want to wait for the wind + * loading process to complete can do so by calling this method which will block until the wind loading has + * completed. + */ + void waitUntilLoadingFromWindStoreComplete() throws InterruptedException; /** - * When provided with a {@link WindStore} during construction, the tracked race will asynchronously load the wind - * data for this tracked race from the wind store in a background thread and update this tracked race with the - * results. Clients that want to wait for the wind loading process to complete can do so by calling this method - * which will block until the wind loading has completed. + * @see #waitUntilLoadingFromWindStoreComplete(), but for fixes from a {@link GPSFixStore} */ - void waitUntilWindLoadingComplete() throws InterruptedException; + void waitUntilLoadingFromGPSFixStoreComplete() throws InterruptedException; TrackedRaceStatus getStatus(); @@ -600,7 +617,7 @@ public interface TrackedRace extends Serializable { * or---if the start waypoint was a single mark---the distance to the single start mark at the timepoint. * If the competitor hasn't started yet, null is returned. * - */ + */ Distance getDistanceFromStarboardSideOfStartLine(Competitor competitor, TimePoint timePoint); /** @@ -612,7 +629,7 @@ public interface TrackedRace extends Serializable { * The speed of the competitor when crossing the start line. It will return null if there are no recorded * mark passings for this competitor (competitor did not yet or never pass the start line). */ - Speed getSpeedWhenCrossingStartLine(Competitor competitor); + Speed getSpeedWhenCrossingStartLine(Competitor competitor); /** * Start time received by the tracking infrastructure. To determine real start time use {@link #getStartOfRace()}. @@ -641,4 +658,6 @@ public interface TrackedRace extends Serializable { * a reference point. */ SpeedWithConfidence getAverageWindSpeedWithConfidence(long resolutionInMillis); + + GPSFixStore getGPSFixStore(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java index 078886cfd50..12bad62fa9f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java @@ -7,8 +7,10 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.impl.TrackedRaces; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; /** * Manages a set of {@link TrackedRace} objects that belong to the same {@link Regatta} (regatta, sailing regatta for a @@ -41,7 +43,7 @@ public interface TrackedRegatta extends Serializable { * {@link DynamicRaceDefinitionSet#addRaceDefinition(RaceDefinition, DynamicTrackedRace) added} to that object. */ DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, - long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, + GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate); /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java index 66ea8c44b34..b9f0bd9553a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java @@ -44,7 +44,7 @@ public interface TrackedRegattaRegistry { */ Regatta getRememberedRegattaForRace(Serializable raceID); - boolean isRaceBeingTracked(RaceDefinition r); + boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r); /** * Stops all {@link RaceTracker}s currently tracking race. Note that if the same tracker also may have diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java index 4a5c0fb1550..09fd746d0a1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java @@ -2,7 +2,7 @@ package com.sap.sailing.domain.tracking; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public interface WindTrack extends DynamicTrack { static final long DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND = 30000; @@ -22,7 +22,7 @@ public interface WindTrack extends DynamicTrack { */ Wind getAveragedWind(Position p, TimePoint at); - WindWithConfidence> getAveragedWindWithConfidence(Position p, TimePoint at); + WindWithConfidence> getAveragedWindWithConfidence(Position p, TimePoint at); /** * A listener is notified whenever a new fix is added to this track diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTracker.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTracker.java index 34d99e00df8..9ec4757621a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTracker.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTracker.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.tracking; +import com.sap.sailing.domain.common.WindSource; + /** * Receives wind information and forwards it to a {@link TrackedRace}'s {@link TrackedRace#getOrCreateWindTrack(WindSource, long) wind * track}. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingWithConfidenceCluster.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingWithConfidenceCluster.java index 34086e0fc0f..5b0c6efb255 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingWithConfidenceCluster.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BearingWithConfidenceCluster.java @@ -5,14 +5,14 @@ import java.util.Collections; import java.util.List; import com.sap.sailing.domain.base.BearingWithConfidence; -import com.sap.sailing.domain.base.BearingWithConfidence.DoublePair; import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.domain.common.DoublePair; import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; import com.sap.sailing.domain.confidence.ConfidenceFactory; import com.sap.sailing.domain.confidence.HasConfidence; import com.sap.sailing.domain.confidence.Weigher; +import com.sap.sse.common.Util; /** * Contains a number of {@link Bearing} objects and maintains the average bearing. For a given {@link Bearing} it @@ -65,7 +65,7 @@ public class BearingWithConfidenceCluster { result[0] = createEmptyCluster(); result[1] = createEmptyCluster(); if (bearings.size() >= 2) { - Pair, BearingWithConfidence> extremeBearings = getExtremeBearings(minimumDegreeDifferenceBetweenTacks); + Util.Pair, BearingWithConfidence> extremeBearings = getExtremeBearings(minimumDegreeDifferenceBetweenTacks); if (extremeBearings != null) { result[0].add(extremeBearings.getA()); result[1].add(extremeBearings.getB()); @@ -98,16 +98,16 @@ public class BearingWithConfidenceCluster { * is scaled by their confidences. This scaled distance is then maximized for those bearings at least * minimumDegreeDifferenceBetweenTacks degrees apart. */ - private Pair, BearingWithConfidence> getExtremeBearings(double minimumDegreeDifferenceBetweenTacks) { + private Util.Pair, BearingWithConfidence> getExtremeBearings(double minimumDegreeDifferenceBetweenTacks) { assert bearings.size() >= 2; double maxAbsDegDiff = 0; - Pair, BearingWithConfidence> result = null; + Util.Pair, BearingWithConfidence> result = null; for (int i=0; i= minimumDegreeDifferenceBetweenTacks && confidenceScaledDifference > maxAbsDegDiff) { - result = new Pair, BearingWithConfidence>(bearings.get(i), bearings.get(j)); + result = new Util.Pair, BearingWithConfidence>(bearings.get(i), bearings.get(j)); maxAbsDegDiff = confidenceScaledDifference; assert Math.abs(bearings.get(i).getObject().getDifferenceTo(bearings.get(j).getObject()).getDegrees()) <= 180.; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CombinedWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CombinedWindTrackImpl.java index 966d3930cb0..3abf99cc582 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CombinedWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CombinedWindTrackImpl.java @@ -1,13 +1,22 @@ package com.sap.sailing.domain.tracking.impl; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.domain.tracking.impl.TrackBasedEstimationWindTrackImpl.EstimatedWindFixesAsNavigableSet; +import com.sap.sse.common.Util; /** * A wind track that delivers the result of * {@link TrackedRace#getWind(com.sap.sailing.domain.common.Position, com.sap.sailing.domain.common.TimePoint)}, * using a {@link CombinedWindAsNavigableSet} as its internal raw fixes collection. Resolution of raw fixes is set to 10s. + * As the result of {@link TrackedRace#getWind(com.sap.sailing.domain.common.Position, com.sap.sailing.domain.common.TimePoint)} + * is already a smoothened, outlier-removed and averaged value, the averaging methods {@link #getAveragedWind(Position, TimePoint)} + * and {@link #getAveragedWindWithConfidence(Position, TimePoint)} are redefined such that they immediately delegate + * to {@link TrackedRace#getWindWithConfidence(Position, TimePoint)}. * * @author Axel Uhl (d043530) * @@ -37,5 +46,14 @@ public class CombinedWindTrackImpl extends VirtualWindTrackImpl { public long getMillisecondsOverWhichToAverageWind() { return 2*getInternalRawFixes().getResolutionInMilliseconds()+2; } - + + @Override + public Wind getAveragedWind(Position p, TimePoint at) { + return getTrackedRace().getWind(p, at); + } + + @Override + public WindWithConfidence> getAveragedWindWithConfidence(Position p, TimePoint at) { + return getTrackedRace().getWindWithConfidence(p, at); + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java index 1986dd1d884..c39dcb8f942 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java @@ -38,7 +38,7 @@ import com.sap.sailing.util.SmartFutureCache.UpdateInterval; * The results can be cached. Cache invalidation becomes necessary as mark passings, mark positions and boat positions * change. For this purpose, this cache subscribes itself as a listener to the {@link TrackedRace} to which it belongs * and manages cache invalidations autonomously. - * + * * @author Axel Uhl (D043530) * */ @@ -48,13 +48,15 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { private static class CrossTrackErrorSumAndNumberOfFixes implements Timed { private static final long serialVersionUID = -278130726836884454L; private final TimePoint timePoint; - private final double distanceInMetersSumFromStart; + private final double absoluteDistanceInMetersSumFromStart; + private final double signedDistanceInMetersSumFromStart; private final int fixCountFromStart; - public CrossTrackErrorSumAndNumberOfFixes(TimePoint timePoint, double distanceInMetersSumFromStart, int fixCountFromStart) { + public CrossTrackErrorSumAndNumberOfFixes(TimePoint timePoint, double absoluteDistanceInMetersSumFromStart, double signedDistanceInMetersSumFromStart, int fixCountFromStart) { super(); this.timePoint = timePoint; - this.distanceInMetersSumFromStart = distanceInMetersSumFromStart; + this.absoluteDistanceInMetersSumFromStart = absoluteDistanceInMetersSumFromStart; + this.signedDistanceInMetersSumFromStart = signedDistanceInMetersSumFromStart; this.fixCountFromStart = fixCountFromStart; } @@ -63,8 +65,12 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { return timePoint; } - public double getDistanceInMetersSumFromStart() { - return distanceInMetersSumFromStart; + public double getAbsoluteDistanceInMetersSumFromStart() { + return absoluteDistanceInMetersSumFromStart; + } + + public double getSignedDistanceInMetersSumFromStart() { + return signedDistanceInMetersSumFromStart; } public int getFixCountFromStart() { @@ -115,7 +121,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { public FromTimePointToEndUpdateInterval join(FromTimePointToEndUpdateInterval otherUpdateInterval) { return new FromTimePointToEndUpdateInterval(getFrom()==null? otherUpdateInterval.getFrom():otherUpdateInterval.getFrom()==null? - getFrom():(getFrom().compareTo(otherUpdateInterval.getFrom())<0)? + getFrom():getFrom().before(otherUpdateInterval.getFrom())? getFrom():otherUpdateInterval.getFrom()); } @@ -185,6 +191,10 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { owner.addListener(this); } + private interface CrossTrackErrorMeterRetriever { + double getDistanceInMetersSumFromStart(CrossTrackErrorSumAndNumberOfFixes cacheEntry); + } + /** * Answers the query from the cache contents. * @@ -194,8 +204,43 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { * whether to wait for any currently ongoing cache update calculation; if false, the current * cache entry will be used */ - public Distance getAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatest) throws NoWindException { + CrossTrackErrorMeterRetriever absoluteMetersRetriever = new CrossTrackErrorMeterRetriever() { + @Override + public double getDistanceInMetersSumFromStart(CrossTrackErrorSumAndNumberOfFixes cacheEntry) { + return cacheEntry.getAbsoluteDistanceInMetersSumFromStart(); + } + }; + return getAbsoluteOrSignedAverageCrossTrackError(competitor, from, to, upwindOnly, waitForLatest, absoluteMetersRetriever); + } + + /** + * Answers the query from the cache contents. + * + * @param upwindOnly + * if true, only fixes in upwind legs are considered during aggregation + * @param waitForLatest + * whether to wait for any currently ongoing cache update calculation; if false, the current + * cache entry will be used + */ + public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, + boolean waitForLatest) throws NoWindException { + CrossTrackErrorMeterRetriever signedMetersRetriever = new CrossTrackErrorMeterRetriever() { + @Override + public double getDistanceInMetersSumFromStart(CrossTrackErrorSumAndNumberOfFixes cacheEntry) { + return cacheEntry.getSignedDistanceInMetersSumFromStart(); + } + }; + return getAbsoluteOrSignedAverageCrossTrackError(competitor, from, to, upwindOnly, waitForLatest, signedMetersRetriever); + } + + /** + * @param absoluteMetersRetriever determines whether the absolute or signed cross track error will be aggregated + */ + private Distance getAbsoluteOrSignedAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, + boolean upwindOnly, boolean waitForLatest, CrossTrackErrorMeterRetriever absoluteMetersRetriever) + throws NoWindException { Track cacheForCompetitor = cachePerCompetitor.get(competitor, waitForLatest); double distanceInMeters = 0; int count = 0; @@ -231,11 +276,11 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { } if (startAggregate == null) { startAggregate = new CrossTrackErrorSumAndNumberOfFixes(/* time point */ null, - /* distanceInMetersSumFromStart */ 0, /* fixCountFromStart */ 0); + /* absoluteDistanceInMetersSumFromStart */ 0, /* signedDistanceInMetersSumFromStart */ 0, /* fixCountFromStart */ 0); } CrossTrackErrorSumAndNumberOfFixes endAggregate = cacheForCompetitor.getLastFixAtOrBefore(end); if (endAggregate != null) { - distanceInMeters += endAggregate.getDistanceInMetersSumFromStart() - startAggregate.getDistanceInMetersSumFromStart(); + distanceInMeters += absoluteMetersRetriever.getDistanceInMetersSumFromStart(endAggregate) - absoluteMetersRetriever.getDistanceInMetersSumFromStart(startAggregate); count += endAggregate.getFixCountFromStart() - startAggregate.getFixCountFromStart(); startAggregate = endAggregate; } @@ -261,13 +306,16 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { final CrossTrackErrorSumAndNumberOfFixesTrack competitorCacheEntry = cachePerCompetitor.get(competitor, /* waitForLatest */ false); final CrossTrackErrorSumAndNumberOfFixes lastCacheEntryBeforeFrom; lastCacheEntryBeforeFrom = competitorCacheEntry == null ? null : competitorCacheEntry.getLastFixBefore(from); - double distanceInMeters; + double absoluteDistanceInMeters; + double signedDistanceInMeters; int count; if (lastCacheEntryBeforeFrom != null) { - distanceInMeters = lastCacheEntryBeforeFrom.getDistanceInMetersSumFromStart(); + absoluteDistanceInMeters = lastCacheEntryBeforeFrom.getAbsoluteDistanceInMetersSumFromStart(); + signedDistanceInMeters = lastCacheEntryBeforeFrom.getSignedDistanceInMetersSumFromStart(); count = lastCacheEntryBeforeFrom.getFixCountFromStart(); } else { - distanceInMeters = 0; + absoluteDistanceInMeters = 0; + signedDistanceInMeters = 0; count = 0; } GPSFixTrack track = owner.getTrack(competitor); @@ -298,12 +346,13 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { if (currentLeg != null && markPassingAtLegStart != null && fix.getTimePoint().compareTo(markPassingAtLegStart.getTimePoint()) >= 0) { TrackedLeg trackedLeg = owner.getTrackedLeg(currentLeg); - Distance xte = owner.getTrackedLeg(trackedLeg.getLeg()).getCrossTrackError(fix.getPosition(), - fix.getTimePoint()); + Distance xte = owner.getTrackedLeg(trackedLeg.getLeg()).getSignedCrossTrackError(fix.getPosition(), fix.getTimePoint()); if (xte != null) { - distanceInMeters += xte.getMeters(); + signedDistanceInMeters += xte.getMeters(); + absoluteDistanceInMeters += Math.abs(xte.getMeters()); count++; - CrossTrackErrorSumAndNumberOfFixes newCacheEntry = new CrossTrackErrorSumAndNumberOfFixes(fix.getTimePoint(), distanceInMeters, count); + CrossTrackErrorSumAndNumberOfFixes newCacheEntry = new CrossTrackErrorSumAndNumberOfFixes(fix.getTimePoint(), absoluteDistanceInMeters, + signedDistanceInMeters, count); result.add(newCacheEntry); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java index 58c05ef75ce..33742217036 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java @@ -6,11 +6,11 @@ import java.util.NavigableSet; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.util.impl.ArrayListNavigableSet; import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sse.common.Util; /** * Computing {@link #getDistanceTraveled(TimePoint, TimePoint)} is more expensive the longer the track is and the @@ -68,20 +68,20 @@ import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; * @author Axel Uhl (D043530) */ public class DistanceCache { - private final NavigableSet>>> distanceCache; + private final NavigableSet>>> distanceCache; private final NamedReentrantReadWriteLock lock; - private static final Comparator> timePointInPairComparator = new Comparator>() { + private static final Comparator> timePointInPairComparator = new Comparator>() { @Override - public int compare(Pair o1, Pair o2) { + public int compare(Util.Pair o1, Util.Pair o2) { return o1.getA().compareTo(o2.getA()); } }; public DistanceCache(String nameForLockLogging) { lock = new NamedReentrantReadWriteLock("lock for DistanceCache for "+nameForLockLogging, /* fair */ true); - this.distanceCache = new ArrayListNavigableSet>>>(timePointInPairComparator); + this.distanceCache = new ArrayListNavigableSet>>>(timePointInPairComparator); } /** @@ -90,15 +90,15 @@ public class DistanceCache { * the to value of the entry. If there is no entry that is at or after from, * null is returned. */ - public Pair> getEarliestFromAndDistanceAtOrAfterFrom(TimePoint from, TimePoint to) { + public Util.Pair> getEarliestFromAndDistanceAtOrAfterFrom(TimePoint from, TimePoint to) { LockUtil.lockForRead(lock); try { - Pair> result = null; - Pair>> entryForTo = distanceCache.floor(createDummy(to)); + Util.Pair> result = null; + Util.Pair>> entryForTo = distanceCache.floor(createDummy(to)); if (entryForTo != null) { - final Pair fromCeiling = entryForTo.getB().ceiling(new Pair(from, null)); + final Util.Pair fromCeiling = entryForTo.getB().ceiling(new Util.Pair(from, null)); if (fromCeiling != null) { - result = new Pair>(entryForTo.getA(), fromCeiling); + result = new Util.Pair>(entryForTo.getA(), fromCeiling); } } return result; @@ -113,10 +113,10 @@ public class DistanceCache { public void invalidateAllAtOrLaterThan(TimePoint timePoint) { LockUtil.lockForWrite(lock); try { - Pair>> dummy = createDummy(timePoint); - NavigableSet>>> toRemove = distanceCache.tailSet(dummy, /* inclusive */ true); - for (Iterator>>> i=toRemove.iterator(); i.hasNext(); ) { - Pair>> entryToRemove = i.next(); + Util.Pair>> dummy = createDummy(timePoint); + NavigableSet>>> toRemove = distanceCache.tailSet(dummy, /* inclusive */ true); + for (Iterator>>> i=toRemove.iterator(); i.hasNext(); ) { + Util.Pair>> entryToRemove = i.next(); assert entryToRemove.getA().compareTo(timePoint) >= 0; i.remove(); } @@ -125,10 +125,10 @@ public class DistanceCache { } } - private NavigableSet> getEntryForTo(TimePoint to) { - NavigableSet> result = null; - Pair>> dummyForTo = createDummy(to); - Pair>> entryForTo = distanceCache.floor(dummyForTo); + private NavigableSet> getEntryForTo(TimePoint to) { + NavigableSet> result = null; + Util.Pair>> dummyForTo = createDummy(to); + Util.Pair>> entryForTo = distanceCache.floor(dummyForTo); if (entryForTo != null && entryForTo.getA().equals(to)) { result = entryForTo.getB(); } @@ -138,20 +138,20 @@ public class DistanceCache { public void cache(TimePoint from, TimePoint to, Distance distance) { LockUtil.lockForWrite(lock); try { - NavigableSet> entryForTo = getEntryForTo(to); + NavigableSet> entryForTo = getEntryForTo(to); if (entryForTo == null) { - entryForTo = new ArrayListNavigableSet>(timePointInPairComparator); - Pair>> pairForTo = new Pair>>( + entryForTo = new ArrayListNavigableSet>(timePointInPairComparator); + Util.Pair>> pairForTo = new Util.Pair>>( to, entryForTo); distanceCache.add(pairForTo); } - entryForTo.add(new Pair(from, distance)); + entryForTo.add(new Util.Pair(from, distance)); } finally { LockUtil.unlockAfterWrite(lock); } } - private Pair>> createDummy(TimePoint to) { - return new Pair>>(to, null); + private Util.Pair>> createDummy(TimePoint to) { + return new Util.Pair>>(to, null); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java index 6a54a72d5ba..95e1b281fc7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.tracking.impl; import com.sap.sailing.domain.base.Boat; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CompetitorChangeListener; import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory; import com.sap.sailing.domain.base.SharedDomainFactory; import com.sap.sailing.domain.base.Team; @@ -63,6 +64,14 @@ public class DummyMarkPassingWithTimePointOnly implements MarkPassing { public Color getColor() { return null; } + + @Override + public void addCompetitorChangeListener(CompetitorChangeListener listener) { + } + + @Override + public void removeCompetitorChangeListener(CompetitorChangeListener listener) { + } }; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java index 822e04f9f75..b2f54320796 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java @@ -63,8 +63,8 @@ public class DynamicGPSFixMovingTrackImpl extends GPSFixTrackImpl 0) && (maxSpeedForSmoothing == null || last.getSpeed().compareTo(maxSpeedForSmoothing) <= 0)) { SpeedWithConfidenceImpl speedWithConfidence = new SpeedWithConfidenceImpl( last.getSpeed(), /* original confidence */0.9, last.getTimePoint()); @@ -78,8 +78,9 @@ public class DynamicGPSFixMovingTrackImpl extends GPSFixTrackImpl 0) + && (maxSpeedForSmoothing == null || next.getSpeed().compareTo(maxSpeedForSmoothing) <= 0)) { SpeedWithConfidenceImpl computedSpeedWithConfidence = new SpeedWithConfidenceImpl( next.getSpeed(), /* original confidence */0.9, next.getTimePoint()); speeds.add(computedSpeedWithConfidence); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java index 91592e62e1d..47d90adebc6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java @@ -17,5 +17,4 @@ public class DynamicTrackImpl extends TrackImpl public boolean add(FixType fix) { return super.add(fix); } - } 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 311476a3b32..bd9d548b26a 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 @@ -1,861 +1,896 @@ -package com.sap.sailing.domain.tracking.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NavigableSet; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.base.Leg; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Sideline; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.TimingConstants; -import com.sap.sailing.domain.common.WindSource; -import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.WindSourceImpl; -import com.sap.sailing.domain.racelog.RaceLog; -import com.sap.sailing.domain.tracking.CourseDesignChangedListener; -import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; -import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.GPSTrackListener; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.RaceChangeListener; -import com.sap.sailing.domain.tracking.StartTimeChangedListener; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedRaceStatus; -import com.sap.sailing.domain.tracking.TrackedRegatta; -import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.domain.tracking.WindStore; -import com.sap.sailing.domain.tracking.WindTrack; -import com.sap.sailing.util.impl.LockUtil; -import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; - -public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements -DynamicTrackedRace, GPSTrackListener { - private static final long serialVersionUID = 1092726918239676958L; - - private static final Logger logger = Logger.getLogger(DynamicTrackedRaceImpl.class.getName()); - - private transient Set listeners; - - private boolean raceIsKnownToStartUpwind; - - private boolean delayToLiveInMillisFixed; - - private transient DynamicTrackedRaceLogListener logListener; - - private transient Set courseDesignChangedListeners; - private transient Set startTimeChangedListeners; - - public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, - WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, - long delayForCacheInvalidationOfWindEstimation) { - super(trackedRegatta, race, sidelines, windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, - delayForCacheInvalidationOfWindEstimation); - this.logListener = new DynamicTrackedRaceLogListener(this); - this.courseDesignChangedListeners = new HashSet(); - this.startTimeChangedListeners = new HashSet(); - this.raceIsKnownToStartUpwind = race.getBoatClass().typicallyStartsUpwind(); - if (!raceIsKnownToStartUpwind) { - Set windSourcesToExclude = new HashSet(); - for (WindSource windSourceToExclude : getWindSourcesToExclude()) { - windSourcesToExclude.add(windSourceToExclude); - } - windSourcesToExclude.add(new WindSourceImpl(WindSourceType.COURSE_BASED)); - setWindSourcesToExclude(windSourcesToExclude); - } - for (Competitor competitor : getRace().getCompetitors()) { - DynamicGPSFixTrack track = getTrack(competitor); - track.addListener(this); - } - // default wind tracks are observed because they are created by the superclass constructor using - // createWindTrack which adds this object as a listener - } - - /** - * After de-serialization sets a valid {@link #listeners} collection which is transient and therefore - * hasn't been serialized. - */ - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - listeners = new HashSet(); - logListener = new DynamicTrackedRaceLogListener(this); - courseDesignChangedListeners = new HashSet(); - startTimeChangedListeners = new HashSet(); - } - - /** - * {@link #raceIsKnownToStartUpwind} (see also {@link #raceIsKnownToStartUpwind()}) is initialized based on the race's - * {@link RaceDefinition#getBoatClass()} boat class's {@link BoatClass#typicallyStartsUpwind()} result. It can be changed - * using {@link #setRaceIsKnownToStartUpwind(boolean)}. Uses millisecondsOverWhichToAverageWind/2 for the - * delayForCacheInvalidationOfWindEstimation argument of the constructor.

- * - * Loading wind tracks from the windStore happens asynchronously which means that when the constructor returns, - * the caller cannot assume that all wind tracks have yet been loaded completely. The caller may call {@link #waitUntilWindLoadingComplete()} - * to wait until all persistent wind sources have been successfully and completely loaded. - */ - public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, - WindStore windStore, long delayToLiveInMillis, - long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed) { - this(trackedRegatta, race, sidelines, windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, - millisecondsOverWhichToAverageWind/2); - } - - @Override - public void recordFix(Competitor competitor, GPSFixMoving fix) { - DynamicGPSFixTrack track = getTrack(competitor); - if (track != null) { - track.addGPSFix(fix); // the track notifies this tracked race which in turn notifies its listeners - } - } - - @Override - public void setStatus(TrackedRaceStatus newStatus) { - super.setStatus(newStatus); - notifyListeners(newStatus); - } - - @Override - public void recordFix(Mark mark, GPSFix fix) { - getOrCreateTrack(mark).addGPSFix(fix); - } - - @Override - public void setMillisecondsOverWhichToAverageSpeed(long millisecondsOverWhichToAverageSpeed) { - this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed; - for (Competitor competitor : getRace().getCompetitors()) { - getTrack(competitor).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageSpeed); - } - for (Waypoint waypoint : getRace().getCourse().getWaypoints()) { - for (Mark mark : waypoint.getMarks()) { - getOrCreateTrack(mark).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageSpeed); - } - } - updated(/* time point */null); - triggerManeuverCacheRecalculationForAllCompetitors(); - } - - @Override - public void setMillisecondsOverWhichToAverageWind(long millisecondsOverWhichToAverageWind) { - long oldMillisecondsOverWhichToAverageWind = this.millisecondsOverWhichToAverageWind; - this.millisecondsOverWhichToAverageWind = millisecondsOverWhichToAverageWind; - for (WindSource windSource : getWindSources()) { - getOrCreateWindTrack(windSource).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageWind); - } - updated(/* time point */null); - triggerManeuverCacheRecalculationForAllCompetitors(); - notifyListenersWindAveragingChanged(oldMillisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageWind); - } - - - @Override - public void setAndFixDelayToLiveInMillis(long delayToLiveInMillis) { - super.setDelayToLiveInMillis(delayToLiveInMillis); - delayToLiveInMillisFixed = true; - } - - @Override - public void setDelayToLiveInMillis(long delayToLiveInMillis) { - if (!delayToLiveInMillisFixed && getDelayToLiveInMillis() != delayToLiveInMillis) { - super.setDelayToLiveInMillis(delayToLiveInMillis); - notifyListenersDelayToLiveChanged(delayToLiveInMillis); - } - } - - @Override - public DynamicGPSFixTrack getTrack(Competitor competitor) { - return (DynamicGPSFixTrack) super.getTrack(competitor); - } - - @Override - public DynamicGPSFixTrack getOrCreateTrack(Mark mark) { - return (DynamicGPSFixTrack) super.getOrCreateTrack(mark); - } - - /** - * 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) 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. - */ - @Override - protected DynamicGPSFixTrackImpl createMarkTrack(Mark mark) { - DynamicGPSFixTrackImpl result = super.createMarkTrack(mark); - result.addListener(new GPSTrackListener() { - private static final long serialVersionUID = -2855787105725103732L; - - @Override - public void gpsFixReceived(GPSFix fix, Mark mark) { - triggerManeuverCacheRecalculationForAllCompetitors(); - notifyListeners(fix, mark); - } - - @Override - public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, - long newMillisecondsOverWhichToAverage) { - // nobody can currently listen for the change of the mark speed averaging because mark speed is not a value used - } - - @Override - public boolean isTransient() { - return false; - } - }); - return result; - } - - /** - * Callers iterating over the result need to synchronize on the resulting collection while iterating - * to avoid {@link ConcurrentModificationException}s. - */ - private Set getListeners() { - if (listeners == null) { - listeners = new HashSet(); - } - return listeners; - } - - @Override - public void addListener(RaceChangeListener listener) { - synchronized (getListeners()) { - getListeners().add(listener); - } - } - - /** - * If the listener wants to be notified about the wind fixes already loaded, this method will obtain the read lock - * of the {@link #getWindLoadingLock() wind loading lock}, locking against the wind loading thread which uses the - * corresponding write lock. This ensures that either all wind fixes have been loaded already or that wind loading - * hasn't started yet. - */ - @Override - public void addListener(RaceChangeListener listener, final boolean notifyAboutWindFixesAlreadyLoaded) { - if (notifyAboutWindFixesAlreadyLoaded) { - LockUtil.lockForRead(getWindLoadingLock()); - } - try { - addListener(listener); - if (notifyAboutWindFixesAlreadyLoaded) { - // Now notify all wind fixes we can get from the race by now. TrackedRace.getWindSource() delivers all wind - // sources known so far. If there is a wind track being loaded, it will be separately notified later by - // DynamicTrackedRaceImpl.createWindTrack(...). - // Holding the serialization lock - for (WindSource windSource : getWindSources()) { - if (windSource.getType().canBeStored()) { - WindTrack windTrack = getOrCreateWindTrack(windSource); - // replicate all wind fixed that may have been loaded by the wind store - windTrack.lockForRead(); - try { - for (Wind wind : windTrack.getRawFixes()) { - listener.windDataReceived(wind, windSource); - } - } finally { - windTrack.unlockAfterRead(); - } - } - } - } - } finally { - if (notifyAboutWindFixesAlreadyLoaded) { - LockUtil.unlockAfterRead(getWindLoadingLock()); - } - } - } - - @Override - public void removeListener(RaceChangeListener listener) { - synchronized (getListeners()) { - getListeners().remove(listener); - } - } - - @Override - public void setWindSourcesToExclude(Iterable windSourcesToExclude) { - super.setWindSourcesToExclude(windSourcesToExclude); - notifyListenersWindSourcesToExcludeChanged(windSourcesToExclude); - } - - private void notifyListenersWindSourcesToExcludeChanged(Iterable windSourcesToExclude) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.windSourcesToExcludeChanged(windSourcesToExclude); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, "notifyListenersWindSourcesToExcludeChanged(Iterable)", e); - } - } - } - - private void notifyListenersRaceTimesChanged(TimePoint startOfTracking, TimePoint endOfTracking, - TimePoint startTimeReceived) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.raceTimesChanged(startOfTracking, endOfTracking, startTimeReceived); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, "notifyListenersRaceTimesChanged(TimePoint, TimePoint, TimePoint)", e); - } - } - } - - private void notifyListeners(GPSFix fix, Mark mark) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.markPositionChanged(fix, mark); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, "notifyListeners(GPSFix, Competitor)", e); - } - } - } - - private void notifyListeners(GPSFixMoving fix, Competitor competitor) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.competitorPositionChanged(fix, competitor); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, "notifyListeners(GPSFix, Competitor)", e); - } - } - } - - private void notifyListeners(TrackedRaceStatus status) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.statusChanged(status); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, "notifyListeners(TrackedRaceStatus)", e); - } - } - } - - private void notifyListeners(Wind wind, WindSource windSource) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.windDataReceived(wind, windSource); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, "notifyListeners(Wind)", e); - } - } - } - - private void notifyListenersSpeedAveragingChanged(long oldMillisecondsOverWhichToAverageSpeed, long newMillisecondsOverWhichToAverageSpeed) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.speedAveragingChanged(oldMillisecondsOverWhichToAverageSpeed, - newMillisecondsOverWhichToAverageSpeed); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, - "notifyListenersSpeedAveragingChanged(long, long)", e); - } - } - } - - private void notifyListenersWindAveragingChanged(long oldMillisecondsOverWhichToAverageWind, long newMillisecondsOverWhichToAverageWind) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.windAveragingChanged(oldMillisecondsOverWhichToAverageWind, - newMillisecondsOverWhichToAverageWind); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, - "notifyListenersWindAveragingChanged(long, long)", e); - } - } - } - - private void notifyListenersDelayToLiveChanged(long delayToLiveInMillis) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.delayToLiveChanged(delayToLiveInMillis); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, - "notifyListenersDelayToLiveChanged(long)", e); - } - } - } - - private void notifyListenersWindRemoved(Wind wind, WindSource windSource) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.windDataRemoved(wind, windSource); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, "notifyListenersWindRemoved(Wind)", e); - } - } - } - - private void notifyListeners(Competitor competitor, Map oldMarkPassings, Iterable markPassings) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - listener.markPassingReceived(competitor, oldMarkPassings, markPassings); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, "notifyListeners(MarkPassing)", e); - } - } - } - - @Override - public void updateMarkPassings(Competitor competitor, Iterable markPassings) { - Map oldMarkPassings = new HashMap(); - MarkPassing oldStartMarkPassing = null; - boolean requiresStartTimeUpdate = true; - final NavigableSet markPassingsForCompetitor = getMarkPassings(competitor); - lockForRead(markPassingsForCompetitor); - try { - for (MarkPassing oldMarkPassing : markPassingsForCompetitor) { - if (oldStartMarkPassing == null) { - oldStartMarkPassing = oldMarkPassing; - } - oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing); - } - } finally { - unlockAfterRead(markPassingsForCompetitor); - } - final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor); - TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0); - // Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers. - // Note that the write lock for the mark passings in order per waypoint is obtained inside clearMarkPassings(...) - // as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock for the competitor - // mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid deadlocks. - LockUtil.lockForWrite(markPassingsLock); - try { - clearMarkPassings(competitor); - for (MarkPassing markPassing : markPassings) { - // try to find corresponding old start mark passing - if (oldStartMarkPassing != null - && markPassing.getWaypoint().getName().equals(oldStartMarkPassing.getWaypoint().getName())) { - if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null - && markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) { - requiresStartTimeUpdate = false; - } - } - if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) { - StringBuilder courseWaypointsWithID = new StringBuilder(); - boolean first = true; - for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) { - if (first) { - first = false; - } else { - courseWaypointsWithID.append(" -> "); - } - courseWaypointsWithID.append(courseWaypoint.toString()); - courseWaypointsWithID.append(" (ID="); - courseWaypointsWithID.append(courseWaypoint.getId()); - courseWaypointsWithID.append(")"); - } - logger.severe("Received mark passing " + markPassing + " for race " + getRace() - + " for waypoint ID" + markPassing.getWaypoint().getId() - + " but the waypoint does not exist in course " + courseWaypointsWithID); - } else { - markPassingsForCompetitor.add(markPassing); - } - Collection markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing - .getWaypoint()); - final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint); - LockUtil.lockForWrite(markPassingsLock2); - try { - // TODO wouldn't we need to remove a previous mark passing of competitor for the same waypoint before re-adding it? - markPassingsInOrderForWaypoint.add(markPassing); - } finally { - LockUtil.unlockAfterWrite(markPassingsLock2); - } - if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) { - timePointOfLatestEvent = markPassing.getTimePoint(); - } - } - } finally { - LockUtil.unlockAfterWrite(markPassingsLock); - } - updated(timePointOfLatestEvent); - triggerManeuverCacheRecalculation(competitor); - // update the race times like start, end and the leg times - if (requiresStartTimeUpdate) { - invalidateStartTime(); - } - invalidateMarkPassingTimes(); - invalidateEndTime(); - - // notify *after* all mark passings have been re-established; should avoid flicker - notifyListeners(competitor, oldMarkPassings, markPassings); - } - - @Override - public Iterable getMarkPassingsInOrder(Waypoint waypoint) { - return (NavigableSet) super.getMarkPassingsInOrder(waypoint); - } - - @Override - public void lockForRead(Iterable markPassings) { - getRace().getCourse().lockForRead(); - LockUtil.lockForRead(getMarkPassingsLock(markPassings)); - } - - @Override - public void unlockAfterRead(Iterable markPassings) { - LockUtil.unlockAfterRead(getMarkPassingsLock(markPassings)); - getRace().getCourse().unlockAfterRead(); - } - - private void clearMarkPassings(Competitor competitor) { - NavigableSet markPassings = getMarkPassings(competitor); - final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassings); - LockUtil.lockForWrite(markPassingsLock); - try { - Iterator mpIter = markPassings.iterator(); - while (mpIter.hasNext()) { - MarkPassing mp = mpIter.next(); - mpIter.remove(); - Collection markPassingsInOrder = getMarkPassingsInOrderAsNavigableSet(mp.getWaypoint()); - LockUtil.lockForWrite(getMarkPassingsLock(markPassingsInOrder)); - try { - markPassingsInOrder.remove(mp); - } finally { - LockUtil.unlockAfterWrite(getMarkPassingsLock(markPassingsInOrder)); - } - } - } finally { - LockUtil.unlockAfterWrite(markPassingsLock); - } - } - - @Override - public void setStartTimeReceived(TimePoint startTimeReceived) { - if ((startTimeReceived == null) != (getStartTimeReceived() == null) - || (startTimeReceived != null && !startTimeReceived.equals(getStartTimeReceived()))) { - super.setStartTimeReceived(startTimeReceived); - notifyListenersRaceTimesChanged(getStartOfTracking(), getEndOfTracking(), getStartTimeReceived()); - } - } - - @Override - public void setStartOfTrackingReceived(TimePoint startOfTrackingReceived) { - if ((getStartOfTracking() == null) != (startOfTrackingReceived == null) - || (startOfTrackingReceived != null && !getStartOfTracking().equals(startOfTrackingReceived))) { - super.setStartOfTrackingReceived(startOfTrackingReceived); - notifyListenersRaceTimesChanged(getStartOfTracking(), getEndOfTracking(), getStartTimeReceived()); - } - } - - @Override - public void setEndOfTrackingReceived(TimePoint endOfTrackingReceived) { - if ((getEndOfTracking() == null) != (endOfTrackingReceived == null) - || (endOfTrackingReceived != null && !getEndOfTracking().equals(endOfTrackingReceived))) { - super.setEndOfTrackingReceived(endOfTrackingReceived); - notifyListenersRaceTimesChanged(getStartOfTracking(), getEndOfTracking(), getStartTimeReceived()); - } - } - - /** - * In addition to calling the superclass implementation, for a stored wind track whose fixes were loaded by this - * call, all listeners are notified about these existing wind fixes using their - * {@link RaceChangeListener#windDataReceived(Wind, WindSource)} callback method. In particular this replicates all - * wind fixes that may have been loaded from the wind store for the new track. - */ - @Override - protected WindTrack createWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) { - WindTrack result = super.createWindTrack(windSource, delayForWindEstimationCacheInvalidation); - if (windSource.getType().canBeStored()) { - // replicate all wind fixes that may have been loaded by the wind store - result.lockForRead(); - try { - for (Wind wind : result.getRawFixes()) { - notifyListeners(wind, windSource); // Note that this doesn't notify the track's listeners but the tracked race's listeners. - } // In particular, the wind store won't receive events (again) for the wind fixes it already has. - } finally { - result.unlockAfterRead(); - } - } - return result; - } - - @Override - public boolean recordWind(Wind wind, WindSource windSource) { - final boolean result; - // TODO check what a good filter is; remember that start/end of tracking may change over time; what if we have discarded valuable wind fixes? - TimePoint startOfRace = getStartOfRace(); - TimePoint startOfTracking = getStartOfTracking(); - TimePoint endOfRace = getEndOfRace(); - TimePoint endOfTracking = getEndOfTracking(); - if ((startOfTracking == null || !startOfTracking.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()) || - (startOfRace != null && !startOfRace.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()))) - && - (endOfTracking == null || endOfTracking.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint()) || - (endOfRace != null && endOfRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint())))) { - result = getOrCreateWindTrack(windSource).add(wind); - updated(/* time point */null); // wind events shouldn't advance race time - triggerManeuverCacheRecalculationForAllCompetitors(); - notifyListeners(wind, windSource); - } else { - result = false; - } - return result; - } - - @Override - public void removeWind(Wind wind, WindSource windSource) { - getOrCreateWindTrack(windSource).remove(wind); - updated(/* time point */null); // wind events shouldn't advance race time - triggerManeuverCacheRecalculationForAllCompetitors(); - notifyListenersWindRemoved(wind, windSource); - } - - @Override - public void gpsFixReceived(GPSFixMoving fix, Competitor competitor) { - updated(fix.getTimePoint()); - triggerManeuverCacheRecalculation(competitor); - notifyListeners(fix, competitor); - } - - @Override - public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { - notifyListenersSpeedAveragingChanged(oldMillisecondsOverWhichToAverage, newMillisecondsOverWhichToAverage); - } - - @Override - public boolean isTransient() { - return false; - } - - @Override - protected TrackedLeg createTrackedLeg(Leg leg) { - return new TrackedLegImpl(this, leg, getRace().getCompetitors()); - } - - @Override - public long getMillisecondsOverWhichToAverageSpeed() { - long result = 0; // default in case there is no competitor - Iterator compIter = getRace().getCompetitors().iterator(); - if (compIter.hasNext()) { - DynamicGPSFixTrack someTrack = getTrack(compIter.next()); - result = someTrack.getMillisecondsOverWhichToAverageSpeed(); - } - return result; - } - - @Override - public long getMillisecondsOverWhichToAverageWind() { - long result = 0; // default in case there is no competitor - for (WindSource windSource : getWindSources()) { - WindTrack someTrack = getOrCreateWindTrack(windSource); - result = someTrack.getMillisecondsOverWhichToAverageWind(); - } - return result; - } - - @Override - public DynamicTrackedRegatta getTrackedRegatta() { - return (DynamicTrackedRegatta) super.getTrackedRegatta(); - } - - @Override - public void setRaceIsKnownToStartUpwind(boolean raceIsKnownToStartUpwind) { - this.raceIsKnownToStartUpwind = raceIsKnownToStartUpwind; - } - - @Override - public boolean raceIsKnownToStartUpwind() { - return raceIsKnownToStartUpwind; - } - - @Override - public void attachRaceLog(RaceLog raceLog) { - super.attachRaceLog(raceLog); - logListener.addTo(raceLog); - } - - @Override - public void detachRaceLog(Serializable identifier) { - RaceLog attachedRaceLog = attachedRaceLogs.get(identifier); - if (attachedRaceLog != null) { - logListener.removeFrom(attachedRaceLog); - } - super.detachRaceLog(identifier); - } - - @Override - public void addCourseDesignChangedListener(CourseDesignChangedListener listener) { - this.courseDesignChangedListeners.add(listener); - } - - @Override - public void onCourseDesignChangedByRaceCommittee(CourseBase newCourseDesign) { - try { - for (CourseDesignChangedListener courseDesignChangedListener : courseDesignChangedListeners) { - courseDesignChangedListener.courseDesignChanged(newCourseDesign); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void onStartTimeChangedByRaceCommittee(TimePoint newStartTime) { - try { - for (StartTimeChangedListener startTimeChangedListener : startTimeChangedListeners) { - startTimeChangedListener.startTimeChanged(newStartTime); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void addStartTimeChangedListener(StartTimeChangedListener listener) { - this.startTimeChangedListeners.add(listener); - } - /** - * @return The Bearing of a line starting at w that needs to be crossed to pass a mark. - */ - @Override - public Bearing getCrossingBearing(Waypoint w, TimePoint t) { - Bearing result = null; - PassingInstruction instruction = w.getPassingInstructions(); - if (instruction == PassingInstruction.None || instruction == null) { - if (w.equals(getRace().getCourse().getFirstWaypoint()) || w.equals(getRace().getCourse().getLastWaypoint())) { - instruction = PassingInstruction.Line; - } else { - int numberofMarks = 0; - Iterator it = w.getMarks().iterator(); - while (it.hasNext()) { - it.next(); - numberofMarks++; - } - if (numberofMarks == 2) { - instruction = PassingInstruction.Gate; - } else if (numberofMarks == 1) { - instruction = PassingInstruction.Port; - } - } - } - if (instruction == PassingInstruction.FixedBearing) { - result = w.getFixedBearing(); - } else if (instruction == PassingInstruction.Gate || instruction == PassingInstruction.Port || instruction == PassingInstruction.Starboard) { - Bearing before = getTrackedLegFinishingAt(w).getLegBearing(t); - Bearing after = getTrackedLegStartingAt(w).getLegBearing(t); - if (before != null && after != null) { - result = before.middle(after.reverse()); - } - } else if (instruction == PassingInstruction.Line) { - Pair pos = getPortAndStarboardMarks(t, w); - if (pos.getA() != null && pos.getB() != null) { - Position portPosition = getOrCreateTrack(pos.getA()).getEstimatedPosition(t, false); - Position starboardPosition = getOrCreateTrack(pos.getB()).getEstimatedPosition(t, false); - if (portPosition != null && starboardPosition != null) { - result = portPosition.getBearingGreatCircle(starboardPosition); - } - } - } else if (instruction == PassingInstruction.Offset) { - // TODO Bug 1712 - } - return result; - } - - @Override - public Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { - List markPositions = new ArrayList(); - for (Mark lineMark : w.getMarks()) { - final Position estimatedMarkPosition = getOrCreateTrack(lineMark).getEstimatedPosition(t, /* extrapolate */ - false); - if (estimatedMarkPosition == null) { - return new Pair(null,null); - } - markPositions.add(estimatedMarkPosition); - } - final List legs = getRace().getCourse().getLegs(); - final int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(w); - final boolean isStartLine = indexOfWaypoint == 0; - final Bearing legDeterminingDirectionBearing = getTrackedLeg(legs.get(isStartLine ? 0 : indexOfWaypoint - 1)).getLegBearing(t); - if (legDeterminingDirectionBearing == null) { - return new Pair(null, null); - } - Distance crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint = markPositions.get(0).crossTrackError(markPositions.get(1), legDeterminingDirectionBearing); - final Mark starboardMarkWhileApproachingLine; - final Mark portMarkWhileApproachingLine; - if (crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint.getMeters() < 0) { - portMarkWhileApproachingLine = Util.get(w.getMarks(), 0); - starboardMarkWhileApproachingLine = Util.get(w.getMarks(), 1); - } else { - portMarkWhileApproachingLine = Util.get(w.getMarks(), 1); - starboardMarkWhileApproachingLine = Util.get(w.getMarks(), 0); - } - return new Pair(portMarkWhileApproachingLine, starboardMarkWhileApproachingLine); - } -} +package com.sap.sailing.domain.tracking.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.Leg; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.PassingInstruction; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimingConstants; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.WindSourceImpl; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.tracking.CourseDesignChangedListener; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.GPSTrackListener; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.RaceChangeListener; +import com.sap.sailing.domain.tracking.StartTimeChangedListener; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; +import com.sap.sailing.domain.tracking.TrackedRegatta; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.util.impl.LockUtil; +import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sse.common.Util; + +public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements +DynamicTrackedRace, GPSTrackListener { + private static final long serialVersionUID = 1092726918239676958L; + + private static final Logger logger = Logger.getLogger(DynamicTrackedRaceImpl.class.getName()); + + private transient Set listeners; + + private boolean raceIsKnownToStartUpwind; + + private boolean delayToLiveInMillisFixed; + + private transient DynamicTrackedRaceLogListener logListener; + + private transient Set courseDesignChangedListeners; + private transient Set startTimeChangedListeners; + + public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, + WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, + long delayForCacheInvalidationOfWindEstimation) { + super(trackedRegatta, race, sidelines, windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, + delayForCacheInvalidationOfWindEstimation); + this.logListener = new DynamicTrackedRaceLogListener(this); + this.courseDesignChangedListeners = new HashSet(); + this.startTimeChangedListeners = new HashSet(); + this.raceIsKnownToStartUpwind = race.getBoatClass().typicallyStartsUpwind(); + if (!raceIsKnownToStartUpwind) { + Set windSourcesToExclude = new HashSet(); + for (WindSource windSourceToExclude : getWindSourcesToExclude()) { + windSourcesToExclude.add(windSourceToExclude); + } + windSourcesToExclude.add(new WindSourceImpl(WindSourceType.COURSE_BASED)); + setWindSourcesToExclude(windSourcesToExclude); + } + for (Competitor competitor : getRace().getCompetitors()) { + DynamicGPSFixTrack track = getTrack(competitor); + track.addListener(this); + } + // default wind tracks are observed because they are created by the superclass constructor using + // createWindTrack which adds this object as a listener + } + + /** + * After de-serialization sets a valid {@link #listeners} collection which is transient and therefore + * hasn't been serialized. + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + listeners = new HashSet(); + logListener = new DynamicTrackedRaceLogListener(this); + courseDesignChangedListeners = new HashSet(); + startTimeChangedListeners = new HashSet(); + } + + /** + * {@link #raceIsKnownToStartUpwind} (see also {@link #raceIsKnownToStartUpwind()}) is initialized based on the race's + * {@link RaceDefinition#getBoatClass()} boat class's {@link BoatClass#typicallyStartsUpwind()} result. It can be changed + * using {@link #setRaceIsKnownToStartUpwind(boolean)}. Uses millisecondsOverWhichToAverageWind/2 for the + * delayForCacheInvalidationOfWindEstimation argument of the constructor.

+ * + * Loading wind tracks from the windStore happens asynchronously which means that when the constructor returns, + * the caller cannot assume that all wind tracks have yet been loaded completely. The caller may call {@link #waitUntilLoadingFromWindStoreComplete()} + * to wait until all persistent wind sources have been successfully and completely loaded. + */ + public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, + WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, + long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed) { + this(trackedRegatta, race, sidelines, windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, + millisecondsOverWhichToAverageWind/2); + } + + @Override + public void recordFix(Competitor competitor, GPSFixMoving fix) { + DynamicGPSFixTrack track = getTrack(competitor); + if (track != null) { + if (logger != null && logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { + logger.finest(""+competitor.getName() + ": " + fix); + } + track.addGPSFix(fix); // the track notifies this tracked race which in turn notifies its listeners + } + } + + @Override + public void setStatus(TrackedRaceStatus newStatus) { + super.setStatus(newStatus); + notifyListeners(newStatus); + } + + @Override + public void recordFix(Mark mark, GPSFix fix) { + getOrCreateTrack(mark).addGPSFix(fix); + } + + @Override + public void setMillisecondsOverWhichToAverageSpeed(long millisecondsOverWhichToAverageSpeed) { + this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed; + for (Competitor competitor : getRace().getCompetitors()) { + getTrack(competitor).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageSpeed); + } + for (Waypoint waypoint : getRace().getCourse().getWaypoints()) { + for (Mark mark : waypoint.getMarks()) { + getOrCreateTrack(mark).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageSpeed); + } + } + updated(/* time point */null); + triggerManeuverCacheRecalculationForAllCompetitors(); + } + + @Override + public void setMillisecondsOverWhichToAverageWind(long millisecondsOverWhichToAverageWind) { + long oldMillisecondsOverWhichToAverageWind = this.millisecondsOverWhichToAverageWind; + this.millisecondsOverWhichToAverageWind = millisecondsOverWhichToAverageWind; + for (WindSource windSource : getWindSources()) { + getOrCreateWindTrack(windSource).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageWind); + } + updated(/* time point */null); + triggerManeuverCacheRecalculationForAllCompetitors(); + notifyListenersWindAveragingChanged(oldMillisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageWind); + } + + + @Override + public void setAndFixDelayToLiveInMillis(long delayToLiveInMillis) { + super.setDelayToLiveInMillis(delayToLiveInMillis); + delayToLiveInMillisFixed = true; + } + + @Override + public void setDelayToLiveInMillis(long delayToLiveInMillis) { + if (!delayToLiveInMillisFixed && getDelayToLiveInMillis() != delayToLiveInMillis) { + super.setDelayToLiveInMillis(delayToLiveInMillis); + notifyListenersDelayToLiveChanged(delayToLiveInMillis); + } + } + + @Override + public DynamicGPSFixTrack getTrack(Competitor competitor) { + return (DynamicGPSFixTrack) super.getTrack(competitor); + } + + @Override + public DynamicGPSFixTrack getOrCreateTrack(Mark mark) { + return (DynamicGPSFixTrack) super.getOrCreateTrack(mark); + } + + /** + * 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) 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. + */ + @Override + protected DynamicGPSFixTrackImpl createMarkTrack(Mark mark) { + DynamicGPSFixTrackImpl result = super.createMarkTrack(mark); + result.addListener(new GPSTrackListener() { + private static final long serialVersionUID = -2855787105725103732L; + + @Override + public void gpsFixReceived(GPSFix fix, Mark mark) { + triggerManeuverCacheRecalculationForAllCompetitors(); + notifyListeners(fix, mark); + } + + @Override + public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, + long newMillisecondsOverWhichToAverage) { + // nobody can currently listen for the change of the mark speed averaging because mark speed is not a value used + } + + @Override + public boolean isTransient() { + return false; + } + }); + return result; + } + + /** + * Callers iterating over the result need to synchronize on the resulting collection while iterating + * to avoid {@link ConcurrentModificationException}s. + */ + private Set getListeners() { + if (listeners == null) { + listeners = new HashSet(); + } + return listeners; + } + + @Override + public void addListener(RaceChangeListener listener) { + synchronized (getListeners()) { + getListeners().add(listener); + } + } + + @Override + public void addListener(RaceChangeListener listener, final boolean notifyAboutWindFixesAlreadyLoaded, + final boolean notifyAboutGPSFixesAlreadyLoaded) { + if (notifyAboutWindFixesAlreadyLoaded) { + LockUtil.lockForRead(getLoadingFromWindStoreLock()); + } + if (notifyAboutGPSFixesAlreadyLoaded) { + LockUtil.lockForRead(getLoadingFromGPSFixStoreLock()); + } + try { + addListener(listener); + if (notifyAboutWindFixesAlreadyLoaded) { + // Now notify all wind fixes we can get from the race by now. TrackedRace.getWindSource() delivers all wind + // sources known so far. If there is a wind track being loaded, it will be separately notified later by + // DynamicTrackedRaceImpl.createWindTrack(...). + // Holding the serialization lock + for (WindSource windSource : getWindSources()) { + if (windSource.getType().canBeStored()) { + WindTrack windTrack = getOrCreateWindTrack(windSource); + // replicate all wind fixed that may have been loaded by the wind store + windTrack.lockForRead(); + try { + for (Wind wind : windTrack.getRawFixes()) { + listener.windDataReceived(wind, windSource); + } + } finally { + windTrack.unlockAfterRead(); + } + } + } + } + if (notifyAboutGPSFixesAlreadyLoaded) { + for (Mark mark : getMarks()) { + GPSFixTrack markTrack = getOrCreateTrack(mark); + markTrack.lockForRead(); + try { + for (GPSFix fix : markTrack.getRawFixes()) { + listener.markPositionChanged(fix, mark); + } + } finally { + markTrack.unlockAfterRead(); + } + } + + for (Competitor competitor : getRace().getCompetitors()) { + GPSFixTrack competitorTrack = getTrack(competitor); + competitorTrack.lockForRead(); + try { + for (GPSFixMoving fix : competitorTrack.getRawFixes()) { + listener.competitorPositionChanged(fix, competitor); + } + } finally { + competitorTrack.unlockAfterRead(); + } + } + } + } finally { + if (notifyAboutWindFixesAlreadyLoaded) { + LockUtil.unlockAfterRead(getLoadingFromWindStoreLock()); + } + if (notifyAboutGPSFixesAlreadyLoaded) { + LockUtil.unlockAfterRead(getLoadingFromGPSFixStoreLock()); + } + } + } + + @Override + public void removeListener(RaceChangeListener listener) { + synchronized (getListeners()) { + getListeners().remove(listener); + } + } + + @Override + public void setWindSourcesToExclude(Iterable windSourcesToExclude) { + super.setWindSourcesToExclude(windSourcesToExclude); + notifyListenersWindSourcesToExcludeChanged(windSourcesToExclude); + } + + private void notifyListenersWindSourcesToExcludeChanged(Iterable windSourcesToExclude) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.windSourcesToExcludeChanged(windSourcesToExclude); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, "notifyListenersWindSourcesToExcludeChanged(Iterable)", e); + } + } + } + + private void notifyListenersRaceTimesChanged(TimePoint startOfTracking, TimePoint endOfTracking, + TimePoint startTimeReceived) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.raceTimesChanged(startOfTracking, endOfTracking, startTimeReceived); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, "notifyListenersRaceTimesChanged(TimePoint, TimePoint, TimePoint)", e); + } + } + } + + private void notifyListeners(GPSFix fix, Mark mark) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.markPositionChanged(fix, mark); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, "notifyListeners(GPSFix, Competitor)", e); + } + } + } + + private void notifyListeners(GPSFixMoving fix, Competitor competitor) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.competitorPositionChanged(fix, competitor); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, "notifyListeners(GPSFix, Competitor)", e); + } + } + } + + private void notifyListeners(TrackedRaceStatus status) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.statusChanged(status); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, "notifyListeners(TrackedRaceStatus)", e); + } + } + } + + private void notifyListeners(Wind wind, WindSource windSource) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.windDataReceived(wind, windSource); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, "notifyListeners(Wind)", e); + } + } + } + + private void notifyListenersSpeedAveragingChanged(long oldMillisecondsOverWhichToAverageSpeed, long newMillisecondsOverWhichToAverageSpeed) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.speedAveragingChanged(oldMillisecondsOverWhichToAverageSpeed, + newMillisecondsOverWhichToAverageSpeed); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, + "notifyListenersSpeedAveragingChanged(long, long)", e); + } + } + } + + private void notifyListenersWindAveragingChanged(long oldMillisecondsOverWhichToAverageWind, long newMillisecondsOverWhichToAverageWind) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.windAveragingChanged(oldMillisecondsOverWhichToAverageWind, + newMillisecondsOverWhichToAverageWind); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, + "notifyListenersWindAveragingChanged(long, long)", e); + } + } + } + + private void notifyListenersDelayToLiveChanged(long delayToLiveInMillis) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.delayToLiveChanged(delayToLiveInMillis); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, + "notifyListenersDelayToLiveChanged(long)", e); + } + } + } + + private void notifyListenersWindRemoved(Wind wind, WindSource windSource) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.windDataRemoved(wind, windSource); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, "notifyListenersWindRemoved(Wind)", e); + } + } + } + + private void notifyListeners(Competitor competitor, Map oldMarkPassings, Iterable markPassings) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + listener.markPassingReceived(competitor, oldMarkPassings, markPassings); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, "notifyListeners(MarkPassing)", e); + } + } + } + + @Override + public void updateMarkPassings(Competitor competitor, Iterable markPassings) { + Map oldMarkPassings = new HashMap(); + MarkPassing oldStartMarkPassing = null; + boolean requiresStartTimeUpdate = true; + final NavigableSet markPassingsForCompetitor = getMarkPassings(competitor); + lockForRead(markPassingsForCompetitor); + try { + for (MarkPassing oldMarkPassing : markPassingsForCompetitor) { + if (oldStartMarkPassing == null) { + oldStartMarkPassing = oldMarkPassing; + } + oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing); + } + } finally { + unlockAfterRead(markPassingsForCompetitor); + } + final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor); + TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0); + // Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers. + // Note that the write lock for the mark passings in order per waypoint is obtained inside clearMarkPassings(...) + // as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock for the competitor + // mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid deadlocks. + LockUtil.lockForWrite(markPassingsLock); + try { + clearMarkPassings(competitor); + for (MarkPassing markPassing : markPassings) { + // try to find corresponding old start mark passing + if (oldStartMarkPassing != null + && markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) { + if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null + && markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) { + requiresStartTimeUpdate = false; + } + } + if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) { + StringBuilder courseWaypointsWithID = new StringBuilder(); + boolean first = true; + for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) { + if (first) { + first = false; + } else { + courseWaypointsWithID.append(" -> "); + } + courseWaypointsWithID.append(courseWaypoint.toString()); + courseWaypointsWithID.append(" (ID="); + courseWaypointsWithID.append(courseWaypoint.getId()); + courseWaypointsWithID.append(")"); + } + logger.severe("Received mark passing " + markPassing + " for race " + getRace() + + " for waypoint ID" + markPassing.getWaypoint().getId() + + " but the waypoint does not exist in course " + courseWaypointsWithID); + } else { + markPassingsForCompetitor.add(markPassing); + } + Collection markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing + .getWaypoint()); + final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint); + LockUtil.lockForWrite(markPassingsLock2); + try { + // The mark passings of competitor have been removed by the call to clearMarkPassings(competitor) above + // from both, the collection that holds the mark passings by waypoint and the one that holds the + // mark passings per competitor; so we can simply add here: + markPassingsInOrderForWaypoint.add(markPassing); + } finally { + LockUtil.unlockAfterWrite(markPassingsLock2); + } + if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) { + timePointOfLatestEvent = markPassing.getTimePoint(); + } + } + } finally { + LockUtil.unlockAfterWrite(markPassingsLock); + } + updated(timePointOfLatestEvent); + triggerManeuverCacheRecalculation(competitor); + // update the race times like start, end and the leg times + if (requiresStartTimeUpdate) { + invalidateStartTime(); + } + invalidateMarkPassingTimes(); + invalidateEndTime(); + + // notify *after* all mark passings have been re-established; should avoid flicker + notifyListeners(competitor, oldMarkPassings, markPassings); + } + + @Override + public Iterable getMarkPassingsInOrder(Waypoint waypoint) { + return (NavigableSet) super.getMarkPassingsInOrder(waypoint); + } + + @Override + public void lockForRead(Iterable markPassings) { + getRace().getCourse().lockForRead(); + LockUtil.lockForRead(getMarkPassingsLock(markPassings)); + } + + @Override + public void unlockAfterRead(Iterable markPassings) { + LockUtil.unlockAfterRead(getMarkPassingsLock(markPassings)); + getRace().getCourse().unlockAfterRead(); + } + + /** + * Removes all mark passings of competitor from both, the {@link #markPassingsForCompetitor} + * and the {@link #markPassingsForWaypoint} collections. + */ + private void clearMarkPassings(Competitor competitor) { + NavigableSet markPassings = getMarkPassings(competitor); + final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassings); + LockUtil.lockForWrite(markPassingsLock); + try { + Iterator mpIter = markPassings.iterator(); + while (mpIter.hasNext()) { + MarkPassing mp = mpIter.next(); + mpIter.remove(); + Collection markPassingsInOrder = getMarkPassingsInOrderAsNavigableSet(mp.getWaypoint()); + LockUtil.lockForWrite(getMarkPassingsLock(markPassingsInOrder)); + try { + markPassingsInOrder.remove(mp); + } finally { + LockUtil.unlockAfterWrite(getMarkPassingsLock(markPassingsInOrder)); + } + } + } finally { + LockUtil.unlockAfterWrite(markPassingsLock); + } + } + + @Override + public void setStartTimeReceived(TimePoint startTimeReceived) { + if ((startTimeReceived == null) != (getStartTimeReceived() == null) + || (startTimeReceived != null && !startTimeReceived.equals(getStartTimeReceived()))) { + super.setStartTimeReceived(startTimeReceived); + notifyListenersRaceTimesChanged(getStartOfTracking(), getEndOfTracking(), getStartTimeReceived()); + } + } + + @Override + public void setStartOfTrackingReceived(TimePoint startOfTrackingReceived) { + if ((getStartOfTracking() == null) != (startOfTrackingReceived == null) + || (startOfTrackingReceived != null && !getStartOfTracking().equals(startOfTrackingReceived))) { + super.setStartOfTrackingReceived(startOfTrackingReceived); + notifyListenersRaceTimesChanged(getStartOfTracking(), getEndOfTracking(), getStartTimeReceived()); + } + } + + @Override + public void setEndOfTrackingReceived(TimePoint endOfTrackingReceived) { + if ((getEndOfTracking() == null) != (endOfTrackingReceived == null) + || (endOfTrackingReceived != null && !getEndOfTracking().equals(endOfTrackingReceived))) { + super.setEndOfTrackingReceived(endOfTrackingReceived); + notifyListenersRaceTimesChanged(getStartOfTracking(), getEndOfTracking(), getStartTimeReceived()); + } + } + + /** + * In addition to calling the superclass implementation, for a stored wind track whose fixes were loaded by this + * call, all listeners are notified about these existing wind fixes using their + * {@link RaceChangeListener#windDataReceived(Wind, WindSource)} callback method. In particular this replicates all + * wind fixes that may have been loaded from the wind store for the new track. + */ + @Override + protected WindTrack createWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) { + WindTrack result = super.createWindTrack(windSource, delayForWindEstimationCacheInvalidation); + if (windSource.getType().canBeStored()) { + // replicate all wind fixes that may have been loaded by the wind store + result.lockForRead(); + try { + for (Wind wind : result.getRawFixes()) { + notifyListeners(wind, windSource); // Note that this doesn't notify the track's listeners but the tracked race's listeners. + } // In particular, the wind store won't receive events (again) for the wind fixes it already has. + } finally { + result.unlockAfterRead(); + } + } + return result; + } + + @Override + public boolean recordWind(Wind wind, WindSource windSource) { + final boolean result; + // TODO check what a good filter is; remember that start/end of tracking may change over time; what if we have discarded valuable wind fixes? + TimePoint startOfRace = getStartOfRace(); + TimePoint startOfTracking = getStartOfTracking(); + TimePoint endOfRace = getEndOfRace(); + TimePoint endOfTracking = getEndOfTracking(); + if ((startOfTracking == null || !startOfTracking.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()) || + (startOfRace != null && !startOfRace.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()))) + && + (endOfTracking == null || endOfTracking.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint()) || + (endOfRace != null && endOfRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint())))) { + result = getOrCreateWindTrack(windSource).add(wind); + updated(/* time point */null); // wind events shouldn't advance race time + triggerManeuverCacheRecalculationForAllCompetitors(); + notifyListeners(wind, windSource); + } else { + result = false; + } + return result; + } + + @Override + public void removeWind(Wind wind, WindSource windSource) { + getOrCreateWindTrack(windSource).remove(wind); + updated(/* time point */null); // wind events shouldn't advance race time + triggerManeuverCacheRecalculationForAllCompetitors(); + notifyListenersWindRemoved(wind, windSource); + } + + @Override + public void gpsFixReceived(GPSFixMoving fix, Competitor competitor) { + updated(fix.getTimePoint()); + triggerManeuverCacheRecalculation(competitor); + notifyListeners(fix, competitor); + } + + @Override + public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { + notifyListenersSpeedAveragingChanged(oldMillisecondsOverWhichToAverage, newMillisecondsOverWhichToAverage); + } + + @Override + public boolean isTransient() { + return false; + } + + @Override + protected TrackedLeg createTrackedLeg(Leg leg) { + return new TrackedLegImpl(this, leg, getRace().getCompetitors()); + } + + @Override + public long getMillisecondsOverWhichToAverageSpeed() { + long result = 0; // default in case there is no competitor + Iterator compIter = getRace().getCompetitors().iterator(); + if (compIter.hasNext()) { + DynamicGPSFixTrack someTrack = getTrack(compIter.next()); + result = someTrack.getMillisecondsOverWhichToAverageSpeed(); + } + return result; + } + + @Override + public long getMillisecondsOverWhichToAverageWind() { + long result = 0; // default in case there is no competitor + for (WindSource windSource : getWindSources()) { + WindTrack someTrack = getOrCreateWindTrack(windSource); + result = someTrack.getMillisecondsOverWhichToAverageWind(); + } + return result; + } + + @Override + public DynamicTrackedRegatta getTrackedRegatta() { + return (DynamicTrackedRegatta) super.getTrackedRegatta(); + } + + @Override + public void setRaceIsKnownToStartUpwind(boolean raceIsKnownToStartUpwind) { + this.raceIsKnownToStartUpwind = raceIsKnownToStartUpwind; + } + + @Override + public boolean raceIsKnownToStartUpwind() { + return raceIsKnownToStartUpwind; + } + + @Override + public void attachRaceLog(RaceLog raceLog) { + super.attachRaceLog(raceLog); + logListener.addTo(raceLog); + } + + @Override + public void detachRaceLog(Serializable identifier) { + RaceLog attachedRaceLog = attachedRaceLogs.get(identifier); + if (attachedRaceLog != null) { + logListener.removeFrom(attachedRaceLog); + } + super.detachRaceLog(identifier); + } + + @Override + public void addCourseDesignChangedListener(CourseDesignChangedListener listener) { + this.courseDesignChangedListeners.add(listener); + } + + @Override + public void onCourseDesignChangedByRaceCommittee(CourseBase newCourseDesign) { + try { + for (CourseDesignChangedListener courseDesignChangedListener : courseDesignChangedListeners) { + courseDesignChangedListener.courseDesignChanged(newCourseDesign); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onStartTimeChangedByRaceCommittee(TimePoint newStartTime) { + try { + for (StartTimeChangedListener startTimeChangedListener : startTimeChangedListeners) { + startTimeChangedListener.startTimeChanged(newStartTime); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void addStartTimeChangedListener(StartTimeChangedListener listener) { + this.startTimeChangedListeners.add(listener); + } + /** + * @return The Bearing of a line starting at w that needs to be crossed to pass a mark. + */ + @Override + public Bearing getCrossingBearing(Waypoint w, TimePoint t) { + Bearing result = null; + PassingInstruction instruction = w.getPassingInstructions(); + if (instruction == PassingInstruction.None || instruction == null) { + if (w.equals(getRace().getCourse().getFirstWaypoint()) || w.equals(getRace().getCourse().getLastWaypoint())) { + instruction = PassingInstruction.Line; + } else { + int numberofMarks = 0; + Iterator it = w.getMarks().iterator(); + while (it.hasNext()) { + it.next(); + numberofMarks++; + } + if (numberofMarks == 2) { + instruction = PassingInstruction.Gate; + } else if (numberofMarks == 1) { + instruction = PassingInstruction.Port; + } + } + } + if (instruction == PassingInstruction.FixedBearing) { + result = w.getFixedBearing(); + } else if (instruction == PassingInstruction.Gate || instruction == PassingInstruction.Port || instruction == PassingInstruction.Starboard) { + Bearing before = getTrackedLegFinishingAt(w).getLegBearing(t); + Bearing after = getTrackedLegStartingAt(w).getLegBearing(t); + if (before != null && after != null) { + result = before.middle(after.reverse()); + } + } else if (instruction == PassingInstruction.Line) { + com.sap.sse.common.Util.Pair pos = getPortAndStarboardMarks(t, w); + if (pos.getA() != null && pos.getB() != null) { + Position portPosition = getOrCreateTrack(pos.getA()).getEstimatedPosition(t, false); + Position starboardPosition = getOrCreateTrack(pos.getB()).getEstimatedPosition(t, false); + if (portPosition != null && starboardPosition != null) { + result = portPosition.getBearingGreatCircle(starboardPosition); + } + } + } else if (instruction == PassingInstruction.Offset) { + // TODO Bug 1712 + } + return result; + } + @Override + public com.sap.sse.common.Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { + List markPositions = new ArrayList(); + for (Mark lineMark : w.getMarks()) { + final Position estimatedMarkPosition = getOrCreateTrack(lineMark).getEstimatedPosition(t, /* extrapolate */ + false); + if (estimatedMarkPosition == null) { + return new com.sap.sse.common.Util.Pair(null,null); + } + markPositions.add(estimatedMarkPosition); + } + final List legs = getRace().getCourse().getLegs(); + final int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(w); + final boolean isStartLine = indexOfWaypoint == 0; + final Bearing legDeterminingDirectionBearing = getTrackedLeg(legs.get(isStartLine ? 0 : indexOfWaypoint - 1)).getLegBearing(t); + if (legDeterminingDirectionBearing == null) { + return new com.sap.sse.common.Util.Pair(null, null); + } + Distance crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint = markPositions.get(0).crossTrackError(markPositions.get(1), legDeterminingDirectionBearing); + final Mark starboardMarkWhileApproachingLine; + final Mark portMarkWhileApproachingLine; + if (crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint.getMeters() < 0) { + portMarkWhileApproachingLine = Util.get(w.getMarks(), 0); + starboardMarkWhileApproachingLine = Util.get(w.getMarks(), 1); + } else { + portMarkWhileApproachingLine = Util.get(w.getMarks(), 1); + starboardMarkWhileApproachingLine = Util.get(w.getMarks(), 0); + } + return new com.sap.sse.common.Util.Pair(portMarkWhileApproachingLine, starboardMarkWhileApproachingLine); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRegattaImpl.java index 891af0e4772..74b99999984 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRegattaImpl.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.tracking.impl; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; @@ -27,9 +28,9 @@ public class DynamicTrackedRegattaImpl extends TrackedRegattaImpl implements Dyn @Override public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, - long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, + GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { - return (DynamicTrackedRace) super.createTrackedRace(raceDefinition, sidelines, windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, + return (DynamicTrackedRace) super.createTrackedRace(raceDefinition, sidelines, windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, raceDefinitionSetToUpdate); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java index 9f8e9f25cbf..3c23bd82534 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java @@ -1,1047 +1,1048 @@ -package com.sap.sailing.domain.tracking.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.ConcurrentModificationException; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.NavigableSet; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.sap.sailing.domain.base.BearingWithConfidence; -import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; -import com.sap.sailing.domain.base.SpeedWithConfidence; -import com.sap.sailing.domain.base.Timed; -import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; -import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; -import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.KnotSpeedImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.NauticalMileDistance; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; -import com.sap.sailing.domain.confidence.ConfidenceFactory; -import com.sap.sailing.domain.confidence.HasConfidence; -import com.sap.sailing.domain.confidence.Weigher; -import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.GPSTrackListener; -import com.sap.sailing.domain.tracking.WithValidityCache; -import com.sap.sailing.util.impl.ArrayListNavigableSet; - -public class GPSFixTrackImpl extends TrackImpl implements GPSFixTrack { - private static final Logger logger = Logger.getLogger(GPSFixTrackImpl.class.getName()); - private static final long serialVersionUID = -7282869695818293745L; - private static final Speed DEFAULT_MAX_SPEED_FOR_SMOOTHING = new KnotSpeedImpl(50); - protected final Speed maxSpeedForSmoothing; - - private final ItemType trackedItem; - private long millisecondsOverWhichToAverage; - private final GPSTrackListeners listeners; - - - private static class GPSTrackListeners implements Serializable { - private static final long serialVersionUID = -7117842092078781722L; - private Set> listeners; - - public GPSTrackListeners() { - listeners = new HashSet<>(); - } - - @SuppressWarnings("unchecked") // need typed generic cast - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - listeners = (Set>) ois.readObject(); - } - - private void writeObject(ObjectOutputStream oos) throws IOException { - final Set> listenersToSerialize; - synchronized (listeners) { - listenersToSerialize = new HashSet<>(); - for (GPSTrackListener listener : listeners) { - if (!listener.isTransient()) { - listenersToSerialize.add(listener); - } - } - } - oos.writeObject(listenersToSerialize); - } - - public void addListener(GPSTrackListener listener) { - synchronized (listeners) { - listeners.add(listener); - } - } - - public void removeListener(GPSTrackListener listener) { - synchronized (listeners) { - listeners.remove(listener); - } - } - - /** - * To iterate over the resulting listener list, synchronize on the iterable returned. Only this will avoid - * {@link ConcurrentModificationException}s because listeners may be added on the fly, and this object will - * synchronize on the listeners collection before adding on. - */ - public Iterable> getListeners() { - synchronized (listeners) { - return new HashSet>(listeners); - } - } - } - - /** - * Computing {@link #getDistanceTraveled(TimePoint, TimePoint)} is more expensive the longer the track is and the - * higher the tracking frequency. It is called regularly, usually for the same start time. Keeping to re-evaluate - * all distances between all GPS fixes, even if they remain constant, is a waste of resources, particularly when we - * assume that usually GPS fixes are appended to a track and not usually randomly inserted, even though this can - * happen. - *

- * - * This cache looks "backwards." It contains pairs whose first component represents a to parameter used - * in {@link #getDistanceTraveled(TimePoint, TimePoint)}. It is ordered by this component. The second component is a - * navigable, ordered set of pairs where the first pair component represents a from parameter used in - * {@link #getDistanceTraveled(TimePoint, TimePoint)} and the second pair component represents the result of - * {@link #getDistanceTraveled(TimePoint, TimePoint)} for this parameter combination. Note that the cache does only - * store distances for from time points actually passed to - * {@link #getDistanceTraveled(TimePoint, TimePoint)} and does not cache all interim combinations collected while - * computing a result. As such, it differs from a typical "dynamic programming" algorithm which would store all - * in-between values too. Assuming that from values are sparse (leg start, race start, ...), there - * doesn't seem to be much use in storing all the other in-between values too. - *

- * - * For implementation efficiency in combination with using a {@link ArrayListNavigableSet} for the values and in - * order to be able to efficiently extend a cache entry for a single to fix, the navigable sets - * containing the from fixes and distances are ordered such that earlier fixes come later in the set. - * This way, extending the cache entry for a to fix to an earlier from fix only requires - * appending to the set. - *

- * - * Invalidation: When a new fix is added to this track, all distance cache entries for fixes at or later than - * the new fix's time point are removed from this cache. Additionally, the fix insertion may have an impact on the - * {@link #getEarlierFixesWhoseValidityMayBeAffected(GPSFix) previous fix's} validity (track smoothing) and - * therefore on its selection for distance aggregation. Therefore, if fix addition turned the previous fix invalid, - * the cache entries for the time points at or after the previous fix also need to be removed. - *

- * - * Cache use: When {@link #getDistanceTraveled(TimePoint, TimePoint)} is called, it first looks for a cache - * entry for the to parameter. If one is found, the earliest entry in the navigable set for the - * navigable set of from and distance values that is at or after the requested from time - * point is determined. If such an entry exists, the distance is remembered and the algorithm is repeated - * recursively, using the from value found in the cache as the new to value, and the - * from value originally passed to {@link #getDistanceTraveled(TimePoint, TimePoint)} as - * from again. If no entry is found in the cache entry for to that is at or after the - * requested from time, the distance is computed by iterating the smoothened fixes. - *

- * - * If a cache entry for to is not found, the latest cache entry before it is looked up. If one is - * found, the distance between the to time point requested and the to time point found in - * the cache is computed by iterating the smoothened fixes for this interval. If none is found, the distance is - * computed by iterating backwards all the way to from. - *

- * - * Once the {@link #getDistanceTraveled(TimePoint, TimePoint)} has computed its value, it adds the result to the - * cache. - */ - private transient DistanceCache distanceCache; - - private transient MaxSpeedCache maxSpeedCache; - - public GPSFixTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage) { - this(trackedItem, millisecondsOverWhichToAverage, DEFAULT_MAX_SPEED_FOR_SMOOTHING); - } - - /** - * @param maxSpeedForSmoothening - * if null, any fix speed will be accepted as long as fix-provided speed (if any) and - * inferred speed can be matched. - */ - public GPSFixTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage, Speed maxSpeedForSmoothening) { - super(/* nameForReadWriteLock */ GPSFixTrackImpl.class.getSimpleName()+(trackedItem==null?"":(" for "+trackedItem.toString()))); - this.trackedItem = trackedItem; - this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage; - this.maxSpeedForSmoothing = maxSpeedForSmoothening; - this.listeners = new GPSTrackListeners(); - this.distanceCache = new DistanceCache(trackedItem==null?"null":trackedItem.toString()); - this.maxSpeedCache = new MaxSpeedCache(this); - } - - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - distanceCache = new DistanceCache(trackedItem.toString()); - maxSpeedCache = new MaxSpeedCache(this); - } - - @Override - public String toString() { - return super.toString()+" for "+getTrackedItem(); - } - - @Override - public void addListener(GPSTrackListener listener) { - listeners.addListener(listener); - } - - @Override - public void removeListener(GPSTrackListener listener) { - listeners.removeListener(listener); - } - - protected Iterable> getListeners() { - return listeners.getListeners(); - } - - private class DummyGPSFix extends DummyTimed implements GPSFix { - private static final long serialVersionUID = -6258506654181816698L; - - public DummyGPSFix(TimePoint timePoint) { - super(timePoint); - } - @Override - public Position getPosition() { - return null; - } - @Override - public SpeedWithBearing getSpeedAndBearingRequiredToReach(GPSFix to) { - return null; - } - @Override - public boolean isValidityCached() { - return false; - } - - @Override - public boolean isValid() { - return false; - } - - @Override - public void invalidateCache() { - } - - @Override - public void cacheValidity(boolean isValid) { - } - } - - - @Override - protected FixType getDummyFix(TimePoint timePoint) { - @SuppressWarnings("unchecked") - FixType result = (FixType) new DummyGPSFix(timePoint); - return result; - } - - @Override - public ItemType getTrackedItem() { - return trackedItem; - } - - @Override - public long getMillisecondsOverWhichToAverageSpeed() { - return millisecondsOverWhichToAverage; - } - - private Pair getFixesForPositionEstimation(TimePoint timePoint, boolean inclusive) { - lockForRead(); - try { - FixType lastFixBefore = inclusive ? getLastFixAtOrBefore(timePoint) : getLastFixBefore(timePoint); - FixType firstFixAfter = inclusive ? getFirstFixAtOrAfter(timePoint) : getFirstFixAfter(timePoint); - return new Pair(lastFixBefore, firstFixAfter); - } finally { - unlockAfterRead(); - } - } - - private class EstimatedPositionIterator implements Iterator { - private final Iterator timedsIter; - private final boolean extrapolate; - private final NavigableSet fixes; - private Iterator subSetIterator; - private FixType earlierFix; - private FixType laterFix; // if this is null, earlierFix is also null - - public EstimatedPositionIterator(Iterable timeds, boolean extrapolate) { - this.timedsIter = timeds.iterator(); - this.extrapolate = extrapolate; - this.fixes = getFixes(); - } - - @Override - public boolean hasNext() { - return timedsIter.hasNext(); - } - - @Override - public Position next() { - TimePoint nextTimePoint = timedsIter.next().getTimePoint(); - if (subSetIterator == null) { - earlierFix = getLastFixAtOrBefore(nextTimePoint); - subSetIterator = fixes.subSet(createDummyGPSFix(nextTimePoint), /* fromInclusive */true, fixes.last(), /* toInclusive */ - true).iterator(); - laterFix = subSetIterator.hasNext() ? subSetIterator.next() : null; - } else { - while (laterFix != null && laterFix.getTimePoint().before(nextTimePoint)) { - earlierFix = laterFix; - laterFix = subSetIterator.hasNext() ? subSetIterator.next() : null; - } - } - return getEstimatedPosition(nextTimePoint, extrapolate, earlierFix, laterFix); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - @Override - public Iterator getEstimatedPositions(Iterable timeds, boolean extrapolate) { - return new EstimatedPositionIterator(timeds, extrapolate); - } - - @Override - public Position getEstimatedPosition(TimePoint timePoint, boolean extrapolate) { - lockForRead(); - try { - Pair fixesForPositionEstimation = getFixesForPositionEstimation(timePoint, /* inclusive */ true); - return getEstimatedPosition(timePoint, extrapolate, fixesForPositionEstimation.getA(), fixesForPositionEstimation.getB()); - } finally { - unlockAfterRead(); - } - } - - @Override - public Pair getEstimatedPositionTimePeriodAffectedBy(GPSFix fix) { - if (fix == null) { - throw new IllegalArgumentException("fix must not be null"); - } - lockForRead(); - try { - Pair fixesForPositionEstimation = getFixesForPositionEstimation(fix.getTimePoint(), /* inclusive */ true); - final TimePoint start; - if (fix.equals(fixesForPositionEstimation.getA())) { - if (getLastFixBefore(fix.getTimePoint()) == null) { - // fix is the first fix in the track; all position estimation before this point may be affected - start = new MillisecondsTimePoint(0); - } else { - start = fix.getTimePoint(); - } - } else { - if (fixesForPositionEstimation.getA() == null) { - start = new MillisecondsTimePoint(0); - } else { - start = fixesForPositionEstimation.getA().getTimePoint(); - } - } - final TimePoint end; - if (fix.equals(fixesForPositionEstimation.getB())) { - if (getFirstFixAfter(fix.getTimePoint()) == null) { - // fix is the first fix in the track; all position estimation before this point may be affected - end = new MillisecondsTimePoint(Long.MAX_VALUE); - } else { - end = fix.getTimePoint(); - } - } else { - if (fixesForPositionEstimation.getB() == null) { - end = new MillisecondsTimePoint(Long.MAX_VALUE); - } else { - end = fixesForPositionEstimation.getB().getTimePoint(); - } - } - return new Pair(start, end); - } finally { - unlockAfterRead(); - } - } - - @Override - public Position getEstimatedRawPosition(TimePoint timePoint, boolean extrapolate) { - lockForRead(); - try { - FixType lastFixAtOrBefore = getLastRawFixAtOrBefore(timePoint); - FixType firstFixAtOrAfter = getFirstRawFixAtOrAfter(timePoint); - return getEstimatedPosition(timePoint, extrapolate, lastFixAtOrBefore, firstFixAtOrAfter); - } finally { - unlockAfterRead(); - } - } - - private Position getEstimatedPosition(TimePoint timePoint, boolean extrapolate, FixType lastFixAtOrBefore, - FixType firstFixAtOrAfter) { - lockForRead(); - try { - // TODO bug #346: compute a confidence value for the position returned based on time difference between fix(es) and timePoint; consider using Taylor approximation of more fixes around timePoint to predict and weigh position - if (lastFixAtOrBefore != null && lastFixAtOrBefore == firstFixAtOrAfter) { - return lastFixAtOrBefore.getPosition(); // exact match; how unlikely is that? - } else { - if (lastFixAtOrBefore == null && firstFixAtOrAfter != null) { - // TODO shouldn't this extrapolate into the past if extrapolate==true? - return firstFixAtOrAfter.getPosition(); // asking for time point before first fix: return first fix's position - } - if (firstFixAtOrAfter == null && !extrapolate) { - return lastFixAtOrBefore == null ? null : lastFixAtOrBefore.getPosition(); - } else { - SpeedWithBearing estimatedSpeed = estimateSpeed(lastFixAtOrBefore, firstFixAtOrAfter); - if (estimatedSpeed == null) { - // TODO even if extrapolation is requested, if there is no firstFixAtOrAfter, and the GPSFixes are without speed, shouldn't this at least return the position at the lastFixAtOrBefore instead of null? - return null; - } else { - if (lastFixAtOrBefore != null) { - Distance distance = estimatedSpeed.travel(lastFixAtOrBefore.getTimePoint(), timePoint); - Position result = lastFixAtOrBefore.getPosition().translateGreatCircle( - estimatedSpeed.getBearing(), distance); - return result; - } else { - // firstFixAtOrAfter can't be null because otherwise no speed could have been estimated - // TODO shouldn't this extrapolate into the past if extrapolate==true? - return firstFixAtOrAfter.getPosition(); - } - } - } - } - } finally { - unlockAfterRead(); - } - } - - @Override - public Pair getMaximumSpeedOverGround(TimePoint from, TimePoint to) { - return maxSpeedCache.getMaxSpeed(from, to); - } - - private SpeedWithBearing estimateSpeed(FixType fix1, FixType fix2) { - if (fix1 == null) { - if (fix2 instanceof GPSFixMoving) { - return ((GPSFixMoving) fix2).getSpeed(); - } else { - return null; - } - } else if (fix2 == null) { - FixType lastBeforeFix1 = getLastFixBefore(fix1.getTimePoint()); - if (lastBeforeFix1 != null) { - fix2 = fix1; - fix1 = lastBeforeFix1; // compute speed based on the last two fixes and assume constant speed - } else { - if (fix1 instanceof GPSFixMoving) { - return ((GPSFixMoving) fix1).getSpeed(); - } else { - return null; - } - } - } - Distance distance = fix1.getPosition().getDistance(fix2.getPosition()); - long millis = Math.abs(fix1.getTimePoint().asMillis() - fix2.getTimePoint().asMillis()); - SpeedWithBearing speed = new KnotSpeedWithBearingImpl(distance.getNauticalMiles() / (millis / 1000. / 3600.), - fix1.getPosition().getBearingGreatCircle(fix2.getPosition())); - return speed; - } - - - /** - * Returns the smoothened fixes (see {@link #getInternalFixes()}), type-cast such that it's a set of {@link GPSFix} - * objects - */ - private NavigableSet getGPSFixes() { - @SuppressWarnings("unchecked") - NavigableSet result = (NavigableSet) getInternalFixes(); - return result; - } - - @Override - public Distance getDistanceTraveled(TimePoint from, TimePoint to) { - return getDistanceTraveledRecursively(from, to, 0); - } - - private Distance getDistanceTraveledRecursively(TimePoint from, TimePoint to, int recursionDepth) { - Distance result; - if (!from.before(to)) { - result = Distance.NULL; - } else { - lockForRead(); - try { - Pair> bestCacheEntry = getDistanceCache() - .getEarliestFromAndDistanceAtOrAfterFrom(from, to); - if (bestCacheEntry != null) { - // compute the missing stretches between best cache entry's "from" and our "from" and the cache - // entry's "to" and our "to" - Distance distanceFromFromToBeginningOfCacheEntry = Distance.NULL; - Distance distanceFromEndOfCacheEntryToTo = Distance.NULL; - if (!bestCacheEntry.getB().getA().equals(from)) { - assert bestCacheEntry.getB().getA().after(from); - distanceFromFromToBeginningOfCacheEntry = getDistanceTraveledRecursively(from, bestCacheEntry - .getB().getA(), recursionDepth + 1); - } - if (!bestCacheEntry.getA().equals(to)) { - assert bestCacheEntry.getA().before(to); - distanceFromEndOfCacheEntryToTo = getDistanceTraveledRecursively(bestCacheEntry.getA(), to, - recursionDepth + 1); - } - result = distanceFromFromToBeginningOfCacheEntry.add(bestCacheEntry.getB().getB()).add( - distanceFromEndOfCacheEntryToTo); - } else { - double distanceInNauticalMiles = 0; - if (from.compareTo(to) < 0) { - // getEstimatedPosition's current implementation returns a position equal to that of a fix at - // "from" if there is one with exactly that time stamp - Position fromPos = getEstimatedPosition(from, /* extrapolate */false); - if (fromPos == null) { - result = Distance.NULL; - } else { - lockForRead(); - try { - NavigableSet subset = getGPSFixes().subSet(new DummyGPSFix(from), - /* fromInclusive */false, new DummyGPSFix(to), - /* toInclusive */false); - for (GPSFix fix : subset) { - double distanceBetweenAdjacentFixesInNauticalMiles = fromPos.getDistance( - fix.getPosition()).getNauticalMiles(); - distanceInNauticalMiles += distanceBetweenAdjacentFixesInNauticalMiles; - fromPos = fix.getPosition(); - } - } finally { - unlockAfterRead(); - } - Position toPos = getEstimatedPosition(to, false); - distanceInNauticalMiles += fromPos.getDistance(toPos).getNauticalMiles(); - result = new NauticalMileDistance(distanceInNauticalMiles); - } - } else { - result = Distance.NULL; - } - } - } finally { - unlockAfterRead(); - } - getDistanceCache().cache(from, to, result); - } - return result; - } - - @Override - public Distance getRawDistanceTraveled(TimePoint from, TimePoint to) { - lockForRead(); - try { - double distanceInNauticalMiles = 0; - if (from.compareTo(to) < 0) { - Position fromPos = getEstimatedRawPosition(from, false); - if (fromPos == null) { - return Distance.NULL; - } - @SuppressWarnings("unchecked") - NavigableSet subset = (NavigableSet) getInternalRawFixes().subSet((FixType) new DummyGPSFix(from), - /* fromInclusive */false, (FixType) new DummyGPSFix(to), - /* toInclusive */false); - for (GPSFix fix : subset) { - distanceInNauticalMiles += fromPos.getDistance(fix.getPosition()).getNauticalMiles(); - fromPos = fix.getPosition(); - } - Position toPos = getEstimatedRawPosition(to, false); - distanceInNauticalMiles += fromPos.getDistance(toPos).getNauticalMiles(); - return new NauticalMileDistance(distanceInNauticalMiles); - } else { - return Distance.NULL; - } - } finally { - unlockAfterRead(); - } - } - - /** - * Here we know for sure that the GPS fixes are {@link GPSFixMoving} instances, - * so we can use their {@link GPSFixMoving#getSpeed() speed} in averaging. We're still - * using an interval of {@link #getMillisecondsOverWhichToAverage()} around at, - * but this time we add the speeds and bearings provided by the fix onto the values for - * averaging, so the result considers both, the GPS-provided speeds and bearings as well as - * the speeds/bearings determined by distance/time difference of the fixes themselves. - */ - @Override - public SpeedWithBearing getEstimatedSpeed(TimePoint at) { - lockForRead(); - try { - SpeedWithBearingWithConfidence estimatedSpeed = getEstimatedSpeed(at, getInternalFixes(), - ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( - // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero - getMillisecondsOverWhichToAverageSpeed()/2, /* minimumConfidence */ 0.00000001)); // half confidence if half averaging interval apart - return estimatedSpeed == null ? null : estimatedSpeed.getObject(); - } finally { - unlockAfterRead(); - } - } - - @Override - public SpeedWithBearing getRawEstimatedSpeed(TimePoint at) { - lockForRead(); - try { - return getEstimatedSpeed(at, getRawFixes(), - ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( - // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero - getMillisecondsOverWhichToAverageSpeed(), /* minimumConfidence */ 0.00000001)).getObject(); - } finally { - unlockAfterRead(); - } - } - - @Override - public SpeedWithBearingWithConfidence getEstimatedSpeed(TimePoint at, Weigher weigher) { - lockForRead(); - try { - return getEstimatedSpeed(at, getInternalFixes(), weigher); - } finally { - unlockAfterRead(); - } - } - - @Override - public SpeedWithBearingWithConfidence getRawEstimatedSpeed(TimePoint at, Weigher weigher) { - lockForRead(); - try { - return getEstimatedSpeed(at, getRawFixes(), weigher); - } finally { - unlockAfterRead(); - } - } - - /** - * Since we don't know for sure whether the GPS fixes are {@link GPSFixMoving} instances, here we only estimate - * speed based on the distance and time between the fixes, averaged over an interval of - * {@link #millisecondsOverWhichToAverage} milliseconds around at. Subclasses that know about the - * particular fix type may redefine this to exploit a {@link SpeedWithBearing} attached, e.g., to a - * {@link GPSFixMoving}. - * - * @param weigher - * If null, a confidence of 1.0 is assumed for all fixes used in the speed/bearing - * estimation. Otherwise, the confidence of each fix contributing to the estimation is computed using the - * weigher. - * @return a speed/bearing with the average confidence attributed to the fixes that contributed to the estimation - */ - protected SpeedWithBearingWithConfidence getEstimatedSpeed(TimePoint at, - NavigableSet fixesToUseForSpeedEstimation, Weigher weigher) { - lockForRead(); - try { - List relevantFixes = getFixesRelevantForSpeedEstimation(at, fixesToUseForSpeedEstimation); - List> speeds = new ArrayList>(); - BearingWithConfidenceCluster bearingCluster = new BearingWithConfidenceCluster(weigher); - FixType last = null; - for (FixType next : relevantFixes) { - if (last != null) { - // TODO bug #346: consider time difference between next.getTimepoint() and at to compute a confidence - aggregateSpeedAndBearingFromLastToNext(speeds, bearingCluster, last, next); - } - last = next; - } - ConfidenceBasedAverager speedAverager = ConfidenceFactory.INSTANCE.createAverager(weigher); - HasConfidence speedWithConfidence = speedAverager.getAverage(speeds, at); - BearingWithConfidence bearingAverage = bearingCluster.getAverage(at); - Bearing bearing = bearingAverage == null ? null : bearingAverage.getObject(); - SpeedWithBearing avgSpeed = (speedWithConfidence == null || bearing == null) ? null : - new KnotSpeedWithBearingImpl(speedWithConfidence.getObject().getKnots(), bearing); - SpeedWithBearingWithConfidence result = avgSpeed == null ? null : - new SpeedWithBearingWithConfidenceImpl(avgSpeed, (bearingAverage.getConfidence() + speedWithConfidence.getConfidence())/2., at); - return result; - } finally { - unlockAfterRead(); - } - } - - protected void aggregateSpeedAndBearingFromLastToNext(List> speeds, - BearingWithConfidenceCluster bearingCluster, GPSFix last, GPSFix next) { - MillisecondsTimePoint relativeTo = new MillisecondsTimePoint((last.getTimePoint().asMillis() + next.getTimePoint().asMillis())/2); - Speed speed = last.getPosition().getDistance(next.getPosition()) - .inTime(next.getTimePoint().asMillis() - last.getTimePoint().asMillis()); - SpeedWithConfidenceImpl speedWithConfidence = new SpeedWithConfidenceImpl(speed, /* original confidence */ - 0.9, relativeTo); - speeds.add(speedWithConfidence); - double bearingConfidence = 0.9; - if (speed.getKnots() < 0.01) { - bearingConfidence = 0; - } - bearingCluster.add(new BearingWithConfidenceImpl(last.getPosition().getBearingGreatCircle(next.getPosition()), - bearingConfidence, // TODO use number of tracked satellites to determine confidence of single fix - relativeTo)); - } - - /** - * Computes the time interval such that {@link #getFixesRelevantForSpeedEstimation(TimePoint, NavigableSet)}, when - * passed any time point from that interval, produces "fix" as part of its result array. The algorithm for - * determining this interval co-varies with the implementation of - * {@link #getFixesRelevantForSpeedEstimation(TimePoint, NavigableSet)}.

- * - * This implementation looks for fixes {@link #getMillisecondsOverWhichToAverage()}/2 before and after fix. - * If no fix is found in a direction within half the averaging interval but there is a fix in that direction which is further - * apart, the yet next fix is checked. If there is none, or that next-next fix is further apart from the next fix than fix, - * then the next fix is added to the result interval. This is because for that next fix, fix in that case will be relevant - * for speed estimation because it's the closest fix. - */ - protected Pair getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(FixType fix) { - TimePoint intervalStart = null; - TimePoint intervalEnd = null; - lockForRead(); - try { - NavigableSet beforeSet = getInternalRawFixes().headSet(fix, /* inclusive */false); - NavigableSet afterSet = getInternalRawFixes().tailSet(fix, /* inclusive */true); - FixType beforeFix = null; - Iterator beforeFixIter = beforeSet.descendingIterator(); - while (beforeFixIter.hasNext() - && fix.getTimePoint().asMillis() - (beforeFix = beforeFixIter.next()).getTimePoint().asMillis() < getMillisecondsOverWhichToAverage() / 2) { - intervalStart = beforeFix.getTimePoint(); - } - if (intervalStart == null) { - if (beforeFixIter.hasNext()) { - // No before fix within half averaging interval, but there is one further away; is its next neighbour even further away? - // If so, or no more neighbours are found, it's affected and marks the invalidation interval start; otherwise, fix' - // time point is the invalidation interval start - TimePoint intervalStartCandidate = beforeFixIter.next().getTimePoint(); - if (beforeFixIter.hasNext()) { - TimePoint nextNeighboursTimePoint = beforeFixIter.next().getTimePoint(); - if (intervalStartCandidate.asMillis()-nextNeighboursTimePoint.asMillis() > fix.getTimePoint().asMillis()-intervalStartCandidate.asMillis()) { - intervalStart = intervalStartCandidate; - } - } else { - intervalStart = intervalStartCandidate; - } - } - } - if (intervalStart == null) { - intervalStart = fix.getTimePoint(); - } - FixType afterFix = null; - Iterator afterFixIter = afterSet.iterator(); - while (afterFixIter.hasNext() - && (afterFix = afterFixIter.next()).getTimePoint().asMillis() - fix.getTimePoint().asMillis() < getMillisecondsOverWhichToAverage() / 2) { - intervalEnd = afterFix.getTimePoint(); - } - if (intervalEnd == null) { - if (afterFixIter.hasNext()) { - // No after fix within half averaging interval, but there is one further away; is its next neighbour even further away? - // If so, or no more neighbours are found, it's affected and marks the invalidation interval start; otherwise, fix's - // time point is the invalidation interval end - TimePoint intervalEndCandidate = afterFixIter.next().getTimePoint(); - if (afterFixIter.hasNext()) { - TimePoint nextNeighboursTimePoint = afterFixIter.next().getTimePoint(); - if (nextNeighboursTimePoint.asMillis()-intervalEndCandidate.asMillis() > intervalEndCandidate.asMillis()-fix.getTimePoint().asMillis()) { - intervalEnd = intervalEndCandidate; - } - } else { - intervalEnd = intervalEndCandidate; - } - } - } - if (intervalEnd == null) { - intervalEnd = fix.getTimePoint(); - } - } finally { - unlockAfterRead(); - } - return new Pair(intervalStart, intervalEnd); - } - - protected FixType createDummyGPSFix(TimePoint at) { - @SuppressWarnings("unchecked") - FixType result = (FixType) new DummyGPSFix(at); - return result; - } - - /** - * A track that doesn't have {@link GPSFixMoving} fixes and therefore needs to compute the speed using the fix time - * and position differences. It therefore needs at least two fixes to compute a result. The algorithm always uses - * at least the closest existing fix later and the closet existing fix earlier than at, if available. - *

- * - * But even for a track with {@link GPSFixMoving} fixes this is a good algorithm because in case the speed changes - * significantly between fixes, it is important to know the next fix to understand and consider the trend. - */ - protected List getFixesRelevantForSpeedEstimation(TimePoint at, NavigableSet fixesToUseForSpeedEstimation) { - lockForRead(); - try { - FixType atTimed = createDummyGPSFix(at); - List relevantFixes = new LinkedList(); - NavigableSet beforeSet = fixesToUseForSpeedEstimation.headSet(atTimed, /* inclusive */false); - NavigableSet afterSet = fixesToUseForSpeedEstimation.tailSet(atTimed, /* inclusive */true); - FixType beforeFix = null; - Iterator beforeFixIter = beforeSet.descendingIterator(); - boolean noBeforeFixUsedYet = true; - while (beforeFixIter.hasNext() && - (at.asMillis() - (beforeFix=beforeFixIter.next()).getTimePoint().asMillis() < getMillisecondsOverWhichToAverage() / 2 || noBeforeFixUsedYet)) { - relevantFixes.add(0, beforeFix); - noBeforeFixUsedYet = false; - beforeFix = null; // mark the fix as used - } - FixType afterFix = null; - Iterator afterFixIter = afterSet.iterator(); - boolean noAfterFixUsedYet = true; - while (afterFixIter.hasNext() && - ((afterFix=afterFixIter.next()).getTimePoint().asMillis() - at.asMillis() < getMillisecondsOverWhichToAverage() / 2 || noAfterFixUsedYet)) { - relevantFixes.add(afterFix); - noAfterFixUsedYet = false; - afterFix = null; // mark the fix as used - } - // now fill up relevantFixes until we have at least two fixes or we run out of fixes entirely (can't estimate speed - // with this type of fix on a track with less than two fixes) - while (relevantFixes.size() < 2 && (beforeFix != null || afterFix != null)) { - if (afterFix == null) { - if (beforeFix != null) { - relevantFixes.add(0, beforeFix); // add the last beforeFix to have at least two fixes, although outside of averaging interval - if (beforeFixIter.hasNext()) { - beforeFix = beforeFixIter.next(); - } else { - beforeFix = null; - } - } - } else if (beforeFix == null) { - relevantFixes.add(afterFix); - if (afterFixIter.hasNext()) { - afterFix = afterFixIter.next(); - } else { - afterFix = null; - } - } else { - // both, beforeFix and afterFix are available; choose the one closest to "at" - if (afterFix.getTimePoint().asMillis()-at.asMillis() < beforeFix.getTimePoint().asMillis()-at.asMillis()) { - relevantFixes.add(afterFix); - if (afterFixIter.hasNext()) { - afterFix = afterFixIter.next(); - } else { - afterFix = null; - } - } else { - relevantFixes.add(0, beforeFix); - if (beforeFixIter.hasNext()) { - beforeFix = beforeFixIter.next(); - } else { - beforeFix = null; - } - } - } - } - return relevantFixes; - } finally { - unlockAfterRead(); - } - } - - protected long getMillisecondsOverWhichToAverage() { - return millisecondsOverWhichToAverage; - } - - /** - * Smoothens the track based on a max-speed assumption. - */ - @Override - protected NavigableSet getInternalFixes() { - assertReadLock(); - return new PartialNavigableSetView(super.getInternalFixes()) { - @Override - protected boolean isValid(FixType e) { - return GPSFixTrackImpl.this.isValid(getRawFixes(), e); - } - }; - } - - /** - * When redefining this method, make sure to redefine {@link #invalidateValidityAndDistanceCaches(GPSFix)} - * accordingly. This implementation checks the immediate previous and next fix for e. Therefore, when - * adding a fix, only immediately adjacent fix's validity caches need to be invalidated. - *

- * - * The fix e is considered valid if at least one of its (not necessarily immediate) neighbors that is - * within the range of {@link #getMillisecondsOverWhichToAverageSpeed()} is in reach with less than - * {@link #maxSpeedForSmoothing}, or if there are no neighbors with the time range defined by - * {@link #getMillisecondsOverWhichToAverageSpeed()}. - */ - protected boolean isValid(NavigableSet rawFixes, FixType e) { - assertReadLock(); - boolean isValid; - if (maxSpeedForSmoothing == null) { - isValid = true; - } else { - if (e.isValidityCached()) { - isValid = e.isValid(); - } else { - FixType previous = rawFixes.lower(e); - final boolean atLeastOnePreviousFixInRange = previous != null && e.getTimePoint().asMillis() - previous.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed(); - Speed speedToPrevious = null; - boolean foundValidPreviousFixInRange = false; - while (previous != null && !foundValidPreviousFixInRange && e.getTimePoint().asMillis() - previous.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed()) { - speedToPrevious = previous.getPosition().getDistance(e.getPosition()) - .inTime(e.getTimePoint().asMillis() - previous.getTimePoint().asMillis()); - foundValidPreviousFixInRange = speedToPrevious.compareTo(maxSpeedForSmoothing) <= 0; - previous = rawFixes.lower(previous); - } - boolean foundValidNextFixInRange = false; - boolean atLeastOneNextFixInRange = false; - // only spend the effort to calculate the "next"-related predicate if the "previous"-related part of the disjunction below isn't already false - if (!atLeastOnePreviousFixInRange || foundValidPreviousFixInRange) { - FixType next = rawFixes.higher(e); - atLeastOneNextFixInRange = next != null && next.getTimePoint().asMillis() - e.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed(); - Speed speedToNext = null; - while (next != null && !foundValidNextFixInRange && next.getTimePoint().asMillis() - e.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed()) { - speedToNext = e.getPosition().getDistance(next.getPosition()) - .inTime(next.getTimePoint().asMillis() - e.getTimePoint().asMillis()); - foundValidNextFixInRange = speedToNext.compareTo(maxSpeedForSmoothing) <= 0; - next = rawFixes.higher(next); - } - } - isValid = (!atLeastOnePreviousFixInRange || foundValidPreviousFixInRange) && (!atLeastOneNextFixInRange || foundValidNextFixInRange); - e.cacheValidity(isValid); - } - } - return isValid; - } - - /** - * After gpsFix was added to this track, invalidate the {@link WithValidityCache validity caches} - * of the fixes whose validity may be affected. If subclasses redefine {@link #isValid(PartialNavigableSetView, GPSFix)}, - * they must make sure that this method is redefined accordingly. Here, {@link #getMillisecondsOverWhichToAverageSpeed()} - * before and after the fix all fixes' validity caches are reset.

- * - * Distance cache invalidation is a bit tricky. Usually, the distance cache is invalidated starting with the time point - * of the gpsFix "upwards." However, if the adjacent earlier fixes have changed their validity by the addition - * of gpsFix, the distance cache must be invalidated starting with the first fix whose validity changed. - */ - protected void invalidateValidityAndDistanceCaches(FixType gpsFix) { - assertWriteLock(); - TimePoint distanceCacheInvalidationStart = gpsFix.getTimePoint(); - // see also bug 968: cache entries for intervals ending after the last fix need to be removed because they are - // based on the last fix's position and don't extrapolate; now, with the new gpsFix, the positions between the last - // and the new fix are defined by interpolation and hence differ from the previous assumption the competitor would have - // stopped moving at the last fix. - FixType last = getInternalFixes().lower(gpsFix); - if (last != null) { - distanceCacheInvalidationStart = last.getTimePoint().plus(1); // add one millisecond to invalidate *after* the last fix only - } - gpsFix.invalidateCache(); - Iterable lowers = getEarlierFixesWhoseValidityMayBeAffected(gpsFix); - for (FixType lower : lowers) { - boolean lowerWasValid = isValid(getRawFixes(), lower); - lower.invalidateCache(); - boolean lowerIsValid = isValid(getRawFixes(), lower); - if (lowerIsValid != lowerWasValid && lower.getTimePoint().before(distanceCacheInvalidationStart)) { - distanceCacheInvalidationStart = lower.getTimePoint(); - } - } - getDistanceCache().invalidateAllAtOrLaterThan(distanceCacheInvalidationStart); - Iterable highers = getLaterFixesWhoseValidityMayBeAffected(gpsFix); - for (FixType higher : highers) { - higher.invalidateCache(); - } - } - - protected Iterable getLaterFixesWhoseValidityMayBeAffected(FixType gpsFix) { - FixType higher = getInternalRawFixes().higher(gpsFix); - if (higher == null) { - return Collections.emptySet(); - } else { - Collection result = new ArrayList(); - while (higher != null && higher.getTimePoint().asMillis() - gpsFix.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed()) { - result.add(higher); - higher = getInternalRawFixes().higher(higher); - } - return result; - } - } - - protected Iterable getEarlierFixesWhoseValidityMayBeAffected(FixType gpsFix) { - FixType lower = getInternalRawFixes().lower(gpsFix); - if (lower == null) { - return Collections.emptySet(); - } else { - Collection result = new ArrayList(); - while (lower != null && gpsFix.getTimePoint().asMillis() - lower.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed()) { - result.add(lower); - lower = getInternalRawFixes().lower(lower); - } - return result; - } - } - - @Override - public boolean hasDirectionChange(TimePoint at, double minimumDegreeDifference) { - lockForRead(); - try { - boolean result = false; - TimePoint start = new MillisecondsTimePoint(at.asMillis() - getMillisecondsOverWhichToAverageSpeed()); - TimePoint end = new MillisecondsTimePoint(at.asMillis() + getMillisecondsOverWhichToAverageSpeed()); - SpeedWithBearing estimatedSpeedAtStart = getEstimatedSpeed(start); - if (estimatedSpeedAtStart != null) { - Bearing bearingAtStart = estimatedSpeedAtStart.getBearing(); - TimePoint next = new MillisecondsTimePoint(start.asMillis() - + Math.max(1000l, getMillisecondsOverWhichToAverageSpeed() / 2)); - while (!result && next.compareTo(end) <= 0) { - SpeedWithBearing estimatedSpeedAtNext = getEstimatedSpeed(next); - if (estimatedSpeedAtNext != null) { - Bearing bearingAtEnd = estimatedSpeedAtNext.getBearing(); - result = Math.abs(bearingAtStart.getDifferenceTo(bearingAtEnd).getDegrees()) > minimumDegreeDifference; - } - next = new MillisecondsTimePoint(next.asMillis() - + Math.max(1000l, getMillisecondsOverWhichToAverageSpeed() / 2)); - } - } - return result; - } finally { - unlockAfterRead(); - } - } - - protected DistanceCache getDistanceCache() { - return distanceCache; - } - - @Override - protected boolean add(FixType fix) { - final boolean result; - lockForWrite(); - try { - result = super.add(fix); - invalidateValidityAndDistanceCaches(fix); - } finally { - unlockAfterWrite(); - } - if (logger.isLoggable(Level.FINEST)) { - FixType last; - try { - lockForRead(); - if (logger.isLoggable(Level.FINEST)) { - logger.finest("GPS fix "+fix+" for "+getTrackedItem()+", isValid="+isValid(getInternalRawFixes(), fix)+ - ", time/distance/speed from last: "+ - ((last=getInternalRawFixes().lower(fix))==null - ? "null" - : (fix.getTimePoint().asMillis()-last.getTimePoint().asMillis()+"ms/"+ - fix.getPosition().getDistance(last.getPosition())) + "/"+ - fix.getPosition().getDistance(last.getPosition()).inTime(fix.getTimePoint().asMillis()-last.getTimePoint().asMillis()))); - } - } finally { - unlockAfterRead(); - } - } - for (GPSTrackListener listener : getListeners()) { - listener.gpsFixReceived(fix, getTrackedItem()); - } - return result; - } - - protected void setMillisecondsOverWhichToAverage(long millisecondsOverWhichToAverage) { - long oldMillis = getMillisecondsOverWhichToAverage(); - this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage; - for (GPSTrackListener listener : getListeners()) { - listener.speedAveragingChanged(oldMillis, millisecondsOverWhichToAverage); - } - } -} +package com.sap.sailing.domain.tracking.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NavigableSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BearingWithConfidence; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.base.SpeedWithConfidence; +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; +import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; +import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.NauticalMileDistance; +import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; +import com.sap.sailing.domain.confidence.ConfidenceFactory; +import com.sap.sailing.domain.confidence.HasConfidence; +import com.sap.sailing.domain.confidence.Weigher; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.GPSTrackListener; +import com.sap.sailing.domain.tracking.WithValidityCache; +import com.sap.sailing.util.impl.ArrayListNavigableSet; +import com.sap.sse.common.Util; + +public class GPSFixTrackImpl extends TrackImpl implements GPSFixTrack { + private static final Logger logger = Logger.getLogger(GPSFixTrackImpl.class.getName()); + private static final long serialVersionUID = -7282869695818293745L; + private static final Speed DEFAULT_MAX_SPEED_FOR_SMOOTHING = new KnotSpeedImpl(50); + protected final Speed maxSpeedForSmoothing; + + private final ItemType trackedItem; + private long millisecondsOverWhichToAverage; + + private final GPSTrackListeners listeners; + + + private static class GPSTrackListeners implements Serializable { + private static final long serialVersionUID = -7117842092078781722L; + private Set> listeners; + + public GPSTrackListeners() { + listeners = new HashSet<>(); + } + + @SuppressWarnings("unchecked") // need typed generic cast + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + listeners = (Set>) ois.readObject(); + } + + private void writeObject(ObjectOutputStream oos) throws IOException { + final Set> listenersToSerialize; + synchronized (listeners) { + listenersToSerialize = new HashSet<>(); + for (GPSTrackListener listener : listeners) { + if (!listener.isTransient()) { + listenersToSerialize.add(listener); + } + } + } + oos.writeObject(listenersToSerialize); + } + + public void addListener(GPSTrackListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + public void removeListener(GPSTrackListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + /** + * To iterate over the resulting listener list, synchronize on the iterable returned. Only this will avoid + * {@link ConcurrentModificationException}s because listeners may be added on the fly, and this object will + * synchronize on the listeners collection before adding on. + */ + public Iterable> getListeners() { + synchronized (listeners) { + return new HashSet>(listeners); + } + } + } + + /** + * Computing {@link #getDistanceTraveled(TimePoint, TimePoint)} is more expensive the longer the track is and the + * higher the tracking frequency. It is called regularly, usually for the same start time. Keeping to re-evaluate + * all distances between all GPS fixes, even if they remain constant, is a waste of resources, particularly when we + * assume that usually GPS fixes are appended to a track and not usually randomly inserted, even though this can + * happen. + *

+ * + * This cache looks "backwards." It contains pairs whose first component represents a to parameter used + * in {@link #getDistanceTraveled(TimePoint, TimePoint)}. It is ordered by this component. The second component is a + * navigable, ordered set of pairs where the first pair component represents a from parameter used in + * {@link #getDistanceTraveled(TimePoint, TimePoint)} and the second pair component represents the result of + * {@link #getDistanceTraveled(TimePoint, TimePoint)} for this parameter combination. Note that the cache does only + * store distances for from time points actually passed to + * {@link #getDistanceTraveled(TimePoint, TimePoint)} and does not cache all interim combinations collected while + * computing a result. As such, it differs from a typical "dynamic programming" algorithm which would store all + * in-between values too. Assuming that from values are sparse (leg start, race start, ...), there + * doesn't seem to be much use in storing all the other in-between values too. + *

+ * + * For implementation efficiency in combination with using a {@link ArrayListNavigableSet} for the values and in + * order to be able to efficiently extend a cache entry for a single to fix, the navigable sets + * containing the from fixes and distances are ordered such that earlier fixes come later in the set. + * This way, extending the cache entry for a to fix to an earlier from fix only requires + * appending to the set. + *

+ * + * Invalidation: When a new fix is added to this track, all distance cache entries for fixes at or later than + * the new fix's time point are removed from this cache. Additionally, the fix insertion may have an impact on the + * {@link #getEarlierFixesWhoseValidityMayBeAffected(GPSFix) previous fix's} validity (track smoothing) and + * therefore on its selection for distance aggregation. Therefore, if fix addition turned the previous fix invalid, + * the cache entries for the time points at or after the previous fix also need to be removed. + *

+ * + * Cache use: When {@link #getDistanceTraveled(TimePoint, TimePoint)} is called, it first looks for a cache + * entry for the to parameter. If one is found, the earliest entry in the navigable set for the + * navigable set of from and distance values that is at or after the requested from time + * point is determined. If such an entry exists, the distance is remembered and the algorithm is repeated + * recursively, using the from value found in the cache as the new to value, and the + * from value originally passed to {@link #getDistanceTraveled(TimePoint, TimePoint)} as + * from again. If no entry is found in the cache entry for to that is at or after the + * requested from time, the distance is computed by iterating the smoothened fixes. + *

+ * + * If a cache entry for to is not found, the latest cache entry before it is looked up. If one is + * found, the distance between the to time point requested and the to time point found in + * the cache is computed by iterating the smoothened fixes for this interval. If none is found, the distance is + * computed by iterating backwards all the way to from. + *

+ * + * Once the {@link #getDistanceTraveled(TimePoint, TimePoint)} has computed its value, it adds the result to the + * cache. + */ + private transient DistanceCache distanceCache; + + private transient MaxSpeedCache maxSpeedCache; + + public GPSFixTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage) { + this(trackedItem, millisecondsOverWhichToAverage, DEFAULT_MAX_SPEED_FOR_SMOOTHING); + } + + /** + * @param maxSpeedForSmoothening + * if null, any fix speed will be accepted as long as fix-provided speed (if any) and + * inferred speed can be matched. + */ + public GPSFixTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage, Speed maxSpeedForSmoothening) { + super(/* nameForReadWriteLock */ GPSFixTrackImpl.class.getSimpleName()+(trackedItem==null?"":(" for "+trackedItem.toString()))); + this.trackedItem = trackedItem; + this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage; + this.maxSpeedForSmoothing = maxSpeedForSmoothening; + this.listeners = new GPSTrackListeners(); + this.distanceCache = new DistanceCache(trackedItem==null?"null":trackedItem.toString()); + this.maxSpeedCache = new MaxSpeedCache(this); + } + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + distanceCache = new DistanceCache(trackedItem.toString()); + maxSpeedCache = new MaxSpeedCache(this); + } + + @Override + public String toString() { + return super.toString()+" for "+getTrackedItem(); + } + + @Override + public void addListener(GPSTrackListener listener) { + listeners.addListener(listener); + } + + @Override + public void removeListener(GPSTrackListener listener) { + listeners.removeListener(listener); + } + + protected Iterable> getListeners() { + return listeners.getListeners(); + } + + private class DummyGPSFix extends DummyTimed implements GPSFix { + private static final long serialVersionUID = -6258506654181816698L; + + public DummyGPSFix(TimePoint timePoint) { + super(timePoint); + } + @Override + public Position getPosition() { + return null; + } + @Override + public SpeedWithBearing getSpeedAndBearingRequiredToReach(GPSFix to) { + return null; + } + @Override + public boolean isValidityCached() { + return false; + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public void invalidateCache() { + } + + @Override + public void cacheValidity(boolean isValid) { + } + } + + + @Override + protected FixType getDummyFix(TimePoint timePoint) { + @SuppressWarnings("unchecked") + FixType result = (FixType) new DummyGPSFix(timePoint); + return result; + } + + @Override + public ItemType getTrackedItem() { + return trackedItem; + } + + @Override + public long getMillisecondsOverWhichToAverageSpeed() { + return millisecondsOverWhichToAverage; + } + + private Util.Pair getFixesForPositionEstimation(TimePoint timePoint, boolean inclusive) { + lockForRead(); + try { + FixType lastFixBefore = inclusive ? getLastFixAtOrBefore(timePoint) : getLastFixBefore(timePoint); + FixType firstFixAfter = inclusive ? getFirstFixAtOrAfter(timePoint) : getFirstFixAfter(timePoint); + return new Util.Pair(lastFixBefore, firstFixAfter); + } finally { + unlockAfterRead(); + } + } + + private class EstimatedPositionIterator implements Iterator { + private final Iterator timedsIter; + private final boolean extrapolate; + private final NavigableSet fixes; + private Iterator subSetIterator; + private FixType earlierFix; + private FixType laterFix; // if this is null, earlierFix is also null + + public EstimatedPositionIterator(Iterable timeds, boolean extrapolate) { + this.timedsIter = timeds.iterator(); + this.extrapolate = extrapolate; + this.fixes = getFixes(); + } + + @Override + public boolean hasNext() { + return timedsIter.hasNext(); + } + + @Override + public Position next() { + TimePoint nextTimePoint = timedsIter.next().getTimePoint(); + if (subSetIterator == null) { + earlierFix = getLastFixAtOrBefore(nextTimePoint); + subSetIterator = fixes.subSet(createDummyGPSFix(nextTimePoint), /* fromInclusive */true, fixes.last(), /* toInclusive */ + true).iterator(); + laterFix = subSetIterator.hasNext() ? subSetIterator.next() : null; + } else { + while (laterFix != null && laterFix.getTimePoint().before(nextTimePoint)) { + earlierFix = laterFix; + laterFix = subSetIterator.hasNext() ? subSetIterator.next() : null; + } + } + return getEstimatedPosition(nextTimePoint, extrapolate, earlierFix, laterFix); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + @Override + public Iterator getEstimatedPositions(Iterable timeds, boolean extrapolate) { + return new EstimatedPositionIterator(timeds, extrapolate); + } + + @Override + public Position getEstimatedPosition(TimePoint timePoint, boolean extrapolate) { + lockForRead(); + try { + Util.Pair fixesForPositionEstimation = getFixesForPositionEstimation(timePoint, /* inclusive */ true); + return getEstimatedPosition(timePoint, extrapolate, fixesForPositionEstimation.getA(), fixesForPositionEstimation.getB()); + } finally { + unlockAfterRead(); + } + } + + @Override + public Util.Pair getEstimatedPositionTimePeriodAffectedBy(GPSFix fix) { + if (fix == null) { + throw new IllegalArgumentException("fix must not be null"); + } + lockForRead(); + try { + Util.Pair fixesForPositionEstimation = getFixesForPositionEstimation(fix.getTimePoint(), /* inclusive */ true); + final TimePoint start; + if (fix.equals(fixesForPositionEstimation.getA())) { + if (getLastFixBefore(fix.getTimePoint()) == null) { + // fix is the first fix in the track; all position estimation before this point may be affected + start = new MillisecondsTimePoint(0); + } else { + start = fix.getTimePoint(); + } + } else { + if (fixesForPositionEstimation.getA() == null) { + start = new MillisecondsTimePoint(0); + } else { + start = fixesForPositionEstimation.getA().getTimePoint(); + } + } + final TimePoint end; + if (fix.equals(fixesForPositionEstimation.getB())) { + if (getFirstFixAfter(fix.getTimePoint()) == null) { + // fix is the first fix in the track; all position estimation before this point may be affected + end = new MillisecondsTimePoint(Long.MAX_VALUE); + } else { + end = fix.getTimePoint(); + } + } else { + if (fixesForPositionEstimation.getB() == null) { + end = new MillisecondsTimePoint(Long.MAX_VALUE); + } else { + end = fixesForPositionEstimation.getB().getTimePoint(); + } + } + return new Util.Pair(start, end); + } finally { + unlockAfterRead(); + } + } + + @Override + public Position getEstimatedRawPosition(TimePoint timePoint, boolean extrapolate) { + lockForRead(); + try { + FixType lastFixAtOrBefore = getLastRawFixAtOrBefore(timePoint); + FixType firstFixAtOrAfter = getFirstRawFixAtOrAfter(timePoint); + return getEstimatedPosition(timePoint, extrapolate, lastFixAtOrBefore, firstFixAtOrAfter); + } finally { + unlockAfterRead(); + } + } + + private Position getEstimatedPosition(TimePoint timePoint, boolean extrapolate, FixType lastFixAtOrBefore, + FixType firstFixAtOrAfter) { + lockForRead(); + try { + // TODO bug #346: compute a confidence value for the position returned based on time difference between fix(es) and timePoint; consider using Taylor approximation of more fixes around timePoint to predict and weigh position + if (lastFixAtOrBefore != null && lastFixAtOrBefore == firstFixAtOrAfter) { + return lastFixAtOrBefore.getPosition(); // exact match; how unlikely is that? + } else { + if (lastFixAtOrBefore == null && firstFixAtOrAfter != null) { + // TODO shouldn't this extrapolate into the past if extrapolate==true? + return firstFixAtOrAfter.getPosition(); // asking for time point before first fix: return first fix's position + } + if (firstFixAtOrAfter == null && !extrapolate) { + return lastFixAtOrBefore == null ? null : lastFixAtOrBefore.getPosition(); + } else { + SpeedWithBearing estimatedSpeed = estimateSpeed(lastFixAtOrBefore, firstFixAtOrAfter); + if (estimatedSpeed == null) { + // TODO even if extrapolation is requested, if there is no firstFixAtOrAfter, and the GPSFixes are without speed, shouldn't this at least return the position at the lastFixAtOrBefore instead of null? + return null; + } else { + if (lastFixAtOrBefore != null) { + Distance distance = estimatedSpeed.travel(lastFixAtOrBefore.getTimePoint(), timePoint); + Position result = lastFixAtOrBefore.getPosition().translateGreatCircle( + estimatedSpeed.getBearing(), distance); + return result; + } else { + // firstFixAtOrAfter can't be null because otherwise no speed could have been estimated + // TODO shouldn't this extrapolate into the past if extrapolate==true? + return firstFixAtOrAfter.getPosition(); + } + } + } + } + } finally { + unlockAfterRead(); + } + } + + @Override + public Util.Pair getMaximumSpeedOverGround(TimePoint from, TimePoint to) { + return maxSpeedCache.getMaxSpeed(from, to); + } + + private SpeedWithBearing estimateSpeed(FixType fix1, FixType fix2) { + if (fix1 == null) { + if (fix2 instanceof GPSFixMoving) { + return ((GPSFixMoving) fix2).getSpeed(); + } else { + return null; + } + } else if (fix2 == null) { + FixType lastBeforeFix1 = getLastFixBefore(fix1.getTimePoint()); + if (lastBeforeFix1 != null) { + fix2 = fix1; + fix1 = lastBeforeFix1; // compute speed based on the last two fixes and assume constant speed + } else { + if (fix1 instanceof GPSFixMoving) { + return ((GPSFixMoving) fix1).getSpeed(); + } else { + return null; + } + } + } + Distance distance = fix1.getPosition().getDistance(fix2.getPosition()); + long millis = Math.abs(fix1.getTimePoint().asMillis() - fix2.getTimePoint().asMillis()); + SpeedWithBearing speed = new KnotSpeedWithBearingImpl(distance.getNauticalMiles() / (millis / 1000. / 3600.), + fix1.getPosition().getBearingGreatCircle(fix2.getPosition())); + return speed; + } + + + /** + * Returns the smoothened fixes (see {@link #getInternalFixes()}), type-cast such that it's a set of {@link GPSFix} + * objects + */ + private NavigableSet getGPSFixes() { + @SuppressWarnings("unchecked") + NavigableSet result = (NavigableSet) getInternalFixes(); + return result; + } + + @Override + public Distance getDistanceTraveled(TimePoint from, TimePoint to) { + return getDistanceTraveledRecursively(from, to, 0); + } + + private Distance getDistanceTraveledRecursively(TimePoint from, TimePoint to, int recursionDepth) { + Distance result; + if (!from.before(to)) { + result = Distance.NULL; + } else { + lockForRead(); + try { + Util.Pair> bestCacheEntry = getDistanceCache() + .getEarliestFromAndDistanceAtOrAfterFrom(from, to); + if (bestCacheEntry != null) { + // compute the missing stretches between best cache entry's "from" and our "from" and the cache + // entry's "to" and our "to" + Distance distanceFromFromToBeginningOfCacheEntry = Distance.NULL; + Distance distanceFromEndOfCacheEntryToTo = Distance.NULL; + if (!bestCacheEntry.getB().getA().equals(from)) { + assert bestCacheEntry.getB().getA().after(from); + distanceFromFromToBeginningOfCacheEntry = getDistanceTraveledRecursively(from, bestCacheEntry + .getB().getA(), recursionDepth + 1); + } + if (!bestCacheEntry.getA().equals(to)) { + assert bestCacheEntry.getA().before(to); + distanceFromEndOfCacheEntryToTo = getDistanceTraveledRecursively(bestCacheEntry.getA(), to, + recursionDepth + 1); + } + result = distanceFromFromToBeginningOfCacheEntry.add(bestCacheEntry.getB().getB()).add( + distanceFromEndOfCacheEntryToTo); + } else { + double distanceInNauticalMiles = 0; + if (from.compareTo(to) < 0) { + // getEstimatedPosition's current implementation returns a position equal to that of a fix at + // "from" if there is one with exactly that time stamp + Position fromPos = getEstimatedPosition(from, /* extrapolate */false); + if (fromPos == null) { + result = Distance.NULL; + } else { + lockForRead(); + try { + NavigableSet subset = getGPSFixes().subSet(new DummyGPSFix(from), + /* fromInclusive */false, new DummyGPSFix(to), + /* toInclusive */false); + for (GPSFix fix : subset) { + double distanceBetweenAdjacentFixesInNauticalMiles = fromPos.getDistance( + fix.getPosition()).getNauticalMiles(); + distanceInNauticalMiles += distanceBetweenAdjacentFixesInNauticalMiles; + fromPos = fix.getPosition(); + } + } finally { + unlockAfterRead(); + } + Position toPos = getEstimatedPosition(to, false); + distanceInNauticalMiles += fromPos.getDistance(toPos).getNauticalMiles(); + result = new NauticalMileDistance(distanceInNauticalMiles); + } + } else { + result = Distance.NULL; + } + } + } finally { + unlockAfterRead(); + } + getDistanceCache().cache(from, to, result); + } + return result; + } + + @Override + public Distance getRawDistanceTraveled(TimePoint from, TimePoint to) { + lockForRead(); + try { + double distanceInNauticalMiles = 0; + if (from.compareTo(to) < 0) { + Position fromPos = getEstimatedRawPosition(from, false); + if (fromPos == null) { + return Distance.NULL; + } + @SuppressWarnings("unchecked") + NavigableSet subset = (NavigableSet) getInternalRawFixes().subSet((FixType) new DummyGPSFix(from), + /* fromInclusive */false, (FixType) new DummyGPSFix(to), + /* toInclusive */false); + for (GPSFix fix : subset) { + distanceInNauticalMiles += fromPos.getDistance(fix.getPosition()).getNauticalMiles(); + fromPos = fix.getPosition(); + } + Position toPos = getEstimatedRawPosition(to, false); + distanceInNauticalMiles += fromPos.getDistance(toPos).getNauticalMiles(); + return new NauticalMileDistance(distanceInNauticalMiles); + } else { + return Distance.NULL; + } + } finally { + unlockAfterRead(); + } + } + + /** + * Here we know for sure that the GPS fixes are {@link GPSFixMoving} instances, + * so we can use their {@link GPSFixMoving#getSpeed() speed} in averaging. We're still + * using an interval of {@link #getMillisecondsOverWhichToAverage()} around at, + * but this time we add the speeds and bearings provided by the fix onto the values for + * averaging, so the result considers both, the GPS-provided speeds and bearings as well as + * the speeds/bearings determined by distance/time difference of the fixes themselves. + */ + @Override + public SpeedWithBearing getEstimatedSpeed(TimePoint at) { + lockForRead(); + try { + SpeedWithBearingWithConfidence estimatedSpeed = getEstimatedSpeed(at, getInternalFixes(), + ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( + // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero + getMillisecondsOverWhichToAverageSpeed()/2, /* minimumConfidence */ 0.00000001)); // half confidence if half averaging interval apart + return estimatedSpeed == null ? null : estimatedSpeed.getObject(); + } finally { + unlockAfterRead(); + } + } + + @Override + public SpeedWithBearing getRawEstimatedSpeed(TimePoint at) { + lockForRead(); + try { + return getEstimatedSpeed(at, getRawFixes(), + ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( + // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero + getMillisecondsOverWhichToAverageSpeed(), /* minimumConfidence */ 0.00000001)).getObject(); + } finally { + unlockAfterRead(); + } + } + + @Override + public SpeedWithBearingWithConfidence getEstimatedSpeed(TimePoint at, Weigher weigher) { + lockForRead(); + try { + return getEstimatedSpeed(at, getInternalFixes(), weigher); + } finally { + unlockAfterRead(); + } + } + + @Override + public SpeedWithBearingWithConfidence getRawEstimatedSpeed(TimePoint at, Weigher weigher) { + lockForRead(); + try { + return getEstimatedSpeed(at, getRawFixes(), weigher); + } finally { + unlockAfterRead(); + } + } + + /** + * Since we don't know for sure whether the GPS fixes are {@link GPSFixMoving} instances, here we only estimate + * speed based on the distance and time between the fixes, averaged over an interval of + * {@link #millisecondsOverWhichToAverage} milliseconds around at. Subclasses that know about the + * particular fix type may redefine this to exploit a {@link SpeedWithBearing} attached, e.g., to a + * {@link GPSFixMoving}. + * + * @param weigher + * If null, a confidence of 1.0 is assumed for all fixes used in the speed/bearing + * estimation. Otherwise, the confidence of each fix contributing to the estimation is computed using the + * weigher. + * @return a speed/bearing with the average confidence attributed to the fixes that contributed to the estimation + */ + protected SpeedWithBearingWithConfidence getEstimatedSpeed(TimePoint at, + NavigableSet fixesToUseForSpeedEstimation, Weigher weigher) { + lockForRead(); + try { + List relevantFixes = getFixesRelevantForSpeedEstimation(at, fixesToUseForSpeedEstimation); + List> speeds = new ArrayList>(); + BearingWithConfidenceCluster bearingCluster = new BearingWithConfidenceCluster(weigher); + FixType last = null; + for (FixType next : relevantFixes) { + if (last != null) { + // TODO bug #346: consider time difference between next.getTimepoint() and at to compute a confidence + aggregateSpeedAndBearingFromLastToNext(speeds, bearingCluster, last, next); + } + last = next; + } + ConfidenceBasedAverager speedAverager = ConfidenceFactory.INSTANCE.createAverager(weigher); + HasConfidence speedWithConfidence = speedAverager.getAverage(speeds, at); + BearingWithConfidence bearingAverage = bearingCluster.getAverage(at); + Bearing bearing = bearingAverage == null ? null : bearingAverage.getObject(); + SpeedWithBearing avgSpeed = (speedWithConfidence == null || bearing == null) ? null : + new KnotSpeedWithBearingImpl(speedWithConfidence.getObject().getKnots(), bearing); + SpeedWithBearingWithConfidence result = avgSpeed == null ? null : + new SpeedWithBearingWithConfidenceImpl(avgSpeed, (bearingAverage.getConfidence() + speedWithConfidence.getConfidence())/2., at); + return result; + } finally { + unlockAfterRead(); + } + } + + protected void aggregateSpeedAndBearingFromLastToNext(List> speeds, + BearingWithConfidenceCluster bearingCluster, GPSFix last, GPSFix next) { + MillisecondsTimePoint relativeTo = new MillisecondsTimePoint((last.getTimePoint().asMillis() + next.getTimePoint().asMillis())/2); + Speed speed = last.getPosition().getDistance(next.getPosition()) + .inTime(next.getTimePoint().asMillis() - last.getTimePoint().asMillis()); + SpeedWithConfidenceImpl speedWithConfidence = new SpeedWithConfidenceImpl(speed, /* original confidence */ + 0.9, relativeTo); + speeds.add(speedWithConfidence); + double bearingConfidence = 0.9; + if (speed.getKnots() < 0.001) { + bearingConfidence = 0; + } + bearingCluster.add(new BearingWithConfidenceImpl(last.getPosition().getBearingGreatCircle(next.getPosition()), + bearingConfidence, // TODO use number of tracked satellites to determine confidence of single fix + relativeTo)); + } + + /** + * Computes the time interval such that {@link #getFixesRelevantForSpeedEstimation(TimePoint, NavigableSet)}, when + * passed any time point from that interval, produces "fix" as part of its result array. The algorithm for + * determining this interval co-varies with the implementation of + * {@link #getFixesRelevantForSpeedEstimation(TimePoint, NavigableSet)}.

+ * + * This implementation looks for fixes {@link #getMillisecondsOverWhichToAverage()}/2 before and after fix. + * If no fix is found in a direction within half the averaging interval but there is a fix in that direction which is further + * apart, the yet next fix is checked. If there is none, or that next-next fix is further apart from the next fix than fix, + * then the next fix is added to the result interval. This is because for that next fix, fix in that case will be relevant + * for speed estimation because it's the closest fix. + */ + protected Util.Pair getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(FixType fix) { + TimePoint intervalStart = null; + TimePoint intervalEnd = null; + lockForRead(); + try { + NavigableSet beforeSet = getInternalRawFixes().headSet(fix, /* inclusive */false); + NavigableSet afterSet = getInternalRawFixes().tailSet(fix, /* inclusive */true); + FixType beforeFix = null; + Iterator beforeFixIter = beforeSet.descendingIterator(); + while (beforeFixIter.hasNext() + && fix.getTimePoint().asMillis() - (beforeFix = beforeFixIter.next()).getTimePoint().asMillis() < getMillisecondsOverWhichToAverage() / 2) { + intervalStart = beforeFix.getTimePoint(); + } + if (intervalStart == null) { + if (beforeFixIter.hasNext()) { + // No before fix within half averaging interval, but there is one further away; is its next neighbour even further away? + // If so, or no more neighbours are found, it's affected and marks the invalidation interval start; otherwise, fix' + // time point is the invalidation interval start + TimePoint intervalStartCandidate = beforeFixIter.next().getTimePoint(); + if (beforeFixIter.hasNext()) { + TimePoint nextNeighboursTimePoint = beforeFixIter.next().getTimePoint(); + if (intervalStartCandidate.asMillis()-nextNeighboursTimePoint.asMillis() > fix.getTimePoint().asMillis()-intervalStartCandidate.asMillis()) { + intervalStart = intervalStartCandidate; + } + } else { + intervalStart = intervalStartCandidate; + } + } + } + if (intervalStart == null) { + intervalStart = fix.getTimePoint(); + } + FixType afterFix = null; + Iterator afterFixIter = afterSet.iterator(); + while (afterFixIter.hasNext() + && (afterFix = afterFixIter.next()).getTimePoint().asMillis() - fix.getTimePoint().asMillis() < getMillisecondsOverWhichToAverage() / 2) { + intervalEnd = afterFix.getTimePoint(); + } + if (intervalEnd == null) { + if (afterFixIter.hasNext()) { + // No after fix within half averaging interval, but there is one further away; is its next neighbour even further away? + // If so, or no more neighbours are found, it's affected and marks the invalidation interval start; otherwise, fix's + // time point is the invalidation interval end + TimePoint intervalEndCandidate = afterFixIter.next().getTimePoint(); + if (afterFixIter.hasNext()) { + TimePoint nextNeighboursTimePoint = afterFixIter.next().getTimePoint(); + if (nextNeighboursTimePoint.asMillis()-intervalEndCandidate.asMillis() > intervalEndCandidate.asMillis()-fix.getTimePoint().asMillis()) { + intervalEnd = intervalEndCandidate; + } + } else { + intervalEnd = intervalEndCandidate; + } + } + } + if (intervalEnd == null) { + intervalEnd = fix.getTimePoint(); + } + } finally { + unlockAfterRead(); + } + return new Util.Pair(intervalStart, intervalEnd); + } + + protected FixType createDummyGPSFix(TimePoint at) { + @SuppressWarnings("unchecked") + FixType result = (FixType) new DummyGPSFix(at); + return result; + } + + /** + * A track that doesn't have {@link GPSFixMoving} fixes and therefore needs to compute the speed using the fix time + * and position differences. It therefore needs at least two fixes to compute a result. The algorithm always uses + * at least the closest existing fix later and the closet existing fix earlier than at, if available. + *

+ * + * But even for a track with {@link GPSFixMoving} fixes this is a good algorithm because in case the speed changes + * significantly between fixes, it is important to know the next fix to understand and consider the trend. + */ + protected List getFixesRelevantForSpeedEstimation(TimePoint at, NavigableSet fixesToUseForSpeedEstimation) { + lockForRead(); + try { + FixType atTimed = createDummyGPSFix(at); + List relevantFixes = new LinkedList(); + NavigableSet beforeSet = fixesToUseForSpeedEstimation.headSet(atTimed, /* inclusive */false); + NavigableSet afterSet = fixesToUseForSpeedEstimation.tailSet(atTimed, /* inclusive */true); + FixType beforeFix = null; + Iterator beforeFixIter = beforeSet.descendingIterator(); + boolean noBeforeFixUsedYet = true; + while (beforeFixIter.hasNext() && + (at.asMillis() - (beforeFix=beforeFixIter.next()).getTimePoint().asMillis() < getMillisecondsOverWhichToAverage() / 2 || noBeforeFixUsedYet)) { + relevantFixes.add(0, beforeFix); + noBeforeFixUsedYet = false; + beforeFix = null; // mark the fix as used + } + FixType afterFix = null; + Iterator afterFixIter = afterSet.iterator(); + boolean noAfterFixUsedYet = true; + while (afterFixIter.hasNext() && + ((afterFix=afterFixIter.next()).getTimePoint().asMillis() - at.asMillis() < getMillisecondsOverWhichToAverage() / 2 || noAfterFixUsedYet)) { + relevantFixes.add(afterFix); + noAfterFixUsedYet = false; + afterFix = null; // mark the fix as used + } + // now fill up relevantFixes until we have at least two fixes or we run out of fixes entirely (can't estimate speed + // with this type of fix on a track with less than two fixes) + while (relevantFixes.size() < 2 && (beforeFix != null || afterFix != null)) { + if (afterFix == null) { + if (beforeFix != null) { + relevantFixes.add(0, beforeFix); // add the last beforeFix to have at least two fixes, although outside of averaging interval + if (beforeFixIter.hasNext()) { + beforeFix = beforeFixIter.next(); + } else { + beforeFix = null; + } + } + } else if (beforeFix == null) { + relevantFixes.add(afterFix); + if (afterFixIter.hasNext()) { + afterFix = afterFixIter.next(); + } else { + afterFix = null; + } + } else { + // both, beforeFix and afterFix are available; choose the one closest to "at" + if (afterFix.getTimePoint().asMillis()-at.asMillis() < beforeFix.getTimePoint().asMillis()-at.asMillis()) { + relevantFixes.add(afterFix); + if (afterFixIter.hasNext()) { + afterFix = afterFixIter.next(); + } else { + afterFix = null; + } + } else { + relevantFixes.add(0, beforeFix); + if (beforeFixIter.hasNext()) { + beforeFix = beforeFixIter.next(); + } else { + beforeFix = null; + } + } + } + } + return relevantFixes; + } finally { + unlockAfterRead(); + } + } + + protected long getMillisecondsOverWhichToAverage() { + return millisecondsOverWhichToAverage; + } + + /** + * Smoothens the track based on a max-speed assumption. + */ + @Override + protected NavigableSet getInternalFixes() { + assertReadLock(); + return new PartialNavigableSetView(super.getInternalFixes()) { + @Override + protected boolean isValid(FixType e) { + return GPSFixTrackImpl.this.isValid(getRawFixes(), e); + } + }; + } + + /** + * When redefining this method, make sure to redefine {@link #invalidateValidityAndDistanceCaches(GPSFix)} + * accordingly. This implementation checks the immediate previous and next fix for e. Therefore, when + * adding a fix, only immediately adjacent fix's validity caches need to be invalidated. + *

+ * + * The fix e is considered valid if at least one of its (not necessarily immediate) neighbors that is + * within the range of {@link #getMillisecondsOverWhichToAverageSpeed()} is in reach with less than + * {@link #maxSpeedForSmoothing}, or if there are no neighbors with the time range defined by + * {@link #getMillisecondsOverWhichToAverageSpeed()}. + */ + protected boolean isValid(NavigableSet rawFixes, FixType e) { + assertReadLock(); + boolean isValid; + if (maxSpeedForSmoothing == null) { + isValid = true; + } else { + if (e.isValidityCached()) { + isValid = e.isValid(); + } else { + FixType previous = rawFixes.lower(e); + final boolean atLeastOnePreviousFixInRange = previous != null && e.getTimePoint().asMillis() - previous.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed(); + Speed speedToPrevious = null; + boolean foundValidPreviousFixInRange = false; + while (previous != null && !foundValidPreviousFixInRange && e.getTimePoint().asMillis() - previous.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed()) { + speedToPrevious = previous.getPosition().getDistance(e.getPosition()) + .inTime(e.getTimePoint().asMillis() - previous.getTimePoint().asMillis()); + foundValidPreviousFixInRange = speedToPrevious.compareTo(maxSpeedForSmoothing) <= 0; + previous = rawFixes.lower(previous); + } + boolean foundValidNextFixInRange = false; + boolean atLeastOneNextFixInRange = false; + // only spend the effort to calculate the "next"-related predicate if the "previous"-related part of the disjunction below isn't already false + if (!atLeastOnePreviousFixInRange || foundValidPreviousFixInRange) { + FixType next = rawFixes.higher(e); + atLeastOneNextFixInRange = next != null && next.getTimePoint().asMillis() - e.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed(); + Speed speedToNext = null; + while (next != null && !foundValidNextFixInRange && next.getTimePoint().asMillis() - e.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed()) { + speedToNext = e.getPosition().getDistance(next.getPosition()) + .inTime(next.getTimePoint().asMillis() - e.getTimePoint().asMillis()); + foundValidNextFixInRange = speedToNext.compareTo(maxSpeedForSmoothing) <= 0; + next = rawFixes.higher(next); + } + } + isValid = (!atLeastOnePreviousFixInRange || foundValidPreviousFixInRange) && (!atLeastOneNextFixInRange || foundValidNextFixInRange); + e.cacheValidity(isValid); + } + } + return isValid; + } + + /** + * After gpsFix was added to this track, invalidate the {@link WithValidityCache validity caches} + * of the fixes whose validity may be affected. If subclasses redefine {@link #isValid(PartialNavigableSetView, GPSFix)}, + * they must make sure that this method is redefined accordingly. Here, {@link #getMillisecondsOverWhichToAverageSpeed()} + * before and after the fix all fixes' validity caches are reset.

+ * + * Distance cache invalidation is a bit tricky. Usually, the distance cache is invalidated starting with the time point + * of the gpsFix "upwards." However, if the adjacent earlier fixes have changed their validity by the addition + * of gpsFix, the distance cache must be invalidated starting with the first fix whose validity changed. + */ + protected void invalidateValidityAndDistanceCaches(FixType gpsFix) { + assertWriteLock(); + TimePoint distanceCacheInvalidationStart = gpsFix.getTimePoint(); + // see also bug 968: cache entries for intervals ending after the last fix need to be removed because they are + // based on the last fix's position and don't extrapolate; now, with the new gpsFix, the positions between the last + // and the new fix are defined by interpolation and hence differ from the previous assumption the competitor would have + // stopped moving at the last fix. + FixType last = getInternalFixes().lower(gpsFix); + if (last != null) { + distanceCacheInvalidationStart = last.getTimePoint().plus(1); // add one millisecond to invalidate *after* the last fix only + } + gpsFix.invalidateCache(); + Iterable lowers = getEarlierFixesWhoseValidityMayBeAffected(gpsFix); + for (FixType lower : lowers) { + boolean lowerWasValid = isValid(getRawFixes(), lower); + lower.invalidateCache(); + boolean lowerIsValid = isValid(getRawFixes(), lower); + if (lowerIsValid != lowerWasValid && lower.getTimePoint().before(distanceCacheInvalidationStart)) { + distanceCacheInvalidationStart = lower.getTimePoint(); + } + } + getDistanceCache().invalidateAllAtOrLaterThan(distanceCacheInvalidationStart); + Iterable highers = getLaterFixesWhoseValidityMayBeAffected(gpsFix); + for (FixType higher : highers) { + higher.invalidateCache(); + } + } + + protected Iterable getLaterFixesWhoseValidityMayBeAffected(FixType gpsFix) { + FixType higher = getInternalRawFixes().higher(gpsFix); + if (higher == null) { + return Collections.emptySet(); + } else { + Collection result = new ArrayList(); + while (higher != null && higher.getTimePoint().asMillis() - gpsFix.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed()) { + result.add(higher); + higher = getInternalRawFixes().higher(higher); + } + return result; + } + } + + protected Iterable getEarlierFixesWhoseValidityMayBeAffected(FixType gpsFix) { + FixType lower = getInternalRawFixes().lower(gpsFix); + if (lower == null) { + return Collections.emptySet(); + } else { + Collection result = new ArrayList(); + while (lower != null && gpsFix.getTimePoint().asMillis() - lower.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed()) { + result.add(lower); + lower = getInternalRawFixes().lower(lower); + } + return result; + } + } + + @Override + public boolean hasDirectionChange(TimePoint at, double minimumDegreeDifference) { + lockForRead(); + try { + boolean result = false; + TimePoint start = new MillisecondsTimePoint(at.asMillis() - getMillisecondsOverWhichToAverageSpeed()); + TimePoint end = new MillisecondsTimePoint(at.asMillis() + getMillisecondsOverWhichToAverageSpeed()); + SpeedWithBearing estimatedSpeedAtStart = getEstimatedSpeed(start); + if (estimatedSpeedAtStart != null) { + Bearing bearingAtStart = estimatedSpeedAtStart.getBearing(); + TimePoint next = new MillisecondsTimePoint(start.asMillis() + + Math.max(1000l, getMillisecondsOverWhichToAverageSpeed() / 2)); + while (!result && next.compareTo(end) <= 0) { + SpeedWithBearing estimatedSpeedAtNext = getEstimatedSpeed(next); + if (estimatedSpeedAtNext != null) { + Bearing bearingAtEnd = estimatedSpeedAtNext.getBearing(); + result = Math.abs(bearingAtStart.getDifferenceTo(bearingAtEnd).getDegrees()) > minimumDegreeDifference; + } + next = new MillisecondsTimePoint(next.asMillis() + + Math.max(1000l, getMillisecondsOverWhichToAverageSpeed() / 2)); + } + } + return result; + } finally { + unlockAfterRead(); + } + } + + protected DistanceCache getDistanceCache() { + return distanceCache; + } + + @Override + protected boolean add(FixType fix) { + final boolean result; + lockForWrite(); + try { + result = addWithoutLocking(fix); + invalidateValidityAndDistanceCaches(fix); + } finally { + unlockAfterWrite(); + } + if (logger.isLoggable(Level.FINEST)) { + FixType last; + try { + lockForRead(); + if (logger.isLoggable(Level.FINEST)) { + logger.finest("GPS fix "+fix+" for "+getTrackedItem()+", isValid="+isValid(getInternalRawFixes(), fix)+ + ", time/distance/speed from last: "+ + ((last=getInternalRawFixes().lower(fix))==null + ? "null" + : (fix.getTimePoint().asMillis()-last.getTimePoint().asMillis()+"ms/"+ + fix.getPosition().getDistance(last.getPosition())) + "/"+ + fix.getPosition().getDistance(last.getPosition()).inTime(fix.getTimePoint().asMillis()-last.getTimePoint().asMillis()))); + } + } finally { + unlockAfterRead(); + } + } + for (GPSTrackListener listener : getListeners()) { + listener.gpsFixReceived(fix, getTrackedItem()); + } + return result; + } + + protected void setMillisecondsOverWhichToAverage(long millisecondsOverWhichToAverage) { + long oldMillis = getMillisecondsOverWhichToAverage(); + this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage; + for (GPSTrackListener listener : getListeners()) { + listener.speedAveragingChanged(oldMillis, millisecondsOverWhichToAverage); + } + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java index d3f8dfa1619..4ad78203d83 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java @@ -1,276 +1,277 @@ -package com.sap.sailing.domain.tracking.impl; - -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NavigableSet; - -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.GPSTrackListener; -import com.sap.sailing.util.impl.ArrayListNavigableSet; -import com.sap.sailing.util.impl.LockUtil; -import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; - -/** - * Re-calculating the maximum speed over a {@link GPSFixTrack} is time consuming. When the track grows the way it - * usually grows (extending at the end), caching can help to reduce the computational effort. The API for computing the - * maximum speed ( - * {@link GPSFixTrack#getMaximumSpeedOverGround(com.sap.sailing.domain.common.TimePoint, com.sap.sailing.domain.common.TimePoint)} - * ) allows clients to specify an interval for which the top speed is to be computed. Therefore, this cache offers a - * similar API which supports interval-based queries. - *

- * - * The cache assumes that queries are usually posed with one of a small set of "from" time points, such as a leg's start - * or the race starting time. The "to" time points are expected to vary, particularly to grow in case a "live" query is - * made, or to represent one of a few more or less fixed time points such as a competitor's leg finishing times. With - * this assumption it seems reasonable to structure the cache such that the "from" time point is a key into a map that - * stores results for this "from" value. The various results for the same "from" entry are stored in the navigable set - * with ascending "to" times. When a query comes in, the best fit is determined by looking up the latest "to" that is - * earlier than or equal to the requested "to." If such an entry is found, the search for a maximum speed can be - * restricted to the interval between the cache entry's "to" and the "to" time point requested. - *

- * - * When a new GPS fix is recorded for the track for which this is a max-speed cache, invalidation takes place. For this, - * the cache {@link GPSTrackListener listens} for GPS fixes being added to the track. For the fix added, we'd like to - * find the time interval within which a - * {@link GPSFixTrackImpl#getFixesRelevantForSpeedEstimation(TimePoint, NavigableSet)} with a {@link TimePoint} from - * that interval delivers the fix added in its result. All overlapping cached intervals will then be cropped to the - * remaining valid interval if their maximum speed time point was in the remaining interval, or removed from the cache - * otherwise. - * - * @author Axel Uhl (d043530) - * - */ -public class MaxSpeedCache implements GPSTrackListener { - private static final long serialVersionUID = 8825205750854940612L; - - private final GPSFixTrackImpl track; - - /** - * Keys are the "from" time points as passed to {@link #getMaxSpeed(TimePoint, TimePoint)}. Values are navigable - * sets of pairs whose {@link Pair#getA() a} component is the "to" parameter as passed to - * {@link #getMaxSpeed(TimePoint, TimePoint)}, and whose {@link Pair#getB() b} component is the track's fix where - * the averaged maximum speed for that interval was achieved together with the averaged speed at that point. The - * navigable set is ordered according to ascending to time points, yielding shorter cache intervals - * before longer intervals. - */ - private final Map>>> cache; - - private final NamedReentrantReadWriteLock lock; - - public MaxSpeedCache(GPSFixTrackImpl track) { - this.track = track; - track.addListener(this); - cache = new HashMap>>>(); - lock = new NamedReentrantReadWriteLock(MaxSpeedCache.class.getSimpleName()+" for track of "+track.getTrackedItem(), /* fair */ false); - } - - /** - * Find the invalidation interval such that getFixesRelevantForSpeedEstimation, when passed any time point from that - * interval, produces "fix", then find all cache entries that overlap with this interval and if the max fix is - * outside of the invalidation interval, crop cache entry's interval such that it's overlap-free, otherwise remove. - */ - @Override - public void gpsFixReceived(FixType fix, ItemType item) { - // find the invalidation interval such that getFixesRelevantForSpeedEstimation, when passed any time point from that interval, produces "fix" - Pair invalidationInterval = track.getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(fix); - LockUtil.lockForWrite(lock); - HashMap>>> additionalCacheEntries = - new HashMap>>>(); - try { - // find all cache entries that overlap with this interval - for (Iterator>>>> i = cache.entrySet().iterator(); i.hasNext(); ) { - Entry>>> next = i.next(); - additionalCacheEntries.putAll(invalidateEntriesAndReturnAdditionalCacheEntries(next, invalidationInterval)); - if (next.getValue().isEmpty()) { - i.remove(); - } - } - mergeAdditionalCacheEntries(additionalCacheEntries); - } finally { - LockUtil.unlockAfterWrite(lock); - } - } - - /** - * Returns true in order to skip serialization of the cache as a listener. The cache itself shall not be serialized. - * It is re-established during the deserialization of the owning track. - */ - @Override - public boolean isTransient() { - return true; - } - - private void mergeAdditionalCacheEntries( - Map>>> additionalCacheEntries) { - assert lock.writeLock().isHeldByCurrentThread(); - for (Map.Entry>>> additionalCacheEntry : additionalCacheEntries.entrySet()) { - NavigableSet>> existingEntry = cache.get(additionalCacheEntry.getKey()); - if (existingEntry == null) { - cache.put(additionalCacheEntry.getKey(), additionalCacheEntry.getValue()); - } else { - existingEntry.addAll(additionalCacheEntry.getValue()); - } - } - } - - private Map>>> invalidateEntriesAndReturnAdditionalCacheEntries( - Entry>>> cacheEntry, - Pair invalidationInterval) { - assert lock.writeLock().isHeldByCurrentThread(); - // cannot modify cache here because caller is in an iteration over cache's entry set; request additions by returning them - Map>>> result = new HashMap<>(); - if (!cacheEntry.getKey().after(invalidationInterval.getB())) { - // invalidation can only become necessary if the cache entry doesn't start after the end of the invalidation interval - TimePoint croppedFrom; // start of the remaining valid part of the cache entries - if (cacheEntry.getKey().before(invalidationInterval.getA())) { - croppedFrom = cacheEntry.getKey(); - } else { - croppedFrom = invalidationInterval.getB(); // cache entry starts in the invalidationInterval; earliest valid point is end of invalidation interval - } - // now scan all entries whose "to" is at or after the invalidationInterval's start: - for (Iterator>> toAndResultIter = cacheEntry.getValue().tailSet( - new Pair>(invalidationInterval.getA(), null), /* inclusive */ true).iterator(); - toAndResultIter.hasNext(); ) { - Pair> toAndResult = toAndResultIter.next(); - // cacheEntry's from is before or in the invalidation interval; the current "to" in this loop iteration is in or after the interval - // and at or after "from"; in any case the record needs to be deleted: - toAndResultIter.remove(); - // if the current "to" is at or after croppedFrom, check if the old max fix is in the cropped interval; if so, request - // creation of a new cache entry: - if (toAndResult.getB() != null) { - final TimePoint maxFixTimePoint = toAndResult.getB().getA().getTimePoint(); - if (!toAndResult.getA().before(croppedFrom) && !maxFixTimePoint.before(croppedFrom) - && !maxFixTimePoint.after(toAndResult.getA())) { - addEntryToMap(croppedFrom, toAndResult.getA(), toAndResult.getB(), result); - } - } - } - } - return result; - } - - @Override - public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { - LockUtil.lockForWrite(lock); - try { - cache.clear(); - } finally { - LockUtil.unlockAfterWrite(lock); - } - } - - public Pair getMaxSpeed(TimePoint from, TimePoint to) { - Pair result = null; - if (!to.before(from)) { - result = cacheLookup(from, to); - if (result == null) { - result = computeMaxSpeed(from, to); - cache(from, to, result); - } - } - return result; - } - - /** - * If a cache entry exists for "from" whose interval ends at or before to, use it and if necessary compute the - * maximum in the missing tail, producing another cache entry for both, the tail and the extended interval. Otherwise, null - * is returned. - */ - private Pair cacheLookup(TimePoint from, TimePoint to) { - assert !from.after(to); - Pair result = null; - NavigableSet>> entry; - Pair> entryForLongestSubseries = null; - LockUtil.lockForRead(lock); - try { - entry = cache.get(from); - if (entry != null) { - entryForLongestSubseries = entry.floor(new Pair>(to, null)); - } - } finally { - LockUtil.unlockAfterRead(lock); - } - if (entryForLongestSubseries != null && entryForLongestSubseries.getB() != null) { - TimePoint entryTo = entryForLongestSubseries.getA(); - // avoid endless recursion in case entryTo is not after from: - if (entryTo.equals(to) || entryTo.after(from)) { - if (entryTo.before(to)) { - Pair maxInMissingTail = getMaxSpeed(entryTo, to); - if (maxInMissingTail != null && maxInMissingTail.getB().compareTo(entryForLongestSubseries.getB().getB()) > 0) { - // the maximum speed is in the tail that was not part of the interval retrieved from the cache - result = maxInMissingTail; - } else { - result = entryForLongestSubseries.getB(); // the interval from the cache also holds the maximum - // for the extended interval - } - cache(from, to, result); // produce new - } else { - result = entryForLongestSubseries.getB(); - } - } - } - return result; - } - - private void cache(TimePoint from, TimePoint to, Pair fixAtMaxSpeed) { - LockUtil.lockForWrite(lock); - try { - addEntryToMap(from, to, fixAtMaxSpeed, cache); - } finally { - LockUtil.unlockAfterWrite(lock); - } - } - - private void addEntryToMap(TimePoint from, TimePoint to, Pair fixAtMaxSpeed, - Map>>> map) { - NavigableSet>> setForFrom = map.get(from); - if (setForFrom == null) { - setForFrom = new ArrayListNavigableSet>>( - new Comparator>>() { - @Override - public int compare(Pair> o1, - Pair> o2) { - return o1.getA().compareTo(o2.getA()); - } - }); - map.put(from, setForFrom); - } - setForFrom.add(new Pair>(to, fixAtMaxSpeed)); - } - - /** - * @return null, if no fix exists in the interval specified - */ - private Pair computeMaxSpeed(TimePoint from, TimePoint to) { - track.lockForRead(); - try { - // fetch all fixes on this leg so far and determine their maximum speed - Iterator iter = track.getFixesIterator(from, /* inclusive */ true); - Speed max = Speed.NULL; - FixType maxSpeedFix = null; - if (iter.hasNext()) { - while (iter.hasNext()) { - FixType fix = iter.next(); - if (fix.getTimePoint().after(to)) { - break; - } - Speed averagedSpeedAtFixTime = track.getEstimatedSpeed(fix.getTimePoint()); - if (averagedSpeedAtFixTime != null && averagedSpeedAtFixTime.compareTo(max) > 0) { - max = averagedSpeedAtFixTime; - maxSpeedFix = fix; - } - } - } - return maxSpeedFix == null ? null : new Pair(maxSpeedFix, max); - } finally { - track.unlockAfterRead(); - } - } -} +package com.sap.sailing.domain.tracking.impl; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableSet; + +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.GPSTrackListener; +import com.sap.sailing.util.impl.ArrayListNavigableSet; +import com.sap.sailing.util.impl.LockUtil; +import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; + +/** + * Re-calculating the maximum speed over a {@link GPSFixTrack} is time consuming. When the track grows the way it + * usually grows (extending at the end), caching can help to reduce the computational effort. The API for computing the + * maximum speed ( + * {@link GPSFixTrack#getMaximumSpeedOverGround(com.sap.sailing.domain.common.TimePoint, com.sap.sailing.domain.common.TimePoint)} + * ) allows clients to specify an interval for which the top speed is to be computed. Therefore, this cache offers a + * similar API which supports interval-based queries. + *

+ * + * The cache assumes that queries are usually posed with one of a small set of "from" time points, such as a leg's start + * or the race starting time. The "to" time points are expected to vary, particularly to grow in case a "live" query is + * made, or to represent one of a few more or less fixed time points such as a competitor's leg finishing times. With + * this assumption it seems reasonable to structure the cache such that the "from" time point is a key into a map that + * stores results for this "from" value. The various results for the same "from" entry are stored in the navigable set + * with ascending "to" times. When a query comes in, the best fit is determined by looking up the latest "to" that is + * earlier than or equal to the requested "to." If such an entry is found, the search for a maximum speed can be + * restricted to the interval between the cache entry's "to" and the "to" time point requested. + *

+ * + * When a new GPS fix is recorded for the track for which this is a max-speed cache, invalidation takes place. For this, + * the cache {@link GPSTrackListener listens} for GPS fixes being added to the track. For the fix added, we'd like to + * find the time interval within which a + * {@link GPSFixTrackImpl#getFixesRelevantForSpeedEstimation(TimePoint, NavigableSet)} with a {@link TimePoint} from + * that interval delivers the fix added in its result. All overlapping cached intervals will then be cropped to the + * remaining valid interval if their maximum speed time point was in the remaining interval, or removed from the cache + * otherwise. + * + * @author Axel Uhl (d043530) + * + */ +public class MaxSpeedCache implements GPSTrackListener { + private static final long serialVersionUID = 8825205750854940612L; + + private final GPSFixTrackImpl track; + + /** + * Keys are the "from" time points as passed to {@link #getMaxSpeed(TimePoint, TimePoint)}. Values are navigable + * sets of pairs whose {@link Pair#getA() a} component is the "to" parameter as passed to + * {@link #getMaxSpeed(TimePoint, TimePoint)}, and whose {@link Pair#getB() b} component is the track's fix where + * the averaged maximum speed for that interval was achieved together with the averaged speed at that point. The + * navigable set is ordered according to ascending to time points, yielding shorter cache intervals + * before longer intervals. + */ + private final Map>>> cache; + + private final NamedReentrantReadWriteLock lock; + + public MaxSpeedCache(GPSFixTrackImpl track) { + this.track = track; + track.addListener(this); + cache = new HashMap>>>(); + lock = new NamedReentrantReadWriteLock(MaxSpeedCache.class.getSimpleName()+" for track of "+track.getTrackedItem(), /* fair */ false); + } + + /** + * Find the invalidation interval such that getFixesRelevantForSpeedEstimation, when passed any time point from that + * interval, produces "fix", then find all cache entries that overlap with this interval and if the max fix is + * outside of the invalidation interval, crop cache entry's interval such that it's overlap-free, otherwise remove. + */ + @Override + public void gpsFixReceived(FixType fix, ItemType item) { + // find the invalidation interval such that getFixesRelevantForSpeedEstimation, when passed any time point from that interval, produces "fix" + Util.Pair invalidationInterval = track.getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(fix); + LockUtil.lockForWrite(lock); + HashMap>>> additionalCacheEntries = + new HashMap>>>(); + try { + // find all cache entries that overlap with this interval + for (Iterator>>>> i = cache.entrySet().iterator(); i.hasNext(); ) { + Entry>>> next = i.next(); + additionalCacheEntries.putAll(invalidateEntriesAndReturnAdditionalCacheEntries(next, invalidationInterval)); + if (next.getValue().isEmpty()) { + i.remove(); + } + } + mergeAdditionalCacheEntries(additionalCacheEntries); + } finally { + LockUtil.unlockAfterWrite(lock); + } + } + + /** + * Returns true in order to skip serialization of the cache as a listener. The cache itself shall not be serialized. + * It is re-established during the deserialization of the owning track. + */ + @Override + public boolean isTransient() { + return true; + } + + private void mergeAdditionalCacheEntries( + Map>>> additionalCacheEntries) { + assert lock.writeLock().isHeldByCurrentThread(); + for (Map.Entry>>> additionalCacheEntry : additionalCacheEntries.entrySet()) { + NavigableSet>> existingEntry = cache.get(additionalCacheEntry.getKey()); + if (existingEntry == null) { + cache.put(additionalCacheEntry.getKey(), additionalCacheEntry.getValue()); + } else { + existingEntry.addAll(additionalCacheEntry.getValue()); + } + } + } + + private Map>>> invalidateEntriesAndReturnAdditionalCacheEntries( + Entry>>> cacheEntry, + Util.Pair invalidationInterval) { + assert lock.writeLock().isHeldByCurrentThread(); + // cannot modify cache here because caller is in an iteration over cache's entry set; request additions by returning them + Map>>> result = new HashMap<>(); + if (!cacheEntry.getKey().after(invalidationInterval.getB())) { + // invalidation can only become necessary if the cache entry doesn't start after the end of the invalidation interval + TimePoint croppedFrom; // start of the remaining valid part of the cache entries + if (cacheEntry.getKey().before(invalidationInterval.getA())) { + croppedFrom = cacheEntry.getKey(); + } else { + croppedFrom = invalidationInterval.getB(); // cache entry starts in the invalidationInterval; earliest valid point is end of invalidation interval + } + // now scan all entries whose "to" is at or after the invalidationInterval's start: + for (Iterator>> toAndResultIter = cacheEntry.getValue().tailSet( + new Util.Pair>(invalidationInterval.getA(), null), /* inclusive */ true).iterator(); + toAndResultIter.hasNext(); ) { + Util.Pair> toAndResult = toAndResultIter.next(); + // cacheEntry's from is before or in the invalidation interval; the current "to" in this loop iteration is in or after the interval + // and at or after "from"; in any case the record needs to be deleted: + toAndResultIter.remove(); + // if the current "to" is at or after croppedFrom, check if the old max fix is in the cropped interval; if so, request + // creation of a new cache entry: + if (toAndResult.getB() != null) { + final TimePoint maxFixTimePoint = toAndResult.getB().getA().getTimePoint(); + if (!toAndResult.getA().before(croppedFrom) && !maxFixTimePoint.before(croppedFrom) + && !maxFixTimePoint.after(toAndResult.getA())) { + addEntryToMap(croppedFrom, toAndResult.getA(), toAndResult.getB(), result); + } + } + } + } + return result; + } + + @Override + public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { + LockUtil.lockForWrite(lock); + try { + cache.clear(); + } finally { + LockUtil.unlockAfterWrite(lock); + } + } + + public Util.Pair getMaxSpeed(TimePoint from, TimePoint to) { + Util.Pair result = null; + if (!to.before(from)) { + result = cacheLookup(from, to); + if (result == null) { + result = computeMaxSpeed(from, to); + cache(from, to, result); + } + } + return result; + } + + /** + * If a cache entry exists for "from" whose interval ends at or before to, use it and if necessary compute the + * maximum in the missing tail, producing another cache entry for both, the tail and the extended interval. Otherwise, null + * is returned. + */ + private Util.Pair cacheLookup(TimePoint from, TimePoint to) { + assert !from.after(to); + Util.Pair result = null; + NavigableSet>> entry; + Util.Pair> entryForLongestSubseries = null; + LockUtil.lockForRead(lock); + try { + entry = cache.get(from); + if (entry != null) { + entryForLongestSubseries = entry.floor(new Util.Pair>(to, null)); + } + } finally { + LockUtil.unlockAfterRead(lock); + } + if (entryForLongestSubseries != null && entryForLongestSubseries.getB() != null) { + TimePoint entryTo = entryForLongestSubseries.getA(); + // avoid endless recursion in case entryTo is not after from: + if (entryTo.equals(to) || entryTo.after(from)) { + if (entryTo.before(to)) { + Util.Pair maxInMissingTail = getMaxSpeed(entryTo, to); + if (maxInMissingTail != null && maxInMissingTail.getB().compareTo(entryForLongestSubseries.getB().getB()) > 0) { + // the maximum speed is in the tail that was not part of the interval retrieved from the cache + result = maxInMissingTail; + } else { + result = entryForLongestSubseries.getB(); // the interval from the cache also holds the maximum + // for the extended interval + } + cache(from, to, result); // produce new + } else { + result = entryForLongestSubseries.getB(); + } + } + } + return result; + } + + private void cache(TimePoint from, TimePoint to, Util.Pair fixAtMaxSpeed) { + LockUtil.lockForWrite(lock); + try { + addEntryToMap(from, to, fixAtMaxSpeed, cache); + } finally { + LockUtil.unlockAfterWrite(lock); + } + } + + private void addEntryToMap(TimePoint from, TimePoint to, Util.Pair fixAtMaxSpeed, + Map>>> map) { + NavigableSet>> setForFrom = map.get(from); + if (setForFrom == null) { + setForFrom = new ArrayListNavigableSet>>( + new Comparator>>() { + @Override + public int compare(Util.Pair> o1, + Util.Pair> o2) { + return o1.getA().compareTo(o2.getA()); + } + }); + map.put(from, setForFrom); + } + setForFrom.add(new Util.Pair>(to, fixAtMaxSpeed)); + } + + /** + * @return null, if no fix exists in the interval specified + */ + private Util.Pair computeMaxSpeed(TimePoint from, TimePoint to) { + track.lockForRead(); + try { + // fetch all fixes on this leg so far and determine their maximum speed + Iterator iter = track.getFixesIterator(from, /* inclusive */ true); + Speed max = Speed.NULL; + FixType maxSpeedFix = null; + if (iter.hasNext()) { + while (iter.hasNext()) { + FixType fix = iter.next(); + if (fix.getTimePoint().after(to)) { + break; + } + Speed averagedSpeedAtFixTime = track.getEstimatedSpeed(fix.getTimePoint()); + if (averagedSpeedAtFixTime != null && averagedSpeedAtFixTime.compareTo(max) > 0) { + max = averagedSpeedAtFixTime; + maxSpeedFix = fix; + } + } + } + return maxSpeedFix == null ? null : new Util.Pair(maxSpeedFix, max); + } finally { + track.unlockAfterRead(); + } + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ScalableWind.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ScalableWind.java index 58aa7572869..c9611e5c0c3 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ScalableWind.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ScalableWind.java @@ -1,11 +1,11 @@ package com.sap.sailing.domain.tracking.impl; -import com.sap.sailing.domain.base.impl.ScalablePosition; -import com.sap.sailing.domain.base.impl.ScalableSpeedWithBearing; import com.sap.sailing.domain.common.AbstractPosition; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.ScalableValue; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalableSpeedWithBearing; import com.sap.sailing.domain.tracking.Wind; /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java index 5682a7efeeb..94d338e81ca 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java @@ -27,7 +27,6 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.SerializableComparator; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.confidence.ConfidenceFactory; import com.sap.sailing.domain.confidence.Weigher; import com.sap.sailing.domain.tracking.GPSFix; @@ -42,6 +41,7 @@ import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.util.impl.ArrayListNavigableSet; import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sse.common.Util; /** * A virtual wind track that computes and caches the wind bearing based on the boat tracks recorded in the tracked race @@ -82,13 +82,21 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl private final NavigableSet timePointsWithCachedNullResult; - /** - * Access to this cache is guarded by {@link #cacheLock}. - */ private final NavigableSet> cache; + /** + * Access to {@link #cache} and {@link #timePointsWithCachedNullResult} and + * {@link #timePointsWithCachedNullResultFastContains} is guarded by this lock. + */ private final NamedReentrantReadWriteLock cacheLock; + /** + * Access to {@link #scheduledRefreshInterval} is guarded by this lock. In particular, when a client + * holds this lock the client can be sure that {@link InvalidationInterval#isSet()} implies that a + * cache update is scheduled or already running. + */ + private final NamedReentrantReadWriteLock scheduledRefreshIntervalLock; + private final Weigher weigher; /** @@ -106,7 +114,13 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl private static class InvalidationInterval implements Serializable { private static final long serialVersionUID = -6406690520919193690L; + /** + * Either both {@link #start} and {@link #end} are null, or both are non-null + */ private WindWithConfidence start; + /** + * Either both {@link #start} and {@link #end} are null, or both are non-null + */ private TimePoint end; public InvalidationInterval() { super(); @@ -117,18 +131,38 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl public TimePoint getEnd() { return end; } - public synchronized void clear() { + public void clear() { start = null; end = null; } - public synchronized boolean isSet() { + public boolean isSet() { return start != null && end != null; } - public synchronized void set(WindWithConfidence startOfInvalidation, TimePoint endOfInvalidation) { + + /** + * Checks whether the interval starting at {@link #start} and ending at {@link #end} is fully contained in this + * interval. Neither of the two arguments is allowed to be null. The interval is contained in this + * interval if this interval {@link #isSet() is set} and this interval's {@link #getStart() start} is at or + * before start and this interval's {@link #getEnd() end} is at or after end. + */ + public boolean contains(TimePoint start, TimePoint end) { + return isSet() && !getStart().getObject().getTimePoint().after(start) && !end.after(getEnd()); + } + /** + * Neither of the two arguments is allowed to be null. + */ + public void set(WindWithConfidence startOfInvalidation, TimePoint endOfInvalidation) { + assert startOfInvalidation != null; + assert endOfInvalidation != null; this.start = startOfInvalidation; this.end = endOfInvalidation; } - public synchronized void extend(WindWithConfidence startOfInvalidation, TimePoint endOfInvalidation) { + /** + * Neither of the two arguments is allowed to be null. + */ + public void extend(WindWithConfidence startOfInvalidation, TimePoint endOfInvalidation) { + assert startOfInvalidation != null; + assert endOfInvalidation != null; if (startOfInvalidation.getObject().getTimePoint().compareTo(start.getObject().getTimePoint()) < 0) { this.start = startOfInvalidation; } @@ -136,11 +170,54 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl end = endOfInvalidation; } } + + /** + * Leaves this object unchanged. Returns a new {@link InvalidationInterval} object. If the interval defined by + * start and end does not exceed this interval, the resulting interval will have + * {@link #isSet()}==false. Otherwise, the resulting interval will contain all time ranges from + * start to end (inclusive) that are not in this interval. In particular, if + * start..end exceeds this interval on both ends, the resulting interval will start at + * start and end at end. If this interval has {@link #isSet()}==false, + * the resulting interval will range from start to end. + * + * @param start must not be null + * @param end must not be null + */ + public InvalidationInterval subtract(WindWithConfidence start, TimePoint end) { + assert start != null; + assert end != null; + final InvalidationInterval result = new InvalidationInterval(); + if (!isSet()) { + result.set(start, end); + } else { + final WindWithConfidence newStart; + final TimePoint startTimePoint = getStart().getObject().getTimePoint(); + if (start.getObject().getTimePoint().before(startTimePoint)) { + newStart = start; + } else { + // don't go beyond the end of time, avoiding overflow + newStart = getDummyFixWithConfidence(getEnd().asMillis()==Long.MAX_VALUE?getEnd():getEnd().plus(1)); + } + final TimePoint newEnd; + if (end.after(getEnd())) { + newEnd = end; + } else { + // avoid underflow + newEnd = startTimePoint.asMillis()==0?startTimePoint:startTimePoint.minus(1); + } + if (!newStart.getObject().getTimePoint().after(newEnd)) { + result.set(newStart, newEnd); + } // else, the resulting interval remains unset + } + return result; + } } /** - * {@link #scheduleCacheRefresh(WindWithConfidence, TimePoint)} synchronizes on this object before changing it - * and when actually invalidating the cache. + * {@link #scheduleCacheRefresh(WindWithConfidence, TimePoint)} synchronizes on this object before changing it and + * when actually invalidating the cache. When a querying thread holds at least the read lock of {@link #cacheLock} + * and the refresh interval it {@link InvalidationInterval#isSet() is set}, an invalidation is currently running or + * has been scheduled. */ private final InvalidationInterval scheduledRefreshInterval; @@ -171,8 +248,11 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl return o1.getObject().getTimePoint().compareTo(o2.getObject().getTimePoint()); } }); - cacheLock = new NamedReentrantReadWriteLock(TrackBasedEstimationWindTrackImpl.class.getSimpleName()+" for race "+ - trackedRace.getRace().getName(), /* fair */ false); + cacheLock = new NamedReentrantReadWriteLock(TrackBasedEstimationWindTrackImpl.class.getSimpleName() + + " cacheLock for race " + trackedRace.getRace().getName(), /* fair */false); + scheduledRefreshIntervalLock = new NamedReentrantReadWriteLock( + TrackBasedEstimationWindTrackImpl.class.getSimpleName() + " scheduledRefreshIntervalLock for race " + + trackedRace.getRace().getName(), /* fair */false); virtualInternalRawFixes = new EstimatedWindFixesAsNavigableSet(trackedRace); weigher = ConfidenceFactory.INSTANCE .createHyperbolicTimeDifferenceWeigher(getMillisecondsOverWhichToAverageWind()); @@ -183,9 +263,11 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl } /** - * Synchronizes serialization on this object to avoid the cache being updated while being written. + * Uses the {@link #cacheLock} and {@link #scheduledRefreshIntervalLock} to make serialization on this object thread + * safe by avoiding the cache being updated while being written. */ - private synchronized void writeObject(ObjectOutputStream s) throws IOException { + private void writeObject(ObjectOutputStream s) throws IOException { + LockUtil.lockForRead(scheduledRefreshIntervalLock); LockUtil.lockForRead(cacheLock); lockForRead(); try { @@ -193,6 +275,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl } finally { unlockAfterRead(); LockUtil.unlockAfterRead(cacheLock); + LockUtil.unlockAfterRead(scheduledRefreshIntervalLock); } } @@ -227,29 +310,22 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl return cache; } + /** + * The caller must not hold the read lock of {@link #cacheLock} when calling this method (unless the caller + * also holds the corresponding write lock) because this method will acquire the corresponding write lock, and + * upgrading from a read lock to a write lock is not possible. + */ protected void cache(TimePoint timePoint, WindWithConfidence fix) { // can't use lockForWrite() here because caching can happen while holding the read lock, and the lock can't be // upgraded. But lockForRead() and synchronization will do the job because all invalidations lock the write lock, // and all contains() checks and get() calls use synchronization too. LockUtil.lockForWrite(cacheLock); try { - // synchronization necessary to protect writeObject from ConcurrentModificationException - synchronized (this) { - if (fix == null) { - synchronized (timePointsWithCachedNullResult) { - timePointsWithCachedNullResult.add(timePoint); - } - synchronized (timePointsWithCachedNullResultFastContains) { - timePointsWithCachedNullResultFastContains.add(timePoint); - } - } else { - LockUtil.lockForWrite(cacheLock); - try { - cache.add(fix); - } finally { - LockUtil.unlockAfterWrite(cacheLock); - } - } + if (fix == null) { + timePointsWithCachedNullResult.add(timePoint); + timePointsWithCachedNullResultFastContains.add(timePoint); + } else { + cache.add(fix); } } finally { LockUtil.unlockAfterWrite(cacheLock); @@ -263,19 +339,29 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl * will not be massively delayed by having to re-calculate the estimation over and over again. */ private void scheduleCacheRefresh(WindWithConfidence startOfInvalidation, TimePoint endOfInvalidation) { - LockUtil.lockForWrite(cacheLock); + final boolean alreadyContained; + LockUtil.lockForRead(scheduledRefreshIntervalLock); try { - if (!scheduledRefreshInterval.isSet()) { - // according to the invariant this implies [1]==null - scheduledRefreshInterval.set(startOfInvalidation, endOfInvalidation); - startSchedulerForCacheRefresh(); - } else { - // this means that an invalidation is already scheduled; as long as we're synchronized on scheduledInvalidationInterval - // we can safely extend the interval; the invalidation won't start before we release the lock - scheduledRefreshInterval.extend(startOfInvalidation, endOfInvalidation); - } + alreadyContained = scheduledRefreshInterval.contains(startOfInvalidation.getObject().getTimePoint(), endOfInvalidation); } finally { - LockUtil.unlockAfterWrite(cacheLock); + LockUtil.unlockAfterRead(scheduledRefreshIntervalLock); + } + if (!alreadyContained) { + LockUtil.lockForWrite(scheduledRefreshIntervalLock); + try { + if (!scheduledRefreshInterval.isSet()) { + // according to the invariant this implies [1]==null + scheduledRefreshInterval.set(startOfInvalidation, endOfInvalidation); + startSchedulerForCacheRefresh(); + } else { + // this means that an invalidation or incremental refresh is already scheduled; as long as we're + // synchronized on scheduledInvalidationInterval we can safely extend the interval; the invalidation + // won't start before we release the lock + scheduledRefreshInterval.extend(startOfInvalidation, endOfInvalidation); + } + } finally { + LockUtil.unlockAfterWrite(scheduledRefreshIntervalLock); + } } } @@ -285,6 +371,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl * running. */ private void invalidateCache() { + LockUtil.lockForWrite(scheduledRefreshIntervalLock); LockUtil.lockForWrite(cacheLock); try { Iterator> iter = (scheduledRefreshInterval.getStart() == null ? getCachedFixes() @@ -312,6 +399,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl scheduledRefreshInterval.clear(); } finally { LockUtil.unlockAfterWrite(cacheLock); + LockUtil.unlockAfterWrite(scheduledRefreshIntervalLock); } } @@ -321,27 +409,33 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl private void refreshCacheIncrementally() { Set> windFixesToRecalculate = new HashSet>(); Set cachedNullResultsToRecalculate = new HashSet(); + final WindWithConfidence refreshIntervalStart; + final TimePoint refreshIntervalEnd; + LockUtil.lockForRead(scheduledRefreshIntervalLock); LockUtil.lockForRead(cacheLock); try { - Iterator> iter = (scheduledRefreshInterval.getStart() == null ? getCachedFixes() - : getCachedFixes().tailSet(scheduledRefreshInterval.getStart(), /* inclusive */true)).iterator(); - Iterator nullIter = (scheduledRefreshInterval.getStart() == null ? timePointsWithCachedNullResult - : timePointsWithCachedNullResult.tailSet(scheduledRefreshInterval.getStart().getObject().getTimePoint(), /* inclusive */ + refreshIntervalStart = scheduledRefreshInterval.getStart(); + Iterator> iter = (refreshIntervalStart == null ? getCachedFixes() + : getCachedFixes().tailSet(refreshIntervalStart, /* inclusive */true)).iterator(); + Iterator nullIter = (refreshIntervalStart == null ? timePointsWithCachedNullResult + : timePointsWithCachedNullResult.tailSet(refreshIntervalStart.getObject().getTimePoint(), /* inclusive */ true)).iterator(); WindWithConfidence nextFixToRecalculate = null; + refreshIntervalEnd = scheduledRefreshInterval.getEnd(); while (iter.hasNext() && - ((nextFixToRecalculate = iter.next()).getObject().getTimePoint().compareTo(scheduledRefreshInterval.getEnd()) < 0) || - scheduledRefreshInterval.getEnd() == null) { + ((nextFixToRecalculate = iter.next()).getObject().getTimePoint().compareTo(refreshIntervalEnd) < 0) || + refreshIntervalEnd == null) { windFixesToRecalculate.add(nextFixToRecalculate); } TimePoint nextNullResultToRecalculate = null; while (nullIter.hasNext() && - ((nextNullResultToRecalculate = nullIter.next()).compareTo(scheduledRefreshInterval.getEnd()) < 0) || - scheduledRefreshInterval.getEnd() == null) { + ((nextNullResultToRecalculate = nullIter.next()).compareTo(refreshIntervalEnd) < 0) || + refreshIntervalEnd == null) { cachedNullResultsToRecalculate.add(nextNullResultToRecalculate); } } finally { LockUtil.unlockAfterRead(cacheLock); + LockUtil.unlockAfterRead(scheduledRefreshIntervalLock); } Set nullRemovals = new HashSet(); Set nullInsertions = new HashSet(); @@ -381,10 +475,22 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl for (Map.Entry> cacheInsertion : cacheInsertions.entrySet()) { cache(cacheInsertion.getKey(), cacheInsertion.getValue()); } - scheduledRefreshInterval.clear(); } finally { LockUtil.unlockAfterWrite(cacheLock); } + LockUtil.lockForWrite(scheduledRefreshIntervalLock); + try { + // now remove the interval for which the cache was refreshed; note that in between the interval may have been extended; + // for the extended part of the interval that hasn't been refreshed during this run, a new refresh needs to be + // requested + InvalidationInterval remainingRefreshInterval = scheduledRefreshInterval.subtract(refreshIntervalStart, refreshIntervalEnd); + scheduledRefreshInterval.clear(); + if (remainingRefreshInterval.isSet()) { + scheduleCacheRefresh(remainingRefreshInterval.getStart(), remainingRefreshInterval.getEnd()); + } + } finally { + LockUtil.unlockAfterWrite(scheduledRefreshIntervalLock); + } } private void startSchedulerForCacheRefresh() { @@ -452,15 +558,18 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl return result; } - private WindWithConfidence getDummyFixWithConfidence(TimePoint timePoint) { + private static WindWithConfidence getDummyFixWithConfidence(TimePoint timePoint) { return new WindWithConfidenceImpl(new WindImpl(null, timePoint, defaultSpeedWithBearing), 0, timePoint, /* useSpeed */false); } private boolean nullResultCacheContains(TimePoint timePoint) { assertReadLock(); - synchronized (timePointsWithCachedNullResultFastContains) { + LockUtil.lockForRead(cacheLock); + try { return timePointsWithCachedNullResultFastContains.contains(timePoint); + } finally { + LockUtil.unlockAfterRead(cacheLock); } } @@ -573,7 +682,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl assert fix != null && fix.getTimePoint() != null; // A mark position change can mean a leg type change. The interval over which the wind estimation is affected // depends on how the GPS track computes the estimated mark position. Ask it: - Pair interval = getTrackedRace().getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix); + Util.Pair interval = getTrackedRace().getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix); WindWithConfidence startOfInvalidation = getDummyFixWithConfidence(interval.getA()); TimePoint endOfInvalidation = interval.getB(); scheduleCacheRefresh(startOfInvalidation, endOfInvalidation); @@ -588,7 +697,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl * result type suggests it. */ @Override - protected WindWithConfidence> getAveragedWindUnsynchronized(Position p, TimePoint at) { + protected WindWithConfidence> getAveragedWindUnsynchronized(Position p, TimePoint at) { lockForRead(); try { TimePoint floorTimePoint = virtualInternalRawFixes.floorToResolution(at); @@ -603,10 +712,10 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl WindWithConfidence preResult = virtualInternalRawFixes.getWindWithConfidence(p, timePoint); // reduce confidence depending on how far *at* is away from the time point of the fix obtained double confidenceMultiplier = weigher.getConfidence(timePoint, at); - WindWithConfidenceImpl> result = preResult == null ? null - : new WindWithConfidenceImpl>(preResult.getObject(), confidenceMultiplier + WindWithConfidenceImpl> result = preResult == null ? null + : new WindWithConfidenceImpl>(preResult.getObject(), confidenceMultiplier * preResult.getConfidence(), - /* relativeTo */new Pair(p, at), preResult.useSpeed()); + /* relativeTo */new Util.Pair(p, at), preResult.useSpeed()); return result; } finally { unlockAfterRead(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java index 13ad89e02f4..fc4e3fb04ff 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java @@ -21,7 +21,6 @@ import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.MeterDistance; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.MarkPassing; @@ -31,6 +30,7 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.Wind; +import com.sap.sse.common.Util; public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { private static final long serialVersionUID = -1944668527284130545L; @@ -258,12 +258,19 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { } @Override - public Distance getCrossTrackError(Position p, TimePoint timePoint) { + public Distance getAbsoluteCrossTrackError(Position p, TimePoint timePoint) { final Position approximatePosition = getTrackedRace().getApproximatePosition(getLeg().getFrom(), timePoint); final Bearing legBearing = getLegBearing(timePoint); return approximatePosition==null || legBearing==null ? null : p.absoluteCrossTrackError(approximatePosition, legBearing); } + @Override + public Distance getSignedCrossTrackError(Position p, TimePoint timePoint) { + final Position approximatePosition = getTrackedRace().getApproximatePosition(getLeg().getFrom(), timePoint); + final Bearing legBearing = getLegBearing(timePoint); + return approximatePosition==null || legBearing==null ? null : p.crossTrackError(approximatePosition, legBearing); + } + @Override public Distance getGreatCircleDistance(TimePoint timePoint) { final Distance result; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java index 32703b3fc78..5c14d315541 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java @@ -1,768 +1,788 @@ -package com.sap.sailing.domain.tracking.impl; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.NavigableSet; - -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.common.Bearing; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.ManeuverType; -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.SpeedWithBearing; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.KnotSpeedImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.MeterDistance; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.tracking.GPSFixMoving; -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.TrackedLegOfCompetitor; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.util.impl.ArrayListNavigableSet; - -/** - * Provides a convenient view on the tracked leg, projecting to a single competitor's performance. - * - * @author Axel Uhl (d043530) - * - */ -public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { - private static final long serialVersionUID = -7060076837717432808L; - private final TrackedLegImpl trackedLeg; - private final Competitor competitor; - - public TrackedLegOfCompetitorImpl(TrackedLegImpl trackedLeg, Competitor competitor) { - this.trackedLeg = trackedLeg; - this.competitor = competitor; - } - - @Override - public TrackedLegImpl getTrackedLeg() { - return trackedLeg; - } - - @Override - public Competitor getCompetitor() { - return competitor; - } - - @Override - public Leg getLeg() { - return trackedLeg.getLeg(); - } - - private TrackedRace getTrackedRace() { - return getTrackedLeg().getTrackedRace(); - } - - @Override - public Long getTimeInMilliSeconds(TimePoint timePoint) { - Long result; - MarkPassing passedStartWaypoint = getTrackedRace().getMarkPassing(getCompetitor(), - getTrackedLeg().getLeg().getFrom()); - if (passedStartWaypoint != null) { - MarkPassing passedEndWaypoint = getTrackedRace().getMarkPassing(getCompetitor(), - getTrackedLeg().getLeg().getTo()); - if (passedEndWaypoint != null) { - result = passedEndWaypoint.getTimePoint().asMillis() - passedStartWaypoint.getTimePoint().asMillis(); - } else { - if (getTrackedRace().getEndOfTracking() != null && timePoint.after(getTrackedRace().getEndOfTracking())) { - result = null; - } else { - result = timePoint.asMillis() - passedStartWaypoint.getTimePoint().asMillis(); - } - } - } else { - result = null; - } - return result; - } - - @Override - public Distance getDistanceTraveled(TimePoint timePoint) { - MarkPassing legStart = getMarkPassingForLegStart(); - if (legStart == null) { - return null; - } else { - MarkPassing legEnd = getMarkPassingForLegEnd(); - TimePoint end = timePoint; - if (legEnd != null && timePoint.compareTo(legEnd.getTimePoint()) > 0) { - // timePoint is after leg finish; take leg end and end time point - end = legEnd.getTimePoint(); - } - return getTrackedRace().getTrack(getCompetitor()).getDistanceTraveled(legStart.getTimePoint(), end); - } - } - - private MarkPassing getMarkPassingForLegStart() { - MarkPassing legStart = getTrackedRace().getMarkPassing(getCompetitor(), getLeg().getFrom()); - return legStart; - } - - private MarkPassing getMarkPassingForLegEnd() { - MarkPassing legEnd = getTrackedRace().getMarkPassing(getCompetitor(), getLeg().getTo()); - return legEnd; - } - - @Override - public Speed getAverageSpeedOverGround(TimePoint timePoint) { - Speed result; - MarkPassing legStart = getMarkPassingForLegStart(); - if (legStart == null) { - result = null; - } else { - TimePoint timePointToUse; - if (hasFinishedLeg(timePoint)) { - timePointToUse = getMarkPassingForLegEnd().getTimePoint(); - } else { - // use time point of latest fix if before timePoint, otherwise timePoint - GPSFixMoving lastFix = getTrackedRace().getTrack(getCompetitor()).getLastRawFix(); - if (lastFix == null) { - // No fix at all? Then we can't determine any speed - timePointToUse = null; - } else if (lastFix.getTimePoint().compareTo(timePoint) < 0) { - timePointToUse = lastFix.getTimePoint(); - } else { - timePointToUse = timePoint; - } - } - if (timePointToUse != null) { - Distance d = getDistanceTraveled(timePointToUse); - long millis = timePointToUse.asMillis() - legStart.getTimePoint().asMillis(); - result = d.inTime(millis); - } else { - result = null; - } - } - return result; - } - - @Override - public Pair getMaximumSpeedOverGround(TimePoint timePoint) { - // fetch all fixes on this leg so far and determine their maximum speed - MarkPassing legStart = getMarkPassingForLegStart(); - if (legStart == null) { - return null; - } - MarkPassing legEnd = getMarkPassingForLegEnd(); - TimePoint to; - if (legEnd == null || legEnd.getTimePoint().compareTo(timePoint) >= 0) { - to = timePoint; - } else { - to = legEnd.getTimePoint(); - } - GPSFixTrack track = getTrackedRace().getTrack(getCompetitor()); - return track.getMaximumSpeedOverGround(legStart.getTimePoint(), to); - } - - @Override - public Distance getWindwardDistanceToGo(TimePoint timePoint) throws NoWindException { - if (hasFinishedLeg(timePoint)) { - return Distance.NULL; - } else { - Distance result = null; - for (Mark mark : getLeg().getTo().getMarks()) { - Distance d = getWindwardDistanceTo(mark, timePoint); - if (result == null || d != null && d.compareTo(result) < 0) { - result = d; - } - } - return result; - } - } - - /** - * If the current {@link #getLeg() leg} is +/- {@link #UPWIND_DOWNWIND_TOLERANCE_IN_DEG} degrees collinear with the - * wind's bearing, the competitor's position is projected onto the line crossing mark in the wind's - * bearing, and the distance from the projection to the mark is returned. Otherwise, it is assumed that - * the leg is neither an upwind nor a downwind leg, and hence the true distance to mark is returned. - */ - private Distance getWindwardDistanceTo(Mark mark, TimePoint at) throws NoWindException { - Position estimatedPosition = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false); - if (!hasStartedLeg(at) || estimatedPosition == null) { - // covers the case with no fixes for this leg yet, also if the mark passing has already been received - estimatedPosition = getTrackedRace().getOrCreateTrack(getLeg().getFrom().getMarks().iterator().next()) - .getEstimatedPosition(at, false); - } - if (estimatedPosition == null) { // may happen if mark positions haven't been received yet - return null; - } - final Position estimatedMarkPosition = getTrackedRace().getOrCreateTrack(mark).getEstimatedPosition(at, false); - if (estimatedMarkPosition == null) { - return null; - } - return getTrackedLeg().getAbsoluteWindwardDistance(estimatedPosition, estimatedMarkPosition, at); - } - - /** - * Projects speed onto the wind direction for upwind/downwind legs to see how fast a boat travels - * "along the wind's direction." For reaching legs (neither upwind nor downwind), the speed is projected onto - * the leg's direction. - * - * @throws NoWindException in case the wind direction is not known - */ - private SpeedWithBearing getWindwardSpeed(SpeedWithBearing speed, TimePoint at) throws NoWindException { - SpeedWithBearing result = null; - if (speed != null) { - Bearing projectToBearing; - if (getTrackedLeg().isUpOrDownwindLeg(at)) { - Wind wind = getWind(getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false), at); - if (wind == null) { - throw new NoWindException("Need at least wind direction to determine windward speed"); - } - projectToBearing = wind.getBearing(); - } else { - projectToBearing = getTrackedLeg().getLegBearing(at); - } - double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians()); - if (cos < 0) { - projectToBearing = projectToBearing.reverse(); - } - result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing); - } - return result; - } - - /** - * For now, we have an incredibly simple wind "model" which assigns a single common wind force and bearing - * to all positions on the course, only variable over time. - */ - Wind getWind(Position p, TimePoint at) { - return getTrackedRace().getWind(p, at); - } - - @Override - public int getRank(TimePoint timePoint) { - int result = 0; - if (hasStartedLeg(timePoint)) { - List competitorTracksByRank = getTrackedLeg().getCompetitorTracksOrderedByRank(timePoint); - result = competitorTracksByRank.indexOf(this)+1; - } - return result; - } - - @Override - public Speed getAverageVelocityMadeGood(TimePoint timePoint) throws NoWindException { - Speed result = null; - MarkPassing start = getMarkPassingForLegStart(); - if (start != null && start.getTimePoint().compareTo(timePoint) <= 0) { - MarkPassing end = getMarkPassingForLegEnd(); - if (end != null) { - TimePoint to; - if (timePoint.compareTo(end.getTimePoint()) >= 0) { - to = end.getTimePoint(); - } else { - to = timePoint; - } - Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, /* extrapolate */ false); - if (endPos != null) { - Distance d = getTrackedLeg().getAbsoluteWindwardDistance( - getTrackedRace().getTrack(getCompetitor()) - .getEstimatedPosition(start.getTimePoint(), false), endPos, to); - result = d.inTime(to.asMillis() - start.getTimePoint().asMillis()); - } - } - } - return result; - } - - - @Override - public Integer getNumberOfTacks(TimePoint timePoint) throws NoWindException { - Integer result = null; - if (hasStartedLeg(timePoint)) { - List maneuvers = getManeuvers(timePoint, /* waitForLatest */ true); - result = 0; - for (Maneuver maneuver : maneuvers) { - if (maneuver.getType() == ManeuverType.TACK) { - result++; - } - } - } - return result; - } - - @Override - public List getManeuvers(TimePoint timePoint, boolean waitForLatest) throws NoWindException { - MarkPassing legEnd = getMarkPassingForLegEnd(); - TimePoint end = timePoint; - if (legEnd != null && timePoint.compareTo(legEnd.getTimePoint()) > 0) { - // timePoint is after leg finish; take leg end and end time point - end = legEnd.getTimePoint(); - } - List maneuvers = getTrackedRace().getManeuvers(getCompetitor(), - getMarkPassingForLegStart().getTimePoint(), end, waitForLatest); - return maneuvers; - } - - @Override - public Integer getNumberOfJibes(TimePoint timePoint) throws NoWindException { - Integer result = null; - if (hasStartedLeg(timePoint)) { - List maneuvers = getManeuvers(timePoint, /* waitForLatest */ true); - result = 0; - for (Maneuver maneuver : maneuvers) { - if (maneuver.getType() == ManeuverType.JIBE) { - result++; - } - } - } - return result; - } - - @Override - public Integer getNumberOfPenaltyCircles(TimePoint timePoint) throws NoWindException { - Integer result = null; - if (hasStartedLeg(timePoint)) { - List maneuvers = getManeuvers(timePoint, /* waitForLatest */ true); - result = 0; - for (Maneuver maneuver : maneuvers) { - if (maneuver.getType() == ManeuverType.PENALTY_CIRCLE) { - result++; - } - } - } - return result; - } - - @Override - public Distance getWindwardDistanceToOverallLeader(TimePoint timePoint) throws NoWindException { - // FIXME bug 607 it seems the following fetches the leader of this leg, not the overall leader; validate!!! Use getTrackedRace().getRanks() instead - Competitor leader = getTrackedRace().getOverallLeader(timePoint); - TrackedLegOfCompetitor leaderLeg = getTrackedRace().getCurrentLeg(leader, timePoint); - Distance result = null; - Position leaderPosition = getTrackedRace().getTrack(leader).getEstimatedPosition(timePoint, /* extrapolate */ false); - Position currentPosition = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(timePoint, /* extrapolate */ false); - if (leaderPosition != null && currentPosition != null) { - result = Distance.NULL; - boolean foundCompetitorsLeg = false; - getTrackedRace().getRace().getCourse().lockForRead(); - try { - for (Leg leg : getTrackedRace().getRace().getCourse().getLegs()) { - if (leg == getLeg()) { - foundCompetitorsLeg = true; - } - if (foundCompetitorsLeg) { - // if the leaderLeg is null, the leader has already arrived - if (leaderLeg == null || leg != leaderLeg.getLeg()) { - // add distance to next mark - Position nextMarkPosition = getTrackedRace().getApproximatePosition(leg.getTo(), timePoint); - Distance distanceToNextMark = getTrackedRace().getTrackedLeg(leg) - .getAbsoluteWindwardDistance(currentPosition, nextMarkPosition, timePoint); - result = new MeterDistance(result.getMeters() + distanceToNextMark.getMeters()); - currentPosition = nextMarkPosition; - } else { - // we're now in the same leg with leader; compute windward distance to leader - result = new MeterDistance(result.getMeters() - + getTrackedRace().getTrackedLeg(leg) - .getAbsoluteWindwardDistance(currentPosition, leaderPosition, timePoint) - .getMeters()); - break; - } - } - } - } finally { - getTrackedRace().getRace().getCourse().unlockAfterRead(); - } - } - return result; - } - - @Override - public Distance getAverageCrossTrackError(TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { - Distance result = null; - final MarkPassing legStartMarkPassing = getTrackedRace().getMarkPassing(competitor, getLeg().getFrom()); - if (legStartMarkPassing != null) { - TimePoint legStart = legStartMarkPassing.getTimePoint(); - final MarkPassing legEndMarkPassing = getTrackedRace().getMarkPassing(competitor, getLeg().getTo()); - TimePoint to; - if (legEndMarkPassing == null || legEndMarkPassing.getTimePoint().compareTo(timePoint) > 0) { - to = timePoint; - } else { - to = legEndMarkPassing.getTimePoint(); - } - result = getTrackedRace().getAverageCrossTrackError(competitor, legStart, to, /* upwindOnly */ false, waitForLatestAnalysis); - } - return result; - } - - @Override - public Double getGapToLeaderInSeconds(TimePoint timePoint, final Competitor leaderInLegAtTimePoint) - throws NoWindException { - return getGapToLeaderInSeconds(timePoint, new LeaderGetter() { - @Override - public Competitor getLeader() { - return leaderInLegAtTimePoint; - } - }); - } - - private static interface LeaderGetter { - Competitor getLeader(); - } - - @Override - public Double getGapToLeaderInSeconds(final TimePoint timePoint) throws NoWindException { - return getGapToLeaderInSeconds(timePoint, new LeaderGetter() { - @Override - public Competitor getLeader() { - return getTrackedLeg().getLeader(hasFinishedLeg(timePoint) ? getFinishTime() : timePoint); - } - }); - } - - private Double getGapToLeaderInSeconds(TimePoint timePoint, LeaderGetter leaderGetter) throws NoWindException { - // If the leader already completed this leg, compute the estimated arrival time at the - // end of this leg; if this leg's competitor also already finished the leg, return the - // difference between this competitor's leg completion time point and the leader's completion - // time point; else, calculate the windward distance to the leader and divide by - // the windward speed - Speed windwardSpeed = getWindwardSpeed(getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(timePoint), timePoint); - Double result = null; - // Has our competitor started the leg already? If not, we won't be able to compute a gap - if (hasStartedLeg(timePoint)) { - Iterable markPassingsInOrder = getTrackedRace().getMarkPassingsInOrder(getLeg().getTo()); - if (markPassingsInOrder != null) { - MarkPassing firstMarkPassing = null; - getTrackedRace().lockForRead(markPassingsInOrder); - try { - Iterator markPassingsForLegEnd = markPassingsInOrder.iterator(); - if (markPassingsForLegEnd.hasNext()) { - firstMarkPassing = markPassingsForLegEnd.next(); - } - } finally { - getTrackedRace().unlockAfterRead(markPassingsInOrder); - } - if (firstMarkPassing != null) { - // someone has already finished the leg - TimePoint whenLeaderFinishedLeg = firstMarkPassing.getTimePoint(); - // Was it before the requested timePoint? - if (whenLeaderFinishedLeg.compareTo(timePoint) <= 0) { - // Has our competitor also already finished this leg? - if (hasFinishedLeg(timePoint)) { - // Yes, so the gap is the time period between the time points at which the leader and - // our competitor finished this leg. - return (getMarkPassingForLegEnd().getTimePoint().asMillis() - whenLeaderFinishedLeg - .asMillis()) / 1000.; - } else { - if (windwardSpeed == null) { - return null; - } else { - // leader has finished already; our competitor hasn't - Distance windwardDistanceToGo = getWindwardDistanceToGo(timePoint); - long millisSinceLeaderPassedMarkToTimePoint = timePoint.asMillis() - - whenLeaderFinishedLeg.asMillis(); - return windwardDistanceToGo.getMeters() / windwardSpeed.getMetersPerSecond() - + millisSinceLeaderPassedMarkToTimePoint / 1000.; - } - } - } - } - // no-one has finished this leg yet at timePoint - Competitor leader = leaderGetter.getLeader(); - // Maybe our competitor is the leader. Check: - if (leader == getCompetitor()) { - return 0.0; // the leader's gap to the leader - } else { - if (windwardSpeed == null) { - return null; - } else { - // no, we're not the leader, so compute our windward distance and divide by our current VMG - Position ourEstimatedPosition = getTrackedRace().getTrack(getCompetitor()) - .getEstimatedPosition(timePoint, false); - Position leaderEstimatedPosition = getTrackedRace().getTrack(leader).getEstimatedPosition( - timePoint, false); - if (ourEstimatedPosition == null || leaderEstimatedPosition == null) { - return null; - } else { - Distance windwardDistanceToGo = getTrackedLeg().getAbsoluteWindwardDistance(ourEstimatedPosition, - leaderEstimatedPosition, timePoint); - return windwardDistanceToGo.getMeters() / windwardSpeed.getMetersPerSecond(); - } - } - } - } - } - // else our competitor hasn't started the leg yet, so we can't compute a gap since we don't - // have a speed estimate; leave result == null - return result; - } - - @Override - public boolean hasStartedLeg(TimePoint timePoint) { - MarkPassing markPassingForLegStart = getMarkPassingForLegStart(); - return markPassingForLegStart != null && markPassingForLegStart.getTimePoint().compareTo(timePoint) <= 0; - } - - @Override - public boolean hasFinishedLeg(TimePoint timePoint) { - MarkPassing markPassingForLegEnd = getMarkPassingForLegEnd(); - return markPassingForLegEnd != null && markPassingForLegEnd.getTimePoint().compareTo(timePoint) <= 0; - } - - @Override - public TimePoint getStartTime() { - MarkPassing markPassingForLegStart = getMarkPassingForLegStart(); - return markPassingForLegStart == null ? null : markPassingForLegStart.getTimePoint(); - } - - @Override - public TimePoint getFinishTime() { - MarkPassing markPassingForLegEnd = getMarkPassingForLegEnd(); - return markPassingForLegEnd == null ? null : markPassingForLegEnd.getTimePoint(); - } - - @Override - public Speed getVelocityMadeGood(TimePoint at) throws NoWindException { - if (hasStartedLeg(at)) { - TimePoint timePoint; - if (hasFinishedLeg(at)) { - // use the leg finishing time point - timePoint = getMarkPassingForLegEnd().getTimePoint(); - } else { - timePoint = at; - } - SpeedWithBearing speedOverGround = getSpeedOverGround(timePoint); - return speedOverGround == null ? null : getWindwardSpeed(speedOverGround, timePoint); - } else { - return null; - } - } - - @Override - public SpeedWithBearing getSpeedOverGround(TimePoint at) { - if (hasStartedLeg(at)) { - TimePoint timePoint; - if (hasFinishedLeg(at)) { - // use the leg finishing time point - timePoint = getMarkPassingForLegEnd().getTimePoint(); - } else { - timePoint = at; - } - return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(timePoint); - } else { - return null; - } - } - - @Override - public Double getEstimatedTimeToNextMarkInSeconds(TimePoint timePoint) throws NoWindException { - Double result; - if (hasFinishedLeg(timePoint)) { - result = 0.0; - } else { - if (hasStartedLeg(timePoint)) { - Distance windwardDistanceToGo = getWindwardDistanceToGo(timePoint); - Speed vmg = getVelocityMadeGood(timePoint); - result = vmg == null ? null : windwardDistanceToGo.getMeters() / vmg.getMetersPerSecond(); - } else { - result = null; - } - } - return result; - } - - @Override - public Distance getManeuverLoss(TimePoint timePointBeforeManeuver, - TimePoint maneuverTimePoint, TimePoint timePointAfterManeuver) throws NoWindException { - assert timePointBeforeManeuver != null; - assert timePointAfterManeuver != null; - Distance result; - final GPSFixTrack track = getTrackedRace().getTrack(getCompetitor()); - List fixes = getFixesToConsiderForManeuverLossAnalysis(timePointBeforeManeuver, - maneuverTimePoint, timePointAfterManeuver); - TimePoint timePointWhenSpeedStartedToDrop = fixes.get(0).getTimePoint(); - SpeedWithBearing speedWhenSpeedStartedToDrop = track.getEstimatedSpeed(timePointWhenSpeedStartedToDrop); - if (speedWhenSpeedStartedToDrop != null) { - TimePoint timePointWhenSpeedLevelledOffAfterManeuver = fixes.get(fixes.size()-1).getTimePoint(); - SpeedWithBearing speedAfterManeuver = track.getEstimatedSpeed(timePointWhenSpeedLevelledOffAfterManeuver); - if (speedAfterManeuver != null) { - // For upwind/downwind legs, find the mean course between inbound and outbound course and project actual and - // extrapolated positions onto it: - Bearing middleManeuverAngle = speedWhenSpeedStartedToDrop.getBearing().middle(speedAfterManeuver.getBearing()); - // extrapolate maximum speed before maneuver to time point of maximum speed after maneuver and project resulting position - // onto the average maneuver course; compare to the projected position actually reached at the time point of maximum speed after - // maneuver: - Position positionWhenSpeedStartedToDrop = track.getEstimatedPosition(timePointWhenSpeedStartedToDrop, /* extrapolate */ false); - Position extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = - speedWhenSpeedStartedToDrop.travelTo(positionWhenSpeedStartedToDrop, timePointWhenSpeedStartedToDrop, timePointWhenSpeedLevelledOffAfterManeuver); - Position actualPositionAtTimePointOfMaxSpeedAfterManeuver = track.getEstimatedPosition(timePointWhenSpeedLevelledOffAfterManeuver, /* extrapolate */ false); - Position projectedExtrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = - extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver.projectToLineThrough(positionWhenSpeedStartedToDrop, middleManeuverAngle); - Position projectedActualPositionAtTimePointOfMaxSpeedAfterManeuver = - actualPositionAtTimePointOfMaxSpeedAfterManeuver.projectToLineThrough(positionWhenSpeedStartedToDrop, middleManeuverAngle); - result = projectedActualPositionAtTimePointOfMaxSpeedAfterManeuver.getDistance(projectedExtrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver); - } else { - result = null; - } - } else { - result = null; - } - return result; - } - - /** - * Fetches the set of fixes that encompass the maneuver. Usually, during a maneuver a boat loses speed over ground - * before it has reached the new tack and starts accelerating again, until the speed levels off. We assume that the - * timePointBeforeManeuver minus some excess time based on the approximate maneuver duration is the - * earliest time point to analyze. Starting there, we look for speed over ground minima between then and - * timePointAfterManeuver plus three times the approximate maneuver time as excess time. There may be - * multiple minima. We choose the one that has the best "fit" in terms of being close to the - * maneuverTimePoint and being low in terms of speed over ground. From that time point, the nearest - * maximum speeds over ground before and after are determined, and the fixes between them are returned. - */ - private List getFixesToConsiderForManeuverLossAnalysis(TimePoint timePointBeforeManeuver, - TimePoint maneuverTimePoint, TimePoint timePointAfterManeuver) { - final long EXCESS_TIME_BEFORE_MANEUVER_END_TO_SCAN_IN_MILLIS = getCompetitor().getBoat().getBoatClass().getApproximateManeuverDurationInMilliseconds(); - final long EXCESS_TIME_AFTER_MANEUVER_END_TO_SCAN_IN_MILLIS = 3*EXCESS_TIME_BEFORE_MANEUVER_END_TO_SCAN_IN_MILLIS; - List fixes = new ArrayList<>(); - NavigableSet maxima = new ArrayListNavigableSet(new TimedComparator()); - NavigableSet minima = new ArrayListNavigableSet(new TimedComparator()); - GPSFixTrack track = getTrackedRace().getTrack(getCompetitor()); - Speed lastSpeed = Speed.NULL; - Speed lastLastSpeed = Speed.NULL; - GPSFixMoving lastFix = null; - Speed minimumSpeed = new KnotSpeedImpl(Double.MAX_VALUE); - track.lockForRead(); - try { - Iterator fixIter = track.getFixesIterator( - timePointBeforeManeuver.minus(EXCESS_TIME_BEFORE_MANEUVER_END_TO_SCAN_IN_MILLIS), /* inclusive */true); - GPSFixMoving fix; - // The timePointAfterManeuver is determined based on the geometric shape of the boat's trajectory, not on - // the speed development. To understand the full maneuver loss, we need to follow the boat speed until it levels off, - // but no further than some reasonable threshold because the wind may continue to pick up, letting the boat accelerate - // over a time much longer than accounted for by the maneuver. - while (fixIter.hasNext()) { - fix = fixIter.next(); - final SpeedWithBearing estimatedSpeedAtFix = track.getEstimatedSpeed(fix.getTimePoint()); - if (lastFix != null && lastSpeed != null && lastLastSpeed != null && estimatedSpeedAtFix != null) { - if (lastSpeed.compareTo(lastLastSpeed) > 0 && lastSpeed.compareTo(estimatedSpeedAtFix) > 0) { - maxima.add(lastFix); - } else if (lastSpeed.compareTo(lastLastSpeed) < 0 && lastSpeed.compareTo(estimatedSpeedAtFix) < 0) { - minima.add(lastFix); - if (lastSpeed.compareTo(minimumSpeed) < 0) { - minimumSpeed = lastSpeed; - } - } - } - if (fix.getTimePoint().after(timePointAfterManeuver.plus(EXCESS_TIME_AFTER_MANEUVER_END_TO_SCAN_IN_MILLIS))) { - break; - } - fixes.add(fix); - lastLastSpeed = lastSpeed; - lastSpeed = estimatedSpeedAtFix; - lastFix = fix; - } - } finally { - track.unlockAfterRead(); - } - GPSFixMoving fixWithLowestSpeedOverGround = getBestFittingSpeedMinimumInManeuver(minima, maxima, minimumSpeed, maneuverTimePoint); - if (fixWithLowestSpeedOverGround == null) { - fixWithLowestSpeedOverGround = fixes.get(0); - } - // now remove all fixes before the last maximum before the fix with the lowest speed over ground during the maneuver if - // there was such a maximum; otherwise, leave all fixes from the maneuver start on in place. - final long MAX_SMOOTHENING_INTERVAL_MILLIS = getCompetitor().getBoat().getBoatClass().getApproximateManeuverDurationInMilliseconds(); - GPSFixMoving lastMaxSpeedFixBeforeLowSpeed = maxima.lower(fixWithLowestSpeedOverGround); - // now check if there's a greater one that's only a little bit earlier - if (lastMaxSpeedFixBeforeLowSpeed != null) { - GPSFixMoving stillGreater; - while ((stillGreater=maxima.lower(lastMaxSpeedFixBeforeLowSpeed)) != null && - track.getEstimatedSpeed(stillGreater.getTimePoint()).compareTo( - track.getEstimatedSpeed(lastMaxSpeedFixBeforeLowSpeed.getTimePoint())) > 0 && - lastMaxSpeedFixBeforeLowSpeed.getTimePoint().asMillis()- - stillGreater.getTimePoint().asMillis() < MAX_SMOOTHENING_INTERVAL_MILLIS) { - lastMaxSpeedFixBeforeLowSpeed = stillGreater; - } - Iterator i = fixes.iterator(); - while (i.hasNext() && i.next().getTimePoint().before(lastMaxSpeedFixBeforeLowSpeed.getTimePoint())) { - i.remove(); - } - } - // now remove all fixes after the first maximum after the global minimum: - GPSFixMoving firstMaxSpeedFixAfterLowSpeed = maxima.higher(fixWithLowestSpeedOverGround); - if (firstMaxSpeedFixAfterLowSpeed != null) { - GPSFixMoving stillGreater; - while ((stillGreater=maxima.higher(firstMaxSpeedFixAfterLowSpeed)) != null && - track.getEstimatedSpeed(stillGreater.getTimePoint()).compareTo( - track.getEstimatedSpeed(firstMaxSpeedFixAfterLowSpeed.getTimePoint())) > 0 && - stillGreater.getTimePoint().asMillis() - firstMaxSpeedFixAfterLowSpeed.getTimePoint().asMillis() - < MAX_SMOOTHENING_INTERVAL_MILLIS) { - firstMaxSpeedFixAfterLowSpeed = stillGreater; - } - ListIterator i = fixes.listIterator(fixes.size()); - while (i.hasPrevious() && i.previous().getTimePoint().after(firstMaxSpeedFixAfterLowSpeed.getTimePoint())) { - i.remove(); - } - } - return fixes; - } - - private GPSFixMoving getBestFittingSpeedMinimumInManeuver(NavigableSet minima, - NavigableSet maxima, Speed minimumSpeed, TimePoint maneuverTimePoint) { - // Idea: being 10x the approximate maneuver duration away from maneuverTimePoint is as bad as having twice the percentage between min - // and max speed (min = 0%; max=100%). - GPSFixMoving bestSpeedMinimum = null; - Speed maxSpeed = null; - for (GPSFixMoving maximum : maxima) { - if (maxSpeed == null || maximum.getSpeed().getKnots() > maxSpeed.getKnots()) { - maxSpeed = maximum.getSpeed(); - } - } - if (maxSpeed != null) { - double speedDifferenceBetweenMaxAndMinInKnots = maxSpeed.getKnots() - minimumSpeed.getKnots(); - double lowestBadness = Double.MAX_VALUE; - final long approximateManeuverTimeInMillis = getCompetitor().getBoat().getBoatClass() - .getApproximateManeuverDurationInMilliseconds(); - final GPSFixTrack track = getTrackedRace().getTrack(getCompetitor()); - for (GPSFixMoving minimum : minima) { - // best speedBadness can be 1 which represents the absolute speed minimum in the interval considered - double speedBadness = 1 + (track.getEstimatedSpeed(minimum.getTimePoint()).getKnots() - minimumSpeed.getKnots()) / - speedDifferenceBetweenMaxAndMinInKnots; - // best timePointBadness can be 1 which is a minimum exactly at the maneuver time point - double timePointBadness = 1. - + (double) Math.abs(minimum.getTimePoint().asMillis() - maneuverTimePoint.asMillis()) - / (double) approximateManeuverTimeInMillis / 10.; - final double totalBadness = speedBadness * timePointBadness; - if (totalBadness < lowestBadness) { - bestSpeedMinimum = minimum; - lowestBadness = totalBadness; - } - } - } - return bestSpeedMinimum; - } - - @Override - public Bearing getBeatAngle(TimePoint at) throws NoWindException { - Bearing beatAngle = null; - - Bearing projectToBearing; - if (getTrackedLeg().isUpOrDownwindLeg(at)) { - Wind wind = getWind(getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false), at); - if (wind == null) { - throw new NoWindException("Need at least wind direction to determine windward speed"); - } - projectToBearing = wind.getBearing(); - } else { - projectToBearing = getTrackedLeg().getLegBearing(at); - } - - SpeedWithBearing speed = getSpeedOverGround(at); - if (speed != null) { - beatAngle = projectToBearing.getDifferenceTo(speed.getBearing()); - } - - return beatAngle; - } -} +package com.sap.sailing.domain.tracking.impl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NavigableSet; + +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.common.Bearing; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Duration; +import com.sap.sailing.domain.common.ManeuverType; +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.SpeedWithBearing; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.tracking.GPSFixMoving; +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.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.util.impl.ArrayListNavigableSet; +import com.sap.sse.common.Util; + +/** + * Provides a convenient view on the tracked leg, projecting to a single competitor's performance. + * + * @author Axel Uhl (d043530) + * + */ +public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { + private static final long serialVersionUID = -7060076837717432808L; + private final TrackedLegImpl trackedLeg; + private final Competitor competitor; + + public TrackedLegOfCompetitorImpl(TrackedLegImpl trackedLeg, Competitor competitor) { + this.trackedLeg = trackedLeg; + this.competitor = competitor; + } + + @Override + public TrackedLegImpl getTrackedLeg() { + return trackedLeg; + } + + @Override + public Competitor getCompetitor() { + return competitor; + } + + @Override + public Leg getLeg() { + return trackedLeg.getLeg(); + } + + private TrackedRace getTrackedRace() { + return getTrackedLeg().getTrackedRace(); + } + + @Override + public Duration getTime(TimePoint timePoint) { + Duration result; + MarkPassing passedStartWaypoint = getTrackedRace().getMarkPassing(getCompetitor(), + getTrackedLeg().getLeg().getFrom()); + if (passedStartWaypoint != null && !passedStartWaypoint.getTimePoint().after(timePoint)) { + MarkPassing passedEndWaypoint = getTrackedRace().getMarkPassing(getCompetitor(), + getTrackedLeg().getLeg().getTo()); + if (passedEndWaypoint != null && timePoint.after(passedEndWaypoint.getTimePoint())) { + // the query asks for a time point after the competitor has finished the leg; return the total leg time + result = passedStartWaypoint.getTimePoint().until(passedEndWaypoint.getTimePoint()); + } else { + if (getTrackedRace().getEndOfTracking() != null && timePoint.after(getTrackedRace().getEndOfTracking())) { + result = null; + } else { + result = passedStartWaypoint.getTimePoint().until(timePoint); + } + } + } else { + result = null; + } + return result; + } + + @Override + public Distance getDistanceTraveled(TimePoint timePoint) { + MarkPassing legStart = getMarkPassingForLegStart(); + if (legStart == null) { + return null; + } else { + MarkPassing legEnd = getMarkPassingForLegEnd(); + TimePoint end = timePoint; + if (legEnd != null && timePoint.compareTo(legEnd.getTimePoint()) > 0) { + // timePoint is after leg finish; take leg end and end time point + end = legEnd.getTimePoint(); + } + return getTrackedRace().getTrack(getCompetitor()).getDistanceTraveled(legStart.getTimePoint(), end); + } + } + + private MarkPassing getMarkPassingForLegStart() { + MarkPassing legStart = getTrackedRace().getMarkPassing(getCompetitor(), getLeg().getFrom()); + return legStart; + } + + private MarkPassing getMarkPassingForLegEnd() { + MarkPassing legEnd = getTrackedRace().getMarkPassing(getCompetitor(), getLeg().getTo()); + return legEnd; + } + + @Override + public Speed getAverageSpeedOverGround(TimePoint timePoint) { + Speed result; + MarkPassing legStart = getMarkPassingForLegStart(); + if (legStart == null) { + result = null; + } else { + TimePoint timePointToUse; + if (hasFinishedLeg(timePoint)) { + timePointToUse = getMarkPassingForLegEnd().getTimePoint(); + } else { + // use time point of latest fix if before timePoint, otherwise timePoint + GPSFixMoving lastFix = getTrackedRace().getTrack(getCompetitor()).getLastRawFix(); + if (lastFix == null) { + // No fix at all? Then we can't determine any speed + timePointToUse = null; + } else if (lastFix.getTimePoint().compareTo(timePoint) < 0) { + timePointToUse = lastFix.getTimePoint(); + } else { + timePointToUse = timePoint; + } + } + if (timePointToUse != null) { + Distance d = getDistanceTraveled(timePointToUse); + long millis = timePointToUse.asMillis() - legStart.getTimePoint().asMillis(); + result = d.inTime(millis); + } else { + result = null; + } + } + return result; + } + + @Override + public Util.Pair getMaximumSpeedOverGround(TimePoint timePoint) { + // fetch all fixes on this leg so far and determine their maximum speed + MarkPassing legStart = getMarkPassingForLegStart(); + if (legStart == null) { + return null; + } + MarkPassing legEnd = getMarkPassingForLegEnd(); + TimePoint to; + if (legEnd == null || legEnd.getTimePoint().compareTo(timePoint) >= 0) { + to = timePoint; + } else { + to = legEnd.getTimePoint(); + } + GPSFixTrack track = getTrackedRace().getTrack(getCompetitor()); + return track.getMaximumSpeedOverGround(legStart.getTimePoint(), to); + } + + @Override + public Distance getWindwardDistanceToGo(TimePoint timePoint) throws NoWindException { + if (hasFinishedLeg(timePoint)) { + return Distance.NULL; + } else { + Distance result = null; + for (Mark mark : getLeg().getTo().getMarks()) { + Distance d = getWindwardDistanceTo(mark, timePoint); + if (result == null || d != null && d.compareTo(result) < 0) { + result = d; + } + } + return result; + } + } + + /** + * If the current {@link #getLeg() leg} is +/- {@link #UPWIND_DOWNWIND_TOLERANCE_IN_DEG} degrees collinear with the + * wind's bearing, the competitor's position is projected onto the line crossing mark in the wind's + * bearing, and the distance from the projection to the mark is returned. Otherwise, it is assumed that + * the leg is neither an upwind nor a downwind leg, and hence the true distance to mark is returned. + */ + private Distance getWindwardDistanceTo(Mark mark, TimePoint at) throws NoWindException { + Position estimatedPosition = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false); + if (!hasStartedLeg(at) || estimatedPosition == null) { + // covers the case with no fixes for this leg yet, also if the mark passing has already been received + estimatedPosition = getTrackedRace().getOrCreateTrack(getLeg().getFrom().getMarks().iterator().next()) + .getEstimatedPosition(at, false); + } + if (estimatedPosition == null) { // may happen if mark positions haven't been received yet + return null; + } + final Position estimatedMarkPosition = getTrackedRace().getOrCreateTrack(mark).getEstimatedPosition(at, false); + if (estimatedMarkPosition == null) { + return null; + } + return getTrackedLeg().getAbsoluteWindwardDistance(estimatedPosition, estimatedMarkPosition, at); + } + + /** + * Projects speed onto the wind direction for upwind/downwind legs to see how fast a boat travels + * "along the wind's direction." For reaching legs (neither upwind nor downwind), the speed is projected onto + * the leg's direction. + * + * @throws NoWindException in case the wind direction is not known + */ + private SpeedWithBearing getWindwardSpeed(SpeedWithBearing speed, TimePoint at) throws NoWindException { + SpeedWithBearing result = null; + if (speed != null) { + Bearing projectToBearing; + if (getTrackedLeg().isUpOrDownwindLeg(at)) { + Wind wind = getWind(getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false), at); + if (wind == null) { + throw new NoWindException("Need at least wind direction to determine windward speed"); + } + projectToBearing = wind.getBearing(); + } else { + projectToBearing = getTrackedLeg().getLegBearing(at); + } + double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians()); + if (cos < 0) { + projectToBearing = projectToBearing.reverse(); + } + result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing); + } + return result; + } + + /** + * For now, we have an incredibly simple wind "model" which assigns a single common wind force and bearing + * to all positions on the course, only variable over time. + */ + Wind getWind(Position p, TimePoint at) { + return getTrackedRace().getWind(p, at); + } + + @Override + public int getRank(TimePoint timePoint) { + int result = 0; + if (hasStartedLeg(timePoint)) { + List competitorTracksByRank = getTrackedLeg().getCompetitorTracksOrderedByRank(timePoint); + result = competitorTracksByRank.indexOf(this)+1; + } + return result; + } + + @Override + public Speed getAverageVelocityMadeGood(TimePoint timePoint) throws NoWindException { + Speed result = null; + MarkPassing start = getMarkPassingForLegStart(); + if (start != null && start.getTimePoint().compareTo(timePoint) <= 0) { + MarkPassing end = getMarkPassingForLegEnd(); + if (end != null) { + TimePoint to; + if (timePoint.compareTo(end.getTimePoint()) >= 0) { + to = end.getTimePoint(); + } else { + to = timePoint; + } + Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, /* extrapolate */ false); + if (endPos != null) { + Distance d = getTrackedLeg().getAbsoluteWindwardDistance( + getTrackedRace().getTrack(getCompetitor()) + .getEstimatedPosition(start.getTimePoint(), false), endPos, to); + result = d.inTime(to.asMillis() - start.getTimePoint().asMillis()); + } + } + } + return result; + } + + + @Override + public Integer getNumberOfTacks(TimePoint timePoint) throws NoWindException { + Integer result = null; + if (hasStartedLeg(timePoint)) { + List maneuvers = getManeuvers(timePoint, /* waitForLatest */ true); + result = 0; + for (Maneuver maneuver : maneuvers) { + if (maneuver.getType() == ManeuverType.TACK) { + result++; + } + } + } + return result; + } + + @Override + public List getManeuvers(TimePoint timePoint, boolean waitForLatest) throws NoWindException { + MarkPassing legEnd = getMarkPassingForLegEnd(); + TimePoint end = timePoint; + if (legEnd != null && timePoint.compareTo(legEnd.getTimePoint()) > 0) { + // timePoint is after leg finish; take leg end and end time point + end = legEnd.getTimePoint(); + } + List maneuvers = getTrackedRace().getManeuvers(getCompetitor(), + getMarkPassingForLegStart().getTimePoint(), end, waitForLatest); + return maneuvers; + } + + @Override + public Integer getNumberOfJibes(TimePoint timePoint) throws NoWindException { + Integer result = null; + if (hasStartedLeg(timePoint)) { + List maneuvers = getManeuvers(timePoint, /* waitForLatest */ true); + result = 0; + for (Maneuver maneuver : maneuvers) { + if (maneuver.getType() == ManeuverType.JIBE) { + result++; + } + } + } + return result; + } + + @Override + public Integer getNumberOfPenaltyCircles(TimePoint timePoint) throws NoWindException { + Integer result = null; + if (hasStartedLeg(timePoint)) { + List maneuvers = getManeuvers(timePoint, /* waitForLatest */ true); + result = 0; + for (Maneuver maneuver : maneuvers) { + if (maneuver.getType() == ManeuverType.PENALTY_CIRCLE) { + result++; + } + } + } + return result; + } + + @Override + public Distance getWindwardDistanceToOverallLeader(TimePoint timePoint) throws NoWindException { + // FIXME bug 607 it seems the following fetches the leader of this leg, not the overall leader; validate!!! Use getTrackedRace().getRanks() instead + Competitor leader = getTrackedRace().getOverallLeader(timePoint); + TrackedLegOfCompetitor leaderLeg = getTrackedRace().getCurrentLeg(leader, timePoint); + Distance result = null; + Position leaderPosition = getTrackedRace().getTrack(leader).getEstimatedPosition(timePoint, /* extrapolate */ false); + Position currentPosition = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(timePoint, /* extrapolate */ false); + if (leaderPosition != null && currentPosition != null) { + result = Distance.NULL; + boolean foundCompetitorsLeg = false; + getTrackedRace().getRace().getCourse().lockForRead(); + try { + for (Leg leg : getTrackedRace().getRace().getCourse().getLegs()) { + if (leg == getLeg()) { + foundCompetitorsLeg = true; + } + if (foundCompetitorsLeg) { + // if the leaderLeg is null, the leader has already arrived + if (leaderLeg == null || leg != leaderLeg.getLeg()) { + // add distance to next mark + Position nextMarkPosition = getTrackedRace().getApproximatePosition(leg.getTo(), timePoint); + Distance distanceToNextMark = getTrackedRace().getTrackedLeg(leg) + .getAbsoluteWindwardDistance(currentPosition, nextMarkPosition, timePoint); + result = new MeterDistance(result.getMeters() + distanceToNextMark.getMeters()); + currentPosition = nextMarkPosition; + } else { + // we're now in the same leg with leader; compute windward distance to leader + result = new MeterDistance(result.getMeters() + + getTrackedRace().getTrackedLeg(leg) + .getAbsoluteWindwardDistance(currentPosition, leaderPosition, timePoint) + .getMeters()); + break; + } + } + } + } finally { + getTrackedRace().getRace().getCourse().unlockAfterRead(); + } + } + return result; + } + + @Override + public Distance getAverageAbsoluteCrossTrackError(TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { + Distance result = null; + final MarkPassing legStartMarkPassing = getTrackedRace().getMarkPassing(competitor, getLeg().getFrom()); + if (legStartMarkPassing != null) { + TimePoint legStart = legStartMarkPassing.getTimePoint(); + final MarkPassing legEndMarkPassing = getTrackedRace().getMarkPassing(competitor, getLeg().getTo()); + TimePoint to; + if (legEndMarkPassing == null || legEndMarkPassing.getTimePoint().compareTo(timePoint) > 0) { + to = timePoint; + } else { + to = legEndMarkPassing.getTimePoint(); + } + result = getTrackedRace().getAverageAbsoluteCrossTrackError(competitor, legStart, to, /* upwindOnly */ false, waitForLatestAnalysis); + } + return result; + } + + @Override + public Distance getAverageSignedCrossTrackError(TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { + Distance result = null; + final MarkPassing legStartMarkPassing = getTrackedRace().getMarkPassing(competitor, getLeg().getFrom()); + if (legStartMarkPassing != null) { + TimePoint legStart = legStartMarkPassing.getTimePoint(); + final MarkPassing legEndMarkPassing = getTrackedRace().getMarkPassing(competitor, getLeg().getTo()); + TimePoint to; + if (legEndMarkPassing == null || legEndMarkPassing.getTimePoint().compareTo(timePoint) > 0) { + to = timePoint; + } else { + to = legEndMarkPassing.getTimePoint(); + } + result = getTrackedRace().getAverageSignedCrossTrackError(competitor, legStart, to, /* upwindOnly */ false, waitForLatestAnalysis); + } + return result; + } + + @Override + public Double getGapToLeaderInSeconds(TimePoint timePoint, final Competitor leaderInLegAtTimePoint) + throws NoWindException { + return getGapToLeaderInSeconds(timePoint, new LeaderGetter() { + @Override + public Competitor getLeader() { + return leaderInLegAtTimePoint; + } + }); + } + + private static interface LeaderGetter { + Competitor getLeader(); + } + + @Override + public Double getGapToLeaderInSeconds(final TimePoint timePoint) throws NoWindException { + return getGapToLeaderInSeconds(timePoint, new LeaderGetter() { + @Override + public Competitor getLeader() { + return getTrackedLeg().getLeader(hasFinishedLeg(timePoint) ? getFinishTime() : timePoint); + } + }); + } + + private Double getGapToLeaderInSeconds(TimePoint timePoint, LeaderGetter leaderGetter) throws NoWindException { + // If the leader already completed this leg, compute the estimated arrival time at the + // end of this leg; if this leg's competitor also already finished the leg, return the + // difference between this competitor's leg completion time point and the leader's completion + // time point; else, calculate the windward distance to the leader and divide by + // the windward speed + Speed windwardSpeed = getWindwardSpeed(getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(timePoint), timePoint); + Double result = null; + // Has our competitor started the leg already? If not, we won't be able to compute a gap + if (hasStartedLeg(timePoint)) { + Iterable markPassingsInOrder = getTrackedRace().getMarkPassingsInOrder(getLeg().getTo()); + if (markPassingsInOrder != null) { + MarkPassing firstMarkPassing = null; + getTrackedRace().lockForRead(markPassingsInOrder); + try { + Iterator markPassingsForLegEnd = markPassingsInOrder.iterator(); + if (markPassingsForLegEnd.hasNext()) { + firstMarkPassing = markPassingsForLegEnd.next(); + } + } finally { + getTrackedRace().unlockAfterRead(markPassingsInOrder); + } + if (firstMarkPassing != null) { + // someone has already finished the leg + TimePoint whenLeaderFinishedLeg = firstMarkPassing.getTimePoint(); + // Was it before the requested timePoint? + if (whenLeaderFinishedLeg.compareTo(timePoint) <= 0) { + // Has our competitor also already finished this leg? + if (hasFinishedLeg(timePoint)) { + // Yes, so the gap is the time period between the time points at which the leader and + // our competitor finished this leg. + return (getMarkPassingForLegEnd().getTimePoint().asMillis() - whenLeaderFinishedLeg + .asMillis()) / 1000.; + } else { + if (windwardSpeed == null) { + return null; + } else { + // leader has finished already; our competitor hasn't + Distance windwardDistanceToGo = getWindwardDistanceToGo(timePoint); + long millisSinceLeaderPassedMarkToTimePoint = timePoint.asMillis() + - whenLeaderFinishedLeg.asMillis(); + return windwardDistanceToGo.getMeters() / windwardSpeed.getMetersPerSecond() + + millisSinceLeaderPassedMarkToTimePoint / 1000.; + } + } + } + } + // no-one has finished this leg yet at timePoint + Competitor leader = leaderGetter.getLeader(); + // Maybe our competitor is the leader. Check: + if (leader == getCompetitor()) { + return 0.0; // the leader's gap to the leader + } else { + if (windwardSpeed == null) { + return null; + } else { + // no, we're not the leader, so compute our windward distance and divide by our current VMG + Position ourEstimatedPosition = getTrackedRace().getTrack(getCompetitor()) + .getEstimatedPosition(timePoint, false); + Position leaderEstimatedPosition = getTrackedRace().getTrack(leader).getEstimatedPosition( + timePoint, false); + if (ourEstimatedPosition == null || leaderEstimatedPosition == null) { + return null; + } else { + Distance windwardDistanceToGo = getTrackedLeg().getAbsoluteWindwardDistance(ourEstimatedPosition, + leaderEstimatedPosition, timePoint); + return windwardDistanceToGo.getMeters() / windwardSpeed.getMetersPerSecond(); + } + } + } + } + } + // else our competitor hasn't started the leg yet, so we can't compute a gap since we don't + // have a speed estimate; leave result == null + return result; + } + + @Override + public boolean hasStartedLeg(TimePoint timePoint) { + MarkPassing markPassingForLegStart = getMarkPassingForLegStart(); + return markPassingForLegStart != null && markPassingForLegStart.getTimePoint().compareTo(timePoint) <= 0; + } + + @Override + public boolean hasFinishedLeg(TimePoint timePoint) { + MarkPassing markPassingForLegEnd = getMarkPassingForLegEnd(); + return markPassingForLegEnd != null && markPassingForLegEnd.getTimePoint().compareTo(timePoint) <= 0; + } + + @Override + public TimePoint getStartTime() { + MarkPassing markPassingForLegStart = getMarkPassingForLegStart(); + return markPassingForLegStart == null ? null : markPassingForLegStart.getTimePoint(); + } + + @Override + public TimePoint getFinishTime() { + MarkPassing markPassingForLegEnd = getMarkPassingForLegEnd(); + return markPassingForLegEnd == null ? null : markPassingForLegEnd.getTimePoint(); + } + + @Override + public Speed getVelocityMadeGood(TimePoint at) throws NoWindException { + if (hasStartedLeg(at)) { + TimePoint timePoint; + if (hasFinishedLeg(at)) { + // use the leg finishing time point + timePoint = getMarkPassingForLegEnd().getTimePoint(); + } else { + timePoint = at; + } + SpeedWithBearing speedOverGround = getSpeedOverGround(timePoint); + return speedOverGround == null ? null : getWindwardSpeed(speedOverGround, timePoint); + } else { + return null; + } + } + + @Override + public SpeedWithBearing getSpeedOverGround(TimePoint at) { + if (hasStartedLeg(at)) { + TimePoint timePoint; + if (hasFinishedLeg(at)) { + // use the leg finishing time point + timePoint = getMarkPassingForLegEnd().getTimePoint(); + } else { + timePoint = at; + } + return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(timePoint); + } else { + return null; + } + } + + @Override + public Double getEstimatedTimeToNextMarkInSeconds(TimePoint timePoint) throws NoWindException { + Double result; + if (hasFinishedLeg(timePoint)) { + result = 0.0; + } else { + if (hasStartedLeg(timePoint)) { + Distance windwardDistanceToGo = getWindwardDistanceToGo(timePoint); + Speed vmg = getVelocityMadeGood(timePoint); + result = vmg == null ? null : windwardDistanceToGo.getMeters() / vmg.getMetersPerSecond(); + } else { + result = null; + } + } + return result; + } + + @Override + public Distance getManeuverLoss(TimePoint timePointBeforeManeuver, + TimePoint maneuverTimePoint, TimePoint timePointAfterManeuver) throws NoWindException { + assert timePointBeforeManeuver != null; + assert timePointAfterManeuver != null; + Distance result; + final GPSFixTrack track = getTrackedRace().getTrack(getCompetitor()); + List fixes = getFixesToConsiderForManeuverLossAnalysis(timePointBeforeManeuver, + maneuverTimePoint, timePointAfterManeuver); + TimePoint timePointWhenSpeedStartedToDrop = fixes.get(0).getTimePoint(); + SpeedWithBearing speedWhenSpeedStartedToDrop = track.getEstimatedSpeed(timePointWhenSpeedStartedToDrop); + if (speedWhenSpeedStartedToDrop != null) { + TimePoint timePointWhenSpeedLevelledOffAfterManeuver = fixes.get(fixes.size()-1).getTimePoint(); + SpeedWithBearing speedAfterManeuver = track.getEstimatedSpeed(timePointWhenSpeedLevelledOffAfterManeuver); + if (speedAfterManeuver != null) { + // For upwind/downwind legs, find the mean course between inbound and outbound course and project actual and + // extrapolated positions onto it: + Bearing middleManeuverAngle = speedWhenSpeedStartedToDrop.getBearing().middle(speedAfterManeuver.getBearing()); + // extrapolate maximum speed before maneuver to time point of maximum speed after maneuver and project resulting position + // onto the average maneuver course; compare to the projected position actually reached at the time point of maximum speed after + // maneuver: + Position positionWhenSpeedStartedToDrop = track.getEstimatedPosition(timePointWhenSpeedStartedToDrop, /* extrapolate */ false); + Position extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = + speedWhenSpeedStartedToDrop.travelTo(positionWhenSpeedStartedToDrop, timePointWhenSpeedStartedToDrop, timePointWhenSpeedLevelledOffAfterManeuver); + Position actualPositionAtTimePointOfMaxSpeedAfterManeuver = track.getEstimatedPosition(timePointWhenSpeedLevelledOffAfterManeuver, /* extrapolate */ false); + Position projectedExtrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = + extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver.projectToLineThrough(positionWhenSpeedStartedToDrop, middleManeuverAngle); + Position projectedActualPositionAtTimePointOfMaxSpeedAfterManeuver = + actualPositionAtTimePointOfMaxSpeedAfterManeuver.projectToLineThrough(positionWhenSpeedStartedToDrop, middleManeuverAngle); + result = projectedActualPositionAtTimePointOfMaxSpeedAfterManeuver.getDistance(projectedExtrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver); + } else { + result = null; + } + } else { + result = null; + } + return result; + } + + /** + * Fetches the set of fixes that encompass the maneuver. Usually, during a maneuver a boat loses speed over ground + * before it has reached the new tack and starts accelerating again, until the speed levels off. We assume that the + * timePointBeforeManeuver minus some excess time based on the approximate maneuver duration is the + * earliest time point to analyze. Starting there, we look for speed over ground minima between then and + * timePointAfterManeuver plus three times the approximate maneuver time as excess time. There may be + * multiple minima. We choose the one that has the best "fit" in terms of being close to the + * maneuverTimePoint and being low in terms of speed over ground. From that time point, the nearest + * maximum speeds over ground before and after are determined, and the fixes between them are returned. + */ + private List getFixesToConsiderForManeuverLossAnalysis(TimePoint timePointBeforeManeuver, + TimePoint maneuverTimePoint, TimePoint timePointAfterManeuver) { + final long EXCESS_TIME_BEFORE_MANEUVER_END_TO_SCAN_IN_MILLIS = getCompetitor().getBoat().getBoatClass().getApproximateManeuverDurationInMilliseconds(); + final long EXCESS_TIME_AFTER_MANEUVER_END_TO_SCAN_IN_MILLIS = 3*EXCESS_TIME_BEFORE_MANEUVER_END_TO_SCAN_IN_MILLIS; + List fixes = new ArrayList<>(); + NavigableSet maxima = new ArrayListNavigableSet(new TimedComparator()); + NavigableSet minima = new ArrayListNavigableSet(new TimedComparator()); + GPSFixTrack track = getTrackedRace().getTrack(getCompetitor()); + Speed lastSpeed = Speed.NULL; + Speed lastLastSpeed = Speed.NULL; + GPSFixMoving lastFix = null; + Speed minimumSpeed = new KnotSpeedImpl(Double.MAX_VALUE); + track.lockForRead(); + try { + Iterator fixIter = track.getFixesIterator( + timePointBeforeManeuver.minus(EXCESS_TIME_BEFORE_MANEUVER_END_TO_SCAN_IN_MILLIS), /* inclusive */true); + GPSFixMoving fix; + // The timePointAfterManeuver is determined based on the geometric shape of the boat's trajectory, not on + // the speed development. To understand the full maneuver loss, we need to follow the boat speed until it levels off, + // but no further than some reasonable threshold because the wind may continue to pick up, letting the boat accelerate + // over a time much longer than accounted for by the maneuver. + while (fixIter.hasNext()) { + fix = fixIter.next(); + final SpeedWithBearing estimatedSpeedAtFix = track.getEstimatedSpeed(fix.getTimePoint()); + if (lastFix != null && lastSpeed != null && lastLastSpeed != null && estimatedSpeedAtFix != null) { + if (lastSpeed.compareTo(lastLastSpeed) > 0 && lastSpeed.compareTo(estimatedSpeedAtFix) > 0) { + maxima.add(lastFix); + } else if (lastSpeed.compareTo(lastLastSpeed) < 0 && lastSpeed.compareTo(estimatedSpeedAtFix) < 0) { + minima.add(lastFix); + if (lastSpeed.compareTo(minimumSpeed) < 0) { + minimumSpeed = lastSpeed; + } + } + } + if (fix.getTimePoint().after(timePointAfterManeuver.plus(EXCESS_TIME_AFTER_MANEUVER_END_TO_SCAN_IN_MILLIS))) { + break; + } + fixes.add(fix); + lastLastSpeed = lastSpeed; + lastSpeed = estimatedSpeedAtFix; + lastFix = fix; + } + } finally { + track.unlockAfterRead(); + } + GPSFixMoving fixWithLowestSpeedOverGround = getBestFittingSpeedMinimumInManeuver(minima, maxima, minimumSpeed, maneuverTimePoint); + if (fixWithLowestSpeedOverGround == null) { + fixWithLowestSpeedOverGround = fixes.get(0); + } + // now remove all fixes before the last maximum before the fix with the lowest speed over ground during the maneuver if + // there was such a maximum; otherwise, leave all fixes from the maneuver start on in place. + final long MAX_SMOOTHENING_INTERVAL_MILLIS = getCompetitor().getBoat().getBoatClass().getApproximateManeuverDurationInMilliseconds(); + GPSFixMoving lastMaxSpeedFixBeforeLowSpeed = maxima.lower(fixWithLowestSpeedOverGround); + // now check if there's a greater one that's only a little bit earlier + if (lastMaxSpeedFixBeforeLowSpeed != null) { + GPSFixMoving stillGreater; + while ((stillGreater=maxima.lower(lastMaxSpeedFixBeforeLowSpeed)) != null && + track.getEstimatedSpeed(stillGreater.getTimePoint()).compareTo( + track.getEstimatedSpeed(lastMaxSpeedFixBeforeLowSpeed.getTimePoint())) > 0 && + lastMaxSpeedFixBeforeLowSpeed.getTimePoint().asMillis()- + stillGreater.getTimePoint().asMillis() < MAX_SMOOTHENING_INTERVAL_MILLIS) { + lastMaxSpeedFixBeforeLowSpeed = stillGreater; + } + Iterator i = fixes.iterator(); + while (i.hasNext() && i.next().getTimePoint().before(lastMaxSpeedFixBeforeLowSpeed.getTimePoint())) { + i.remove(); + } + } + // now remove all fixes after the first maximum after the global minimum: + GPSFixMoving firstMaxSpeedFixAfterLowSpeed = maxima.higher(fixWithLowestSpeedOverGround); + if (firstMaxSpeedFixAfterLowSpeed != null) { + GPSFixMoving stillGreater; + while ((stillGreater=maxima.higher(firstMaxSpeedFixAfterLowSpeed)) != null && + track.getEstimatedSpeed(stillGreater.getTimePoint()).compareTo( + track.getEstimatedSpeed(firstMaxSpeedFixAfterLowSpeed.getTimePoint())) > 0 && + stillGreater.getTimePoint().asMillis() - firstMaxSpeedFixAfterLowSpeed.getTimePoint().asMillis() + < MAX_SMOOTHENING_INTERVAL_MILLIS) { + firstMaxSpeedFixAfterLowSpeed = stillGreater; + } + ListIterator i = fixes.listIterator(fixes.size()); + while (i.hasPrevious() && i.previous().getTimePoint().after(firstMaxSpeedFixAfterLowSpeed.getTimePoint())) { + i.remove(); + } + } + return fixes; + } + + private GPSFixMoving getBestFittingSpeedMinimumInManeuver(NavigableSet minima, + NavigableSet maxima, Speed minimumSpeed, TimePoint maneuverTimePoint) { + // Idea: being 10x the approximate maneuver duration away from maneuverTimePoint is as bad as having twice the percentage between min + // and max speed (min = 0%; max=100%). + GPSFixMoving bestSpeedMinimum = null; + Speed maxSpeed = null; + for (GPSFixMoving maximum : maxima) { + if (maxSpeed == null || maximum.getSpeed().getKnots() > maxSpeed.getKnots()) { + maxSpeed = maximum.getSpeed(); + } + } + if (maxSpeed != null) { + double speedDifferenceBetweenMaxAndMinInKnots = maxSpeed.getKnots() - minimumSpeed.getKnots(); + double lowestBadness = Double.MAX_VALUE; + final long approximateManeuverTimeInMillis = getCompetitor().getBoat().getBoatClass() + .getApproximateManeuverDurationInMilliseconds(); + final GPSFixTrack track = getTrackedRace().getTrack(getCompetitor()); + for (GPSFixMoving minimum : minima) { + // best speedBadness can be 1 which represents the absolute speed minimum in the interval considered + double speedBadness = 1 + (track.getEstimatedSpeed(minimum.getTimePoint()).getKnots() - minimumSpeed.getKnots()) / + speedDifferenceBetweenMaxAndMinInKnots; + // best timePointBadness can be 1 which is a minimum exactly at the maneuver time point + double timePointBadness = 1. + + (double) Math.abs(minimum.getTimePoint().asMillis() - maneuverTimePoint.asMillis()) + / (double) approximateManeuverTimeInMillis / 10.; + final double totalBadness = speedBadness * timePointBadness; + if (totalBadness < lowestBadness) { + bestSpeedMinimum = minimum; + lowestBadness = totalBadness; + } + } + } + return bestSpeedMinimum; + } + + @Override + public Bearing getBeatAngle(TimePoint at) throws NoWindException { + Bearing beatAngle = null; + + Bearing projectToBearing; + if (getTrackedLeg().isUpOrDownwindLeg(at)) { + Wind wind = getWind(getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false), at); + if (wind == null) { + throw new NoWindException("Need at least wind direction to determine windward speed"); + } + projectToBearing = wind.getBearing(); + } else { + projectToBearing = getTrackedLeg().getLegBearing(at); + } + + SpeedWithBearing speed = getSpeedOverGround(at); + if (speed != null) { + beatAngle = projectToBearing.getDifferenceTo(speed.getBearing()); + } + + return beatAngle; + } +} 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 index 7d5d9d6d6c0..e9c3c2dcefd 100755 --- 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 @@ -1,2683 +1,2877 @@ -package com.sap.sailing.domain.tracking.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.ConcurrentModificationException; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.NavigableSet; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.sap.sailing.domain.base.BearingWithConfidence; -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.CourseListener; -import com.sap.sailing.domain.base.Leg; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Sideline; -import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; -import com.sap.sailing.domain.base.SpeedWithConfidence; -import com.sap.sailing.domain.base.Timed; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; -import com.sap.sailing.domain.base.impl.DouglasPeucker; -import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.BearingChangeAnalyzer; -import com.sap.sailing.domain.common.CourseChange; -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.NauticalSide; -import com.sap.sailing.domain.common.NoWindError; -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.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.TimingConstants; -import com.sap.sailing.domain.common.TrackedRaceStatusEnum; -import com.sap.sailing.domain.common.WindSource; -import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.impl.CentralAngleDistance; -import com.sap.sailing.domain.common.impl.KnotSpeedImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.NauticalMileDistance; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; -import com.sap.sailing.domain.common.impl.WindSourceImpl; -import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; -import com.sap.sailing.domain.confidence.ConfidenceFactory; -import com.sap.sailing.domain.confidence.HasConfidence; -import com.sap.sailing.domain.confidence.Weigher; -import com.sap.sailing.domain.confidence.impl.HyperbolicTimeDifferenceWeigher; -import com.sap.sailing.domain.confidence.impl.PositionAndTimePointWeigher; -import com.sap.sailing.domain.racelog.RaceLog; -import com.sap.sailing.domain.racelog.analyzing.impl.StartTimeFinder; -import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.GPSTrackListener; -import com.sap.sailing.domain.tracking.LineDetails; -import com.sap.sailing.domain.tracking.Maneuver; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; -import com.sap.sailing.domain.tracking.TrackedRaceStatus; -import com.sap.sailing.domain.tracking.TrackedRaceWithWindEssentials; -import com.sap.sailing.domain.tracking.TrackedRegatta; -import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.domain.tracking.WindStore; -import com.sap.sailing.domain.tracking.WindTrack; -import com.sap.sailing.domain.tracking.WindWithConfidence; -import com.sap.sailing.util.SmartFutureCache; -import com.sap.sailing.util.SmartFutureCache.AbstractCacheUpdater; -import com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval; -import com.sap.sailing.util.impl.ArrayListNavigableSet; -import com.sap.sailing.util.impl.LockUtil; -import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; - -public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials implements CourseListener { - private static final long serialVersionUID = -4825546964220003507L; - - private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName()); - - // TODO make this variable - private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500; - - public static final long TIME_BEFORE_START_TO_TRACK_WIND_MILLIS = 4 * 60 * 1000l; // let wind start four minutes before race - - private TrackedRaceStatus status; - - private final Object statusNotifier; - - /** - * By default, all wind sources are used, none are excluded. However, e.g., for performance reasons, particular wind - * sources such as the track-based estimation wind source, may be excluded by adding them to this set. - */ - private final Set windSourcesToExclude; - - /** - * Keeps the oldest timestamp that is fed into this tracked race, either from a boat fix, a mark fix, a race - * start/finish or a course definition. - */ - private TimePoint timePointOfOldestEvent; - - /** - * The start of tracking time as announced by the tracking infrastructure. - */ - private TimePoint startOfTrackingReceived; - - /** - * The end of tracking time as announced by the tracking infrastructure. - */ - private TimePoint endOfTrackingReceived; - - /** - * Race start time as announced by the tracking infrastructure - */ - private TimePoint startTimeReceived; - - /** - * The calculated race start time - */ - private TimePoint startTime; - - /** - * The calculated race end time - */ - private TimePoint endTime; - - /** - * The first and last passing times of all course waypoints - */ - private transient List>> markPassingsTimes; - - /** - * The latest time point contained by any of the events received and processed - */ - private TimePoint timePointOfNewestEvent; - - /** - * Time stamp that the event received last from the underlying push service carried on it - */ - private TimePoint timePointOfLastEvent; - - private long updateCount; - - private transient Map> competitorRankings; - - /** - * The locks managed here correspond with the {@link #competitorRankings} structure. When - * {@link #getCompetitorsFromBestToWorst(TimePoint)} starts to compute rankings, it locks the write lock for the - * time point. Readers use the read lock. Checking / entering a lock into this map uses synchronized on - * the map itself. - */ - private transient Map competitorRankingsLocks; - - /** - * legs appear in the order in which they appear in the race's course - */ - private final LinkedHashMap trackedLegs; - - private final Map> tracks; - - private final Map> markPassingsForCompetitor; - - /** - * The mark passing sets used as values are ordered by time stamp. - */ - private final Map> markPassingsForWaypoint; - - /** - * Values are the from and to time points between which the maneuvers have been previously - * computed. Clients wanting to know maneuvers for the competitor outside of this time interval need to (re-)compute - * them. - */ - private transient SmartFutureCache>, EmptyUpdateInterval> maneuverCache; - - private transient Map> directionFromStartToNextMarkCache; - - private final ConcurrentHashMap> markTracks; - - private final Map courseSidelines; - - protected long millisecondsOverWhichToAverageSpeed; - - private final Map startToNextMarkCacheInvalidationListeners; - - private transient Timer cacheInvalidationTimer; - private transient Object cacheInvalidationTimerLock; - - protected transient HashMap attachedRaceLogs; - - /** - * The time delay to the current point in time in milliseconds. - */ - private long delayToLiveInMillis; - - private enum WindLoadingState { NOT_STARTED, RUNNING, FINISHED }; - /** - * The constructor loads wind fixes from the {@link #windStore} asynchronously. When completed, this flag is set to - * true, and all threads currently waiting on this object are notified. - */ - private WindLoadingState windLoadingCompleted; - - private transient CrossTrackErrorCache crossTrackErrorCache; - - /** - * Wind loading is started in a background thread during object construction. If a client needs to ensure that wind loading - * either has terminated or has not yet begun, it can obtain the read lock of this lock. The wind loading procedure will obtain - * the write lock before it starts loading wind fixes. - */ - private final NamedReentrantReadWriteLock windLoadingLock; - - private final Map, NamedReentrantReadWriteLock> locksForMarkPassings; - - public TrackedRaceImpl(final TrackedRegatta trackedRegatta, RaceDefinition race, final Iterable sidelines, final WindStore windStore, - long delayToLiveInMillis, final long millisecondsOverWhichToAverageWind, - long millisecondsOverWhichToAverageSpeed, long delayForWindEstimationCacheInvalidation) { - super(race, trackedRegatta, windStore, millisecondsOverWhichToAverageWind); - locksForMarkPassings = new IdentityHashMap<>(); - attachedRaceLogs = new HashMap<>(); - this.status = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.PREPARED, 0.0); - this.statusNotifier = new Object[0]; - this.windLoadingLock = new NamedReentrantReadWriteLock("Wind loading lock for tracked race " - + race.getName(), /* fair */ false); - this.cacheInvalidationTimerLock = new Object(); - this.updateCount = 0; - this.windSourcesToExclude = new HashSet(); - this.directionFromStartToNextMarkCache = new HashMap>(); - this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed; - this.delayToLiveInMillis = delayToLiveInMillis; - this.startToNextMarkCacheInvalidationListeners = new ConcurrentHashMap(); - this.maneuverCache = createManeuverCache(); - this.markTracks = new ConcurrentHashMap>(); - this.crossTrackErrorCache = new CrossTrackErrorCache(this); - int i = 0; - for (Waypoint waypoint : race.getCourse().getWaypoints()) { - for (Mark mark : waypoint.getMarks()) { - getOrCreateTrack(mark); - if (i < 2) { - // add cache invalidation listeners for first and second waypoint's marks for - // directionFromStartToNextMarkCache - addStartToNextMarkCacheInvalidationListener(mark); - } - } - i++; - } - courseSidelines = new LinkedHashMap(); - for (Sideline sideline : sidelines) { - courseSidelines.put(sideline.getName(), sideline); - for (Mark mark : sideline.getMarks()) { - getOrCreateTrack(mark); - } - } - - trackedLegs = new LinkedHashMap(); - race.getCourse().lockForRead(); - try { - for (Leg leg : race.getCourse().getLegs()) { - trackedLegs.put(leg, createTrackedLeg(leg)); - } - getRace().getCourse().addCourseListener(this); - } finally { - race.getCourse().unlockAfterRead(); - } - markPassingsForCompetitor = new HashMap>(); - tracks = new HashMap>(); - for (Competitor competitor : race.getCompetitors()) { - markPassingsForCompetitor.put(competitor, new ConcurrentSkipListSet( - MarkPassingByTimeComparator.INSTANCE)); - tracks.put(competitor, new DynamicGPSFixMovingTrackImpl(competitor, - millisecondsOverWhichToAverageSpeed)); - } - markPassingsForWaypoint = new ConcurrentHashMap>(); - for (Waypoint waypoint : race.getCourse().getWaypoints()) { - markPassingsForWaypoint.put(waypoint, new ConcurrentSkipListSet( - MarkPassingByTimeComparator.INSTANCE)); - } - markPassingsTimes = new ArrayList>>(); - windLoadingCompleted = WindLoadingState.NOT_STARTED; - // When this tracked race is to be serialized, wait for the loading of the wind tracks to complete. - new Thread("Wind loader for tracked race " + getRace().getName()) { - @Override - public void run() { - LockUtil.lockForRead(getSerializationLock()); - LockUtil.lockForWrite(getWindLoadingLock()); - synchronized (TrackedRaceImpl.this) { - windLoadingCompleted = WindLoadingState.RUNNING; // indicates that the serialization lock is now safely held - TrackedRaceImpl.this.notifyAll(); - } - try { - logger.info("Started loading wind tracks for " + getRace().getName()); - final Map loadedWindTracks = windStore.loadWindTracks( - trackedRegatta.getRegatta().getName(), TrackedRaceImpl.this, millisecondsOverWhichToAverageWind); - windTracks.putAll(loadedWindTracks); - updateEventTimePoints(loadedWindTracks); - logger.info("Finished loading wind tracks for " + getRace().getName() + "! Found " + windTracks.size() + " wind tracks for this race!"); - } finally { - synchronized (TrackedRaceImpl.this) { - windLoadingCompleted = WindLoadingState.FINISHED; - TrackedRaceImpl.this.notifyAll(); - } - LockUtil.unlockAfterWrite(getWindLoadingLock()); - LockUtil.unlockAfterRead(getSerializationLock()); - } - } - }.start(); - // by default, a tracked race offers one course-based wind estimation and one track-based wind estimation track; - // other wind tracks may be added as fixes are received for them and as they are loaded from the persistent - // store - WindSource courseBasedWindSource = new WindSourceImpl(WindSourceType.COURSE_BASED); - windTracks.put(courseBasedWindSource, - getOrCreateWindTrack(courseBasedWindSource, delayForWindEstimationCacheInvalidation)); - WindSource trackBasedWindSource = new WindSourceImpl(WindSourceType.TRACK_BASED_ESTIMATION); - windTracks.put(trackBasedWindSource, - getOrCreateWindTrack(trackBasedWindSource, delayForWindEstimationCacheInvalidation)); - competitorRankings = new HashMap>(); - competitorRankingsLocks = new HashMap(); - // now wait until wind loading has at least started; then we know that the serialization lock is safely held by the loader - synchronized (this) { - while (windLoadingCompleted != WindLoadingState.FINISHED) { - try { - this.wait(); - } catch (InterruptedException e) { - logger.log(Level.SEVERE, "Waiting for wind loading to start was interrupted", e); - } - } - } - } - - /** - * Assuming that the wind tracks loadedWindTracks were loaded from the persistent store, this method updates - * the time stamps that frame the data held by this tracked race. See {@link #timePointOfLastEvent}, {@link #timePointOfNewestEvent} - * and {@link #timePointOfOldestEvent}. - */ - private void updateEventTimePoints(Map loadedWindTracks) { - for (WindTrack windTrack : loadedWindTracks.values()) { - windTrack.lockForRead(); - try { - for (Wind wind : windTrack.getRawFixes()) { - updated(wind.getTimePoint()); - } - } finally { - windTrack.unlockAfterRead(); - } - } - } - - /** - * Object serialization obtains a read lock for the course so that in cannot change while serializing this object. - */ - private void writeObject(ObjectOutputStream s) throws IOException { - LockUtil.lockForWrite(getSerializationLock()); - try { - s.defaultWriteObject(); - } finally { - LockUtil.unlockAfterWrite(getSerializationLock()); - } - } - - /** - * Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}. - * When de-serializing, a possibly remote {@link #windStore} is ignored because it is transient. Instead, an - * {@link EmptyWindStore} is used for the de-serialized instance. - */ - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - attachedRaceLogs = new HashMap<>(); - markPassingsTimes = new ArrayList>>(); - cacheInvalidationTimerLock = new Object(); - windStore = EmptyWindStore.INSTANCE; - competitorRankings = new HashMap>(); - competitorRankingsLocks = new HashMap(); - directionFromStartToNextMarkCache = new HashMap>(); - crossTrackErrorCache = new CrossTrackErrorCache(this); - maneuverCache = createManeuverCache(); - logger.info("Deserialized race " + getRace().getName()); - } - - @Override - public synchronized void waitUntilWindLoadingComplete() throws InterruptedException { - while (windLoadingCompleted != WindLoadingState.FINISHED) { - wait(); - } - } - - private SmartFutureCache>, EmptyUpdateInterval> createManeuverCache() { - return new SmartFutureCache>, EmptyUpdateInterval>( - new AbstractCacheUpdater>, EmptyUpdateInterval>() { - @Override - public Triple> computeCacheUpdate(Competitor competitor, - EmptyUpdateInterval updateInterval) throws NoWindException { - return computeManeuvers(competitor); - } - }, /* nameForLocks */"Maneuver cache for race " + getRace().getName()); - } - - /** - * Precondition: race has already been set, e.g., in constructor before this method is called - */ - abstract protected TrackedLeg createTrackedLeg(Leg leg); - - public RegattaAndRaceIdentifier getRaceIdentifier() { - return new RegattaNameAndRaceName(getTrackedRegatta().getRegatta().getName(), getRace().getName()); - } - - @Override - public NavigableSet getMarkPassings(Competitor competitor) { - return markPassingsForCompetitor.get(competitor); - } - - protected NavigableSet getMarkPassingsInOrderAsNavigableSet(Waypoint waypoint) { - return markPassingsForWaypoint.get(waypoint); - } - - @Override - public WindStore getWindStore() { - return windStore; - } - - @Override - public Iterable getMarkPassingsInOrder(Waypoint waypoint) { - return getMarkPassingsInOrderAsNavigableSet(waypoint); - } - - protected NavigableSet getOrCreateMarkPassingsInOrderAsNavigableSet(Waypoint waypoint) { - NavigableSet result = getMarkPassingsInOrderAsNavigableSet(waypoint); - if (result == null) { - result = createMarkPassingsCollectionForWaypoint(waypoint); - } - return result; - } - - protected NavigableSet createMarkPassingsCollectionForWaypoint(Waypoint waypoint) { - final ConcurrentSkipListSet result = new ConcurrentSkipListSet( - MarkPassingByTimeComparator.INSTANCE); - LockUtil.lockForRead(getSerializationLock()); - try { - markPassingsForWaypoint.put(waypoint, result); - } finally { - LockUtil.unlockAfterRead(getSerializationLock()); - } - return result; - } - - @Override - public TimePoint getStartOfTracking() { - return startOfTrackingReceived; - } - - @Override - public TimePoint getEndOfTracking() { - return endOfTrackingReceived; - } - - public void invalidateStartTime() { - startTime = null; - } - - public void invalidateEndTime() { - endTime = null; - } - - protected void invalidateMarkPassingTimes() { - synchronized (markPassingsTimes) { - markPassingsTimes.clear(); - } - } - - /** - * Calculates the start time of the race from various sources. The highest precedence take the {@link #attachedRaceLogs race logs}, - * followed by the field {@link #startTimeReceived} which can explicitly be set using {@link #setStartTimeReceived(TimePoint)}. - * If that does not provide any start time either, a start time is attempted to be inferred from the time points - * of the start mark passing events. - */ - @Override - public TimePoint getStartOfRace() { - if (startTime == null) { - for (RaceLog raceLog : attachedRaceLogs.values()) { - startTime = new StartTimeFinder(raceLog).analyze(); - if (startTime != null) { - break; - } - } - if (startTime == null) { - startTime = getStartTimeReceived(); - // If not null, check if the first mark passing for the start line is too much after the - // startTimeReceived; if so, return an adjusted, later start time. - // If no official start time was received, try to estimate the start time using the mark passings for - // the start line. - final Waypoint firstWaypoint = getRace().getCourse().getFirstWaypoint(); - if (firstWaypoint != null) { - if (startTimeReceived != null) { - TimePoint timeOfFirstMarkPassing = getFirstPassingTime(firstWaypoint); - if (timeOfFirstMarkPassing != null) { - long startTimeReceived2timeOfFirstMarkPassingFirstMark = timeOfFirstMarkPassing.asMillis() - - startTimeReceived.asMillis(); - if (startTimeReceived2timeOfFirstMarkPassingFirstMark > MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS) { - startTime = new MillisecondsTimePoint(timeOfFirstMarkPassing.asMillis() - - MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS); - } else { - startTime = startTimeReceived; - } - } - } else { - final NavigableSet markPassingsForFirstWaypointInOrder = getMarkPassingsInOrderAsNavigableSet(firstWaypoint); - if (markPassingsForFirstWaypointInOrder != null) { - startTime = calculateStartOfRaceFromMarkPassings(markPassingsForFirstWaypointInOrder, - getRace().getCompetitors()); - } - } - } - } - } - return startTime; - } - - /** - * Calculates the end time of the race from the mark passings of the last course waypoint - */ - @Override - public TimePoint getEndOfRace() { - if (endTime == null) { - endTime = getLastPassingOfFinishLine(); - } - return endTime; - } - - private TimePoint getLastPassingOfFinishLine() { - TimePoint passingTime = null; - final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint(); - if (lastWaypoint != null) { - Iterable markPassingsInOrder = getMarkPassingsInOrder(lastWaypoint); - if (markPassingsInOrder != null) { - lockForRead(markPassingsInOrder); - try { - for (MarkPassing passingFinishLine : markPassingsInOrder) { - passingTime = passingFinishLine.getTimePoint(); - } - } finally { - unlockAfterRead(markPassingsInOrder); - } - } - } - return passingTime; - } - - private TimePoint getFirstPassingTime(Waypoint waypoint) { - NavigableSet markPassingsInOrder = getMarkPassingsInOrderAsNavigableSet(waypoint); - MarkPassing firstMarkPassing = null; - if (markPassingsInOrder != null) { - lockForRead(markPassingsInOrder); - try { - if (!markPassingsInOrder.isEmpty()) { - firstMarkPassing = markPassingsInOrder.first(); - } - } finally { - unlockAfterRead(markPassingsInOrder); - } - } - TimePoint timeOfFirstMarkPassing = null; - if (firstMarkPassing != null) { - timeOfFirstMarkPassing = firstMarkPassing.getTimePoint(); - } - return timeOfFirstMarkPassing; - } - - private TimePoint calculateStartOfRaceFromMarkPassings(NavigableSet markPassings, - Iterable competitors) { - TimePoint startOfRace = null; - // Find the first mark passing within the largest cluster crossing the line within one minute. - final long ONE_MINUTE_IN_MILLIS = 60 * 1000; - lockForRead(markPassings); - try { - if (markPassings != null) { - int largestStartGroupWithinOneMinuteSize = 0; - MarkPassing startOfLargestGroupSoFar = null; - int candiateGroupSize = 0; - MarkPassing candidateForStartOfLargestGroupSoFar = null; - Iterator iterator = markPassings.iterator(); - // sweep over all start mark passings and for each element find the number of competitors that passed - // the start up to one minute later; - // pick the start mark passing of the competitor leading the largest such group - while (iterator.hasNext()) { - MarkPassing currentMarkPassing = iterator.next(); - if (candidateForStartOfLargestGroupSoFar == null) { - // first start mark passing - candidateForStartOfLargestGroupSoFar = currentMarkPassing; - candiateGroupSize = 1; - startOfLargestGroupSoFar = currentMarkPassing; - largestStartGroupWithinOneMinuteSize = 1; - } else { - if (currentMarkPassing.getTimePoint().asMillis() - - candidateForStartOfLargestGroupSoFar.getTimePoint().asMillis() <= ONE_MINUTE_IN_MILLIS) { - // currentMarkPassing is within one minute of candidateForStartOfLargestGroupSoFar; extend - // candidate group... - candiateGroupSize++; - if (candiateGroupSize > largestStartGroupWithinOneMinuteSize) { - // ...and remember as best fit if greater than largest group so far - startOfLargestGroupSoFar = candidateForStartOfLargestGroupSoFar; - largestStartGroupWithinOneMinuteSize = candiateGroupSize; - } - } else { - // currentMarkPassing is more than a minute after candidateForStartOfLargestGroupSoFar; - // advance - // candidateForStartOfLargestGroupSoFar and reduce group size counter, until - // candidateForStartOfLargestGroupSoFar - // is again within the one-minute interval; may catch up all the way to currentMarkPassing - // if that was - // more than a minute after its predecessor - while (currentMarkPassing.getTimePoint().asMillis() - - candidateForStartOfLargestGroupSoFar.getTimePoint().asMillis() > ONE_MINUTE_IN_MILLIS) { - candidateForStartOfLargestGroupSoFar = markPassings - .higher(candidateForStartOfLargestGroupSoFar); - candiateGroupSize--; - } - } - } - } - startOfRace = startOfLargestGroupSoFar == null ? null : startOfLargestGroupSoFar.getTimePoint(); - } - } finally { - unlockAfterRead(markPassings); - } - return startOfRace; - } - - @Override - public boolean hasStarted(TimePoint at) { - return getStartOfRace() != null && getStartOfRace().compareTo(at) <= 0; - } - - @Override - public boolean isLive(TimePoint at) { - final Date startOfLivePeriod; - final Date endOfLivePeriod; - if (!hasGPSData() || !hasWindData()) { - startOfLivePeriod = null; - endOfLivePeriod = null; - } else { - if (getStartOfRace() == null) { - startOfLivePeriod = getStartOfTracking().asDate(); - } else { - startOfLivePeriod = new Date(getStartOfRace().asMillis() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS); - } - if (getEndOfRace() == null) { - if (getTimePointOfNewestEvent() != null) { - endOfLivePeriod = new Date(getTimePointOfNewestEvent().asMillis() - + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS); - } else { - endOfLivePeriod = null; - } - } else { - endOfLivePeriod = new Date(getEndOfRace().asMillis() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS); - } - } - - // if an empty timepoint is given then take the start of the race - if (at == null) { - at = new MillisecondsTimePoint(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() <= at.asMillis() && - at.asMillis() <= endOfLivePeriod.getTime(); - return result; - } - - @Override - public RaceDefinition getRace() { - return race; - } - - @Override - public Iterable getTrackedLegs() { - // ensure that no course modification is carried out while copying the tracked legs - getRace().getCourse().lockForRead(); - try { - return new ArrayList(trackedLegs.values()); - } finally { - getRace().getCourse().unlockAfterRead(); - } - } - - @Override - public Iterable>> getMarkPassingsTimes() { - getRace().getCourse().lockForRead(); // ensure the list of waypoints doesn't change while we're updating the - // markPassingTimes structure - try { - synchronized (markPassingsTimes) { - if (markPassingsTimes.isEmpty()) { - // Remark: sometimes it can happen that a mark passing with a wrong time stamp breaks the right time - // order of the waypoint times - Date previousLegPassingTime = null; - for (Waypoint waypoint : getRace().getCourse().getWaypoints()) { - TimePoint firstPassingTime = null; - TimePoint lastPassingTime = null; - NavigableSet markPassings = getMarkPassingsInOrderAsNavigableSet(waypoint); - if (markPassings != null && !markPassings.isEmpty()) { - // ensure the leg times are in the right time order; there may perhaps be left-overs for - // marks to be reached later that - // claim it has been passed in the past which may have been an accidental tracker - // read-out; - // the results of getMarkPassingsInOrder(to) has by definition an ascending time-point - // ordering - lockForRead(markPassings); - try { - for (MarkPassing currentMarkPassing : markPassings) { - Date currentPassingDate = currentMarkPassing.getTimePoint().asDate(); - if (previousLegPassingTime == null - || currentPassingDate.after(previousLegPassingTime)) { - firstPassingTime = currentMarkPassing.getTimePoint(); - previousLegPassingTime = currentPassingDate; - break; - } - } - } finally { - unlockAfterRead(markPassings); - } - } - Pair timesPair = new Pair(firstPassingTime, - lastPassingTime); - markPassingsTimes.add(new Pair>(waypoint, timesPair)); - } - } - return markPassingsTimes; - } - } finally { - getRace().getCourse().unlockAfterRead(); - } - } - - @Override - public Distance getDistanceTraveled(Competitor competitor, TimePoint timePoint) { - NavigableSet markPassings = getMarkPassings(competitor); - if (markPassings.isEmpty()) { - return null; - } else { - TimePoint end = timePoint; - if (markPassings.last().getWaypoint() == getRace().getCourse().getLastWaypoint() - && timePoint.compareTo(markPassings.last().getTimePoint()) > 0) { - // competitor has finished race; use time point of crossing the finish line - end = markPassings.last().getTimePoint(); - } else if (markPassings.last().getWaypoint() != getRace().getCourse().getLastWaypoint() && - timePoint.after(markPassings.last().getTimePoint())) { - // if competitor has not finished the race (and the requested timepoint - // is after the last mark passing) then we can not tell anything about - // the distance traveled - this is in line with AbstractSimpleLeaderboardImpl#getTotalTimeSailedInMilliseconds - return null; - } - return getTrack(competitor).getDistanceTraveled(markPassings.first().getTimePoint(), end); - } - } - - @Override - public GPSFixTrack getTrack(Competitor competitor) { - return tracks.get(competitor); - } - - @Override - public TrackedLeg getTrackedLegFinishingAt(Waypoint endOfLeg) { - getRace().getCourse().lockForRead(); - try { - int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(endOfLeg); - if (indexOfWaypoint == -1) { - throw new IllegalArgumentException("Waypoint " + endOfLeg + " not found in " + getRace().getCourse()); - } else if (indexOfWaypoint == 0) { - throw new IllegalArgumentException("Waypoint " + endOfLeg + " isn't start of any leg in " - + getRace().getCourse()); - } - return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint - 1)); - } finally { - getRace().getCourse().unlockAfterRead(); - } - } - - @Override - public TrackedLeg getTrackedLegStartingAt(Waypoint startOfLeg) { - getRace().getCourse().lockForRead(); - try { - int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(startOfLeg); - if (indexOfWaypoint == -1) { - throw new IllegalArgumentException("Waypoint " + startOfLeg + " not found in " + getRace().getCourse()); - } else if (indexOfWaypoint == Util.size(getRace().getCourse().getWaypoints()) - 1) { - throw new IllegalArgumentException("Waypoint " + startOfLeg + " isn't start of any leg in " - + getRace().getCourse()); - } - return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint)); - } finally { - getRace().getCourse().unlockAfterRead(); - } - } - - @Override - public TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, TimePoint at) { - NavigableSet roundings = getMarkPassings(competitor); - lockForRead(roundings); - try { - NavigableSet localRoundings = new ArrayListNavigableSet<>(roundings.size(), - new TimedComparator()); - localRoundings.addAll(roundings); - } finally { - unlockAfterRead(roundings); - } - TrackedLegOfCompetitor result = null; - if (roundings != null) { - TrackedLeg trackedLeg; - // obtain last waypoint before obtaining mark passings monitor because obtaining the last waypoint - // obtains the read lock for the course - final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint(); - MarkPassing lastBeforeOrAt = roundings.floor(new DummyMarkPassingWithTimePointOnly(at)); - // already finished the race? - if (lastBeforeOrAt != null) { - // and not at or after last mark passing - if (lastWaypoint != lastBeforeOrAt.getWaypoint()) { - trackedLeg = getTrackedLegStartingAt(lastBeforeOrAt.getWaypoint()); - } else { - // exactly *at* last mark passing? - if (!roundings.isEmpty() && at.equals(roundings.last().getTimePoint())) { - // exactly at finish line; return last leg - trackedLeg = getTrackedLegFinishingAt(lastBeforeOrAt.getWaypoint()); - } else { - // no, then we're after the last mark passing - trackedLeg = null; - } - } - } else { - // before beginning of race - trackedLeg = null; - } - if (trackedLeg != null) { - result = trackedLeg.getTrackedLeg(competitor); - } - } - return result; - } - - public TrackedLeg getTrackedLeg(Leg leg) { - getRace().getCourse().lockForRead(); - try { - return trackedLegs.get(leg); - } finally { - getRace().getCourse().unlockAfterRead(); - } - } - - @Override - public TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, Leg leg) { - final TrackedLeg trackedLeg = getTrackedLeg(leg); - return trackedLeg == null ? null : trackedLeg.getTrackedLeg(competitor); - } - - @Override - public long getUpdateCount() { - return updateCount; - } - - @Override - public int getRankDifference(Competitor competitor, Leg leg, TimePoint timePoint) { - int previousRank; - if (leg == getRace().getCourse().getFirstLeg()) { - // first leg; report rank difference from 0 - previousRank = 0; - } else { - TrackedLeg previousLeg = getTrackedLegFinishingAt(leg.getFrom()); - previousRank = previousLeg.getTrackedLeg(competitor).getRank(timePoint); - } - int currentRank = getTrackedLeg(competitor, leg).getRank(timePoint); - return currentRank - previousRank; - } - - @Override - public int getRank(Competitor competitor) throws NoWindException { - return getRank(competitor, MillisecondsTimePoint.now()); - } - - @Override - public Competitor getOverallLeader(TimePoint timePoint) throws NoWindException { - try { - Competitor result = null; - List ranks = getCompetitorsFromBestToWorst(timePoint); - if (ranks != null && !ranks.isEmpty()) { - result = ranks.iterator().next(); - } - return result; - } catch (NoWindError e) { - throw e.getCause(); - } - } - - @Override - public int getRank(Competitor competitor, TimePoint timePoint) throws NoWindException { - try { - int result; - if (getMarkPassings(competitor).isEmpty()) { - result = 0; - } else { - result = getCompetitorsFromBestToWorst(timePoint).indexOf(competitor) + 1; - } - return result; - } catch (NoWindError e) { - throw e.getCause(); - } - } - - @Override - public List getCompetitorsFromBestToWorst(TimePoint timePoint) throws NoWindException { - NamedReentrantReadWriteLock readWriteLock; - synchronized (competitorRankingsLocks) { - readWriteLock = competitorRankingsLocks.get(timePoint); - if (readWriteLock == null) { - readWriteLock = new NamedReentrantReadWriteLock("competitor rankings for race " + getRace().getName() - + " for time point " + timePoint, /* fair */false); - competitorRankingsLocks.put(timePoint, readWriteLock); - } - } - List rankedCompetitors; - final boolean lockForWrite; - synchronized (competitorRankings) { - rankedCompetitors = competitorRankings.get(timePoint); - if (rankedCompetitors == null) { - lockForWrite = true; - } else { - lockForWrite = false; - } - } - if (lockForWrite) { - LockUtil.lockForWrite(readWriteLock); - } else { - LockUtil.lockForRead(readWriteLock); - } - try { - if (rankedCompetitors == null) { - rankedCompetitors = competitorRankings.get(timePoint); // try again; maybe a writer released the write - // lock after updating the cache - if (rankedCompetitors == null) { - RaceRankComparator comparator = new RaceRankComparator(this, timePoint); - rankedCompetitors = new ArrayList(); - for (Competitor c : getRace().getCompetitors()) { - rankedCompetitors.add(c); - } - Collections.sort(rankedCompetitors, comparator); - synchronized (competitorRankings) { - competitorRankings.put(timePoint, rankedCompetitors); - } - } - } - return rankedCompetitors; - } finally { - if (lockForWrite) { - LockUtil.unlockAfterWrite(readWriteLock); - } else { - LockUtil.unlockAfterRead(readWriteLock); - } - } - } - - @Override - public Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) - throws NoWindException { - NavigableSet markPassings = getMarkPassings(competitor); - TimePoint from = null; - lockForRead(markPassings); - try { - if (markPassings != null && !markPassings.isEmpty()) { - from = markPassings.iterator().next().getTimePoint(); - } - } finally { - unlockAfterRead(markPassings); - } - Distance result; - if (from != null) { - result = getAverageCrossTrackError(competitor, from, timePoint, /* upwindOnly */true, waitForLatestAnalysis); - } else { - result = null; - } - return result; - } - - @Override - public Distance getAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, - boolean waitForLatestAnalysis) throws NoWindException { - Distance result; - result = crossTrackErrorCache - .getAverageCrossTrackError(competitor, from, to, upwindOnly, waitForLatestAnalysis); - return result; - } - - @Override - public TrackedLegOfCompetitor getCurrentLeg(Competitor competitor, TimePoint timePoint) { - NavigableSet competitorMarkPassings = markPassingsForCompetitor.get(competitor); - DummyMarkPassingWithTimePointOnly markPassingTimePoint = new DummyMarkPassingWithTimePointOnly(timePoint); - TrackedLegOfCompetitor result = null; - if (!competitorMarkPassings.isEmpty()) { - MarkPassing lastMarkPassingAtOfBeforeTimePoint = competitorMarkPassings.floor(markPassingTimePoint); - if (lastMarkPassingAtOfBeforeTimePoint != null) { - Waypoint waypointPassedLastAtOrBeforeTimePoint = lastMarkPassingAtOfBeforeTimePoint.getWaypoint(); - // don't return a leg if competitor has already finished last leg and therefore the race - if (waypointPassedLastAtOrBeforeTimePoint != getRace().getCourse().getLastWaypoint()) { - result = getTrackedLegStartingAt(waypointPassedLastAtOrBeforeTimePoint).getTrackedLeg(competitor); - } - } - } - return result; - } - - @Override - public TrackedLeg getCurrentLeg(TimePoint timePoint) { - Waypoint lastWaypointPassed = null; - int indexOfLastWaypointPassed = -1; - for (Map.Entry> entry : markPassingsForWaypoint.entrySet()) { - if (!entry.getValue().isEmpty()) { - MarkPassing first = entry.getValue().first(); - // Did the mark passing happen at or before the requested time point? - if (first.getTimePoint().compareTo(timePoint) <= 0) { - int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(entry.getKey()); - if (indexOfWaypoint > indexOfLastWaypointPassed) { - indexOfLastWaypointPassed = indexOfWaypoint; - lastWaypointPassed = entry.getKey(); - } - } - } - } - TrackedLeg result = null; - if (lastWaypointPassed != null && lastWaypointPassed != getRace().getCourse().getLastWaypoint()) { - result = getTrackedLegStartingAt(lastWaypointPassed); - } - return result; - } - - @Override - public MarkPassing getMarkPassing(Competitor competitor, Waypoint waypoint) { - final NavigableSet markPassings = getMarkPassings(competitor); - if (markPassings != null) { - lockForRead(markPassings); - try { - for (MarkPassing markPassing : markPassings) { - if (markPassing.getWaypoint() == waypoint) { - return markPassing; - } - } - } finally { - unlockAfterRead(markPassings); - } - } - return null; - } - - /** - * This method was a synchronization bottleneck when it was using a regular HashMap for {@link #markTracks}. It is - * frequently used, and the most frequent case is that the get call on {@link #markTracks} succeeds - * with a non-null result. To improve performance for this case, {@link #markTracks} now is a - * {@link ConcurrentHashMap} that can be read while writes are going on without locking or synchronization. Only if - * the get call does not provide a result, the entire procedure is repeated, this time with - * synchronization to avoid duplicate track creation for the same mark. - */ - @Override - public GPSFixTrack getOrCreateTrack(Mark mark) { - GPSFixTrack result = markTracks.get(mark); - if (result == null) { - // try again, this time with more expensive synchronization - synchronized (markTracks) { - LockUtil.lockForRead(getSerializationLock()); - try { - result = markTracks.get(mark); - if (result == null) { - result = createMarkTrack(mark); - markTracks.put(mark, result); - } - } finally { - LockUtil.unlockAfterRead(getSerializationLock()); - } - } - } - return result; - } - - protected DynamicGPSFixTrackImpl createMarkTrack(Mark mark) { - return new DynamicGPSFixTrackImpl(mark, millisecondsOverWhichToAverageSpeed); - } - - @Override - public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) { - Position result = null; - for (Mark mark : waypoint.getMarks()) { - Position nextPos = getOrCreateTrack(mark).getEstimatedPosition(timePoint, /* extrapolate */false); - if (result == null) { - result = nextPos; - } else if (nextPos != null) { - result = result.translateGreatCircle(result.getBearingGreatCircle(nextPos), result.getDistance(nextPos) - .scale(0.5)); - } - } - return result; - } - - @Override - public boolean hasWindData() { - boolean result = false; - Course course = getRace().getCourse(); - Waypoint firstWaypoint = course.getFirstWaypoint(); - TimePoint timepoint = startTime != null ? startTime : startOfTrackingReceived; - if (firstWaypoint != null && timepoint != null) { - Position position = getApproximatePosition(firstWaypoint, timepoint); - if (position != null) { - Wind wind = getWind(position, timepoint); - if (wind != null) { - result = true; - } - } - } - return result; - } - - @Override - public boolean hasGPSData() { - boolean result = false; - if (!tracks.values().isEmpty()) { - for (GPSFixTrack gpsTrack : tracks.values()) { - if (gpsTrack.getFirstRawFix() != null) { - result = true; - break; - } - } - } - return result; - } - - @Override - public Wind getWind(Position p, TimePoint at) { - return getWind(p, at, getWindSourcesToExclude()); - } - - @Override - public Wind getWind(Position p, TimePoint at, Iterable windSourcesToExclude) { - final WindWithConfidence> windWithConfidence = getWindWithConfidence(p, at, - windSourcesToExclude); - return windWithConfidence == null ? null : windWithConfidence.getObject(); - } - - @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { - return getWindWithConfidence(p, at, getWindSourcesToExclude()); - } - - @Override - public Iterable getWindSourcesToExclude() { - synchronized (windSourcesToExclude) { - return Collections.unmodifiableCollection(windSourcesToExclude); - } - } - - @Override - public void setWindSourcesToExclude(Iterable windSourcesToExclude) { - Set old = new HashSet<>(this.windSourcesToExclude); - synchronized (this.windSourcesToExclude) { - LockUtil.lockForRead(getSerializationLock()); - try { - this.windSourcesToExclude.clear(); - for (WindSource windSourceToExclude : windSourcesToExclude) { - this.windSourcesToExclude.add(windSourceToExclude); - } - } finally { - LockUtil.unlockAfterRead(getSerializationLock()); - } - } - if (!old.equals(this.windSourcesToExclude)) { - clearAllCachesExceptManeuvers(); - triggerManeuverCacheRecalculationForAllCompetitors(); - } - } - - @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, - Iterable windSourcesToExclude) { - boolean canUseSpeedOfAtLeastOneWindSource = false; - Weigher> timeWeigherThatPretendsToAlsoWeighPositions = new PositionAndTimePointWeigher( - /* halfConfidenceAfterMilliseconds */10000l); - ConfidenceBasedWindAverager> averager = ConfidenceFactory.INSTANCE - .createWindAverager(timeWeigherThatPretendsToAlsoWeighPositions); - List>> windFixesWithConfidences = new ArrayList>>(); - for (WindSource windSource : getWindSources()) { - // TODO consider parallelizing and consider caching - if (!Util.contains(windSourcesToExclude, windSource)) { - WindTrack track = getOrCreateWindTrack(windSource); - WindWithConfidence> windWithConfidence = track.getAveragedWindWithConfidence( - p, at); - if (windWithConfidence != null) { - windFixesWithConfidences.add(windWithConfidence); - canUseSpeedOfAtLeastOneWindSource = canUseSpeedOfAtLeastOneWindSource - || windSource.getType().useSpeed(); - } - } - } - HasConfidence> average = averager.getAverage( - windFixesWithConfidences, new Pair(p, at)); - WindWithConfidence> result = average == null ? null - : new WindWithConfidenceImpl>(average.getObject(), average.getConfidence(), - new Pair(p, at), canUseSpeedOfAtLeastOneWindSource); - return result; - } - - @Override - public Wind getDirectionFromStartToNextMark(final TimePoint at) { - Future future; - FutureTask newFuture = null; - synchronized (directionFromStartToNextMarkCache) { - future = directionFromStartToNextMarkCache.get(at); - if (future == null) { - newFuture = new FutureTask(new Callable() { - @Override - public Wind call() { - Wind result; - Leg firstLeg = getRace().getCourse().getFirstLeg(); - if (firstLeg != null) { - Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at); - Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at); - if (firstLegStart != null && firstLegEnd != null) { - result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(1.0, - firstLegEnd.getBearingGreatCircle(firstLegStart))); - } else { - result = null; - } - } else { - result = null; - } - return result; - } - }); - directionFromStartToNextMarkCache.put(at, newFuture); - } - } - if (newFuture != null) { - newFuture.run(); - future = newFuture; - } - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - - @Override - public TimePoint getTimePointOfOldestEvent() { - return timePointOfOldestEvent; - } - - @Override - public TimePoint getTimePointOfNewestEvent() { - return timePointOfNewestEvent; - } - - @Override - public TimePoint getTimePointOfLastEvent() { - return timePointOfLastEvent; - } - - /** - * @param timeOfEvent - * may be null meaning to only unblock waiters but not update any time points - */ - protected void updated(TimePoint timeOfEvent) { - updateCount++; - clearAllCachesExceptManeuvers(); - if (timeOfEvent != null) { - if (timePointOfNewestEvent == null || timePointOfNewestEvent.compareTo(timeOfEvent) < 0) { - timePointOfNewestEvent = timeOfEvent; - } - if (timePointOfOldestEvent == null || timePointOfOldestEvent.compareTo(timeOfEvent) > 0) { - timePointOfOldestEvent = timeOfEvent; - } - timePointOfLastEvent = timeOfEvent; - } - synchronized (this) { - notifyAll(); - } - } - - protected void setStartTimeReceived(TimePoint start) { - if ((start == null) != (startTimeReceived == null) || (start != null && !start.equals(startTimeReceived))) { - this.startTimeReceived = start; - invalidateStartTime(); - invalidateMarkPassingTimes(); - } - } - - @Override - public TimePoint getStartTimeReceived() { - return startTimeReceived; - } - - protected void setStartOfTrackingReceived(TimePoint startOfTracking) { - this.startOfTrackingReceived = startOfTracking; - } - - protected void setEndOfTrackingReceived(TimePoint endOfTracking) { - this.endOfTrackingReceived = endOfTracking; - } - - /** - * Schedules the clearing of the caches. If a cache clearing is already scheduled, this is a no-op. - */ - private void clearAllCachesExceptManeuvers() { - synchronized (cacheInvalidationTimerLock) { - if (cacheInvalidationTimer == null) { - cacheInvalidationTimer = new Timer("Cache invalidation timer for TrackedRaceImpl " - + getRace().getName()); - cacheInvalidationTimer.schedule(new TimerTask() { - @Override - public void run() { - synchronized (cacheInvalidationTimerLock) { - cacheInvalidationTimer.cancel(); - cacheInvalidationTimer = null; - } - synchronized (competitorRankings) { - competitorRankings.clear(); - } - synchronized (competitorRankingsLocks) { - competitorRankingsLocks.clear(); - } - } - }, DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS); - } - } - } - - @Override - public synchronized void waitForNextUpdate(int sinceUpdate) throws InterruptedException { - while (updateCount <= sinceUpdate) { - wait(); // ...until updated(...) notifies us - } - } - - @Override - public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { - // expecting to hold the course's write lock - invalidateMarkPassingTimes(); - LockUtil.lockForRead(getSerializationLock()); - try { - // assuming that getRace().getCourse()'s write lock is held by the current thread - updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointAdded(zeroBasedIndex, waypointThatGotAdded); - getOrCreateMarkPassingsInOrderAsNavigableSet(waypointThatGotAdded); - for (Mark mark : waypointThatGotAdded.getMarks()) { - getOrCreateTrack(mark); - } - // a waypoint got added; this means that a leg got added as well; but we shouldn't claim we know where - // in the leg list of the course the leg was added; that's an implementation secret of CourseImpl. So try: - LinkedHashMap reorderedTrackedLegs = new LinkedHashMap(); - List newLegs = getRace().getCourse().getLegs(); - for (Leg leg : newLegs) { - TrackedLeg trackedLeg = trackedLegs.get(leg); - if (trackedLeg != null) { - reorderedTrackedLegs.put(leg, trackedLeg); - } else { - reorderedTrackedLegs.put(leg, createTrackedLeg(leg)); - } - } - // now ensure that the iteration order is in sync with the leg iteration order - trackedLegs.clear(); - for (Map.Entry entry : reorderedTrackedLegs.entrySet()) { - trackedLegs.put(entry.getKey(), entry.getValue()); - entry.getValue().waypointsMayHaveChanges(); - } - updated(/* time point */null); // no maneuver cache invalidation required because we don't yet have mark - // passings for new waypoint - } finally { - LockUtil.unlockAfterRead(getSerializationLock()); - } - } - - private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointAdded(int zeroBasedIndex, - Waypoint waypointThatGotAdded) { - if (zeroBasedIndex < 2) { - // the observing listener on any previous mark will be GCed; we need to ensure - // that the cache is recomputed - clearDirectionFromStartToNextMarkCache(); - Iterator waypointsIter = getRace().getCourse().getWaypoints().iterator(); - waypointsIter.next(); // skip first - if (waypointsIter.hasNext()) { - waypointsIter.next(); // skip second - if (waypointsIter.hasNext()) { - Waypoint oldSecond = waypointsIter.next(); - stopAndRemoveStartToNextMarkCacheInvalidationListener(oldSecond); - } - } - } - addStartToNextMarkCacheInvalidationListener(waypointThatGotAdded); - } - - private void clearDirectionFromStartToNextMarkCache() { - synchronized (directionFromStartToNextMarkCache) { - directionFromStartToNextMarkCache.clear(); - } - } - - private void addStartToNextMarkCacheInvalidationListener(Waypoint waypoint) { - for (Mark mark : waypoint.getMarks()) { - addStartToNextMarkCacheInvalidationListener(mark); - } - } - - private void addStartToNextMarkCacheInvalidationListener(Mark mark) { - GPSFixTrack track = getOrCreateTrack(mark); - StartToNextMarkCacheInvalidationListener listener = new StartToNextMarkCacheInvalidationListener(track); - LockUtil.lockForRead(getSerializationLock()); - try { - startToNextMarkCacheInvalidationListeners.put(mark, listener); - } finally { - LockUtil.unlockAfterRead(getSerializationLock()); - } - track.addListener(listener); - } - - private void stopAndRemoveStartToNextMarkCacheInvalidationListener(Waypoint waypoint) { - for (Mark mark : waypoint.getMarks()) { - stopAndRemoveStartToNextMarkCacheInvalidationListener(mark); - } - } - - private void stopAndRemoveStartToNextMarkCacheInvalidationListener(Mark mark) { - StartToNextMarkCacheInvalidationListener listener = startToNextMarkCacheInvalidationListeners.get(mark); - if (listener != null) { - listener.stopListening(); - LockUtil.lockForRead(getSerializationLock()); - try { - startToNextMarkCacheInvalidationListeners.remove(mark); - } finally { - LockUtil.unlockAfterRead(getSerializationLock()); - } - } - } - - @Override - public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { - invalidateMarkPassingTimes(); - LockUtil.lockForRead(getSerializationLock()); - try { - // assuming that getRace().getCourse()'s write lock is held by the current thread - updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(zeroBasedIndex, - waypointThatGotRemoved); - Leg toRemove = null; - Leg last = null; - int i = 0; - for (Map.Entry e : trackedLegs.entrySet()) { - last = e.getKey(); - if (i == zeroBasedIndex) { - toRemove = e.getKey(); - break; - } - i++; - } - if (toRemove == null && !trackedLegs.isEmpty()) { - // last waypoint removed - toRemove = last; - } - if (toRemove != null) { - logger.info("Removing tracked leg at zero-based index " + zeroBasedIndex + " from tracked race " - + getRace().getName()); - LinkedHashMap newTrackedLegs = new LinkedHashMap<>(); - for (Map.Entry trackedLegsEntry : trackedLegs.entrySet()) { - if (trackedLegsEntry.getKey() == toRemove) { - break; - } else { - newTrackedLegs.put(trackedLegsEntry.getKey(), trackedLegsEntry.getValue()); - } - } - trackedLegs.clear(); - trackedLegs.putAll(newTrackedLegs); - List newLegs = getRace().getCourse().getLegs(); - for (int j = zeroBasedIndex; j < newLegs.size(); j++) { - trackedLegs.put(newLegs.get(j), createTrackedLeg(newLegs.get(j))); - } - updated(/* time point */null); - } - // remove all corresponding markpassings if a waypoint has been removed - NavigableSet markPassingsRemoved; - markPassingsRemoved = markPassingsForWaypoint.remove(waypointThatGotRemoved); - for (NavigableSet markPassingsForOneCompetitor : markPassingsForCompetitor.values()) { - if (!markPassingsForOneCompetitor.isEmpty()) { - final Competitor competitor = markPassingsForOneCompetitor.iterator().next().getCompetitor(); - LockUtil.lockForWrite(getMarkPassingsLock(markPassingsForOneCompetitor)); - try { - markPassingsForOneCompetitor.removeAll(markPassingsRemoved); - } finally { - LockUtil.unlockAfterWrite(getMarkPassingsLock(markPassingsForOneCompetitor)); - } - triggerManeuverCacheRecalculation(competitor); - } - } - } finally { - LockUtil.unlockAfterRead(getSerializationLock()); - } - } - - protected NamedReentrantReadWriteLock getMarkPassingsLock(Iterable markPassings) { - synchronized (locksForMarkPassings) { - NamedReentrantReadWriteLock lock = locksForMarkPassings.get(markPassings); - if (lock == null) { - lock = new NamedReentrantReadWriteLock("mark passings lock for tracked race " + getRace().getName(), /* fair */ - false); - locksForMarkPassings.put(markPassings, lock); - } - return lock; - } - } - - private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(int zeroBasedIndex, - Waypoint waypointThatGotRemoved) { - if (zeroBasedIndex < 2) { - // the observing listener on any previous mark will be GCed; we need to ensure - // that the cache is recomputed - clearDirectionFromStartToNextMarkCache(); - stopAndRemoveStartToNextMarkCacheInvalidationListener(waypointThatGotRemoved); - Iterator waypointsIter = getRace().getCourse().getWaypoints().iterator(); - if (waypointsIter.hasNext()) { // catches the case of a course being empty - waypointsIter.next(); // skip first - if (waypointsIter.hasNext()) { - waypointsIter.next(); // skip second - if (waypointsIter.hasNext()) { - Waypoint newSecond = waypointsIter.next(); - addStartToNextMarkCacheInvalidationListener(newSecond); - } - } - } - } - } - - @Override - public TrackedRegatta getTrackedRegatta() { - return trackedRegatta; - } - - @Override - public Wind getEstimatedWindDirection(Position position, TimePoint timePoint) { - WindWithConfidence estimatedWindWithConfidence = getEstimatedWindDirectionWithConfidence(position, - timePoint); - return estimatedWindWithConfidence == null ? null : estimatedWindWithConfidence.getObject(); - } - - @Override - public WindWithConfidence getEstimatedWindDirectionWithConfidence(Position position, TimePoint timePoint) { - DummyMarkPassingWithTimePointOnly dummyMarkPassingForNow = new DummyMarkPassingWithTimePointOnly(timePoint); - Weigher weigher = ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( - // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero - getMillisecondsOverWhichToAverageSpeed(), /* minimum confidence */0.0000000001); - Map> bearings = clusterBearingsByLegType(timePoint, position, - dummyMarkPassingForNow, weigher); - // use the minimum confidence of the four "quadrants" as the result's confidence - BearingWithConfidenceImpl reversedUpwindAverage = null; - int upwindNumberOfRelevantBoats = 0; - double confidence = 0; - int numberOfBoatsRelevantForEstimate = 0; - BearingWithConfidence resultBearing = null; - if (bearings != null) { - BearingWithConfidenceCluster[] bearingClustersUpwind = bearings.get(LegType.UPWIND).splitInTwo( - getMinimumAngleBetweenDifferentTacksUpwind(), timePoint); - if (!bearingClustersUpwind[0].isEmpty() && !bearingClustersUpwind[1].isEmpty()) { - BearingWithConfidence average0 = bearingClustersUpwind[0].getAverage(timePoint); - BearingWithConfidence average1 = bearingClustersUpwind[1].getAverage(timePoint); - upwindNumberOfRelevantBoats = Math - .min(bearingClustersUpwind[0].size(), bearingClustersUpwind[1].size()); - confidence = Math.min(average0.getConfidence(), average1.getConfidence()) - * getRace().getBoatClass().getUpwindWindEstimationConfidence(upwindNumberOfRelevantBoats); - reversedUpwindAverage = new BearingWithConfidenceImpl(average0.getObject() - .middle(average1.getObject()).reverse(), confidence, timePoint); - } - BearingWithConfidenceImpl downwindAverage = null; - int downwindNumberOfRelevantBoats = 0; - BearingWithConfidenceCluster[] bearingClustersDownwind = bearings.get(LegType.DOWNWIND) - .splitInTwo(getMinimumAngleBetweenDifferentTacksDownwind(), timePoint); - if (!bearingClustersDownwind[0].isEmpty() && !bearingClustersDownwind[1].isEmpty()) { - BearingWithConfidence average0 = bearingClustersDownwind[0].getAverage(timePoint); - BearingWithConfidence average1 = bearingClustersDownwind[1].getAverage(timePoint); - downwindNumberOfRelevantBoats = Math.min(bearingClustersDownwind[0].size(), - bearingClustersDownwind[1].size()); - confidence = Math.min(average0.getConfidence(), average1.getConfidence()) - * getRace().getBoatClass().getDownwindWindEstimationConfidence(downwindNumberOfRelevantBoats); - downwindAverage = new BearingWithConfidenceImpl(average0.getObject().middle( - average1.getObject()), confidence, timePoint); - } - numberOfBoatsRelevantForEstimate = upwindNumberOfRelevantBoats + downwindNumberOfRelevantBoats; - BearingWithConfidenceCluster resultCluster = new BearingWithConfidenceCluster(weigher); - assert upwindNumberOfRelevantBoats == 0 || reversedUpwindAverage != null; - if (upwindNumberOfRelevantBoats > 0) { - resultCluster.add(reversedUpwindAverage); - } - assert downwindNumberOfRelevantBoats == 0 || downwindAverage != null; - if (downwindNumberOfRelevantBoats > 0) { - resultCluster.add(downwindAverage); - } - resultBearing = resultCluster.getAverage(timePoint); - } - return resultBearing == null ? null : new WindWithConfidenceImpl(new WindImpl(null, timePoint, - new KnotSpeedWithBearingImpl( - /* speedInKnots */numberOfBoatsRelevantForEstimate, resultBearing.getObject())), - resultBearing.getConfidence(), resultBearing.getRelativeTo(), /* useSpeed */false); - } - - // TODO confidences need to be computed not only based on timePoint but also on position: boats far away don't - // contribute as confidently as boats close by - private Map> clusterBearingsByLegType(TimePoint timePoint, - Position position, DummyMarkPassingWithTimePointOnly dummyMarkPassingForNow, Weigher weigher) { - Weigher weigherForMarkPassingProximity = new HyperbolicTimeDifferenceWeigher( - getMillisecondsOverWhichToAverageSpeed() * 5); - Map> bearings = new HashMap>(); - for (LegType legType : LegType.values()) { - bearings.put(legType, new BearingWithConfidenceCluster(weigher)); - } - Map legTypesCache = new HashMap(); - getRace().getCourse().lockForRead(); // ensure the course doesn't change, particularly lose the leg we're - // interested in, while we're running - try { - for (Competitor competitor : getRace().getCompetitors()) { - TrackedLegOfCompetitor leg; - try { - leg = getTrackedLeg(competitor, timePoint); - } catch (IllegalArgumentException iae) { - logger.warning("Caught " + iae + " during wind estimation; ignoring seemingly broken leg"); - logger.log(Level.SEVERE, "clusterBearingsByLegType", iae); - // supposedly, we got a "Waypoint X isn't start of any leg in Y" exception; leg not found - leg = null; - } - // if bearings was set to null this indicates there was an exception; no need for further calculations, - // return null - if (bearings != null && leg != null) { - TrackedLeg trackedLeg = getTrackedLeg(leg.getLeg()); - LegType legType; - try { - legType = legTypesCache.get(trackedLeg); - if (legType == null) { - legType = trackedLeg.getLegType(timePoint); - legTypesCache.put(trackedLeg, legType); - } - if (legType != LegType.REACHING) { - GPSFixTrack track = getTrack(competitor); - if (!track.hasDirectionChange(timePoint, - /* be even more conservative than maneuver detection to really try to get "straight line" behavior */ - getManeuverDegreeAngleThreshold()/2.)) { - SpeedWithBearingWithConfidence estimatedSpeedWithConfidence = track - .getEstimatedSpeed(timePoint, weigher); - if (estimatedSpeedWithConfidence != null - && estimatedSpeedWithConfidence.getObject() != null && - // Mark passings may be missing or far off. This can lead to boats apparently - // going "backwards" regarding the leg's direction; ignore those - isNavigatingForward(estimatedSpeedWithConfidence.getObject().getBearing(), - trackedLeg, timePoint)) { - // additionally to generally excluding maneuvers, reduce confidence around mark - // passings: - NavigableSet markPassings = getMarkPassings(competitor); - double markPassingProximityConfidenceReduction = 1.0; - lockForRead(markPassings); - try { - NavigableSet prevMarkPassing = markPassings.headSet( - dummyMarkPassingForNow, /* inclusive */true); - NavigableSet nextMarkPassing = markPassings.tailSet( - dummyMarkPassingForNow, /* inclusive */true); - if (prevMarkPassing != null && !prevMarkPassing.isEmpty()) { - markPassingProximityConfidenceReduction *= Math.max(0.0, - 1.0 - weigherForMarkPassingProximity.getConfidence(prevMarkPassing - .last().getTimePoint(), timePoint)); - } - if (nextMarkPassing != null && !nextMarkPassing.isEmpty()) { - markPassingProximityConfidenceReduction *= Math.max(0.0, - 1.0 - weigherForMarkPassingProximity.getConfidence(nextMarkPassing - .first().getTimePoint(), timePoint)); - } - } finally { - unlockAfterRead(markPassings); - } - BearingWithConfidence bearing = new BearingWithConfidenceImpl( - estimatedSpeedWithConfidence.getObject() == null ? null - : estimatedSpeedWithConfidence.getObject().getBearing(), - markPassingProximityConfidenceReduction - * estimatedSpeedWithConfidence.getConfidence(), - estimatedSpeedWithConfidence.getRelativeTo()); - BearingWithConfidenceCluster bearingClusterForLegType = bearings - .get(legType); - bearingClusterForLegType.add(bearing); - } - } - } - } catch (NoWindException e) { - logger.fine("Unable to determine leg type for race " + getRace().getName() - + " while trying to estimate wind (Background: I've got a NoWindException)"); - bearings = null; - } - } - } - } finally { - getRace().getCourse().unlockAfterRead(); - } - return bearings; - } - - /** - * Checks if the bearing generally moves in the direction that the trackedLeg has at time - * point at. - */ - private boolean isNavigatingForward(Bearing bearing, TrackedLeg trackedLeg, TimePoint at) { - Bearing legBearing = trackedLeg.getLegBearing(at); - return Math.abs(bearing.getDifferenceTo(legBearing).getDegrees()) < 90; - } - - /** - * This is probably best explained by example. If the wind bearing is from port to starboard, the situation looks - * like this: - * - *

-     *                                 ^
-     *                 Wind            | Boat
-     *               ----------->      |
-     *                                 |
-     * 
-     * 
- * - * In this case, the boat gets the wind from port, so the result has to be {@link Tack#PORT}. The angle between the - * boat's heading (which we can only approximate by the boat's course over ground) and the wind bearing in this case - * is 90 degrees. wind.{@link Bearing#getDifferenceTo(Bearing) getDifferenceTo}(boat) in this case will - * return a bearing representing -90 degrees. - *

- * - * If the wind is blowing the other way, the angle returned by {@link Bearing#getDifferenceTo(Bearing)} will - * correspond to +90 degrees. In other words, a negative angle means starboard tack, a positive angle represents - * port tack. - *

- * - * For the unlikely case of 0 degrees difference, {@link Tack#STARBOARD} will result. - * - * @return null in case the boat's bearing cannot be determined for timePoint - * @throws NoWindException - */ - @Override - public Tack getTack(Competitor competitor, TimePoint timePoint) throws NoWindException { - final SpeedWithBearing estimatedSpeed = getTrack(competitor).getEstimatedSpeed(timePoint); - Tack result = null; - if (estimatedSpeed != null) { - result = getTack(getTrack(competitor).getEstimatedPosition(timePoint, /* extrapolate */false), timePoint, - estimatedSpeed.getBearing()); - } - return result; - } - - /** - * Based on the wind direction at timePoint and at position where, compares the - * boatBearing to the wind's bearing at that time and place and determined the tack. - * - * @throws NoWindException - * in case the wind cannot be determined because without a wind direction, the tack cannot be determined - * either - */ - private Tack getTack(Position where, TimePoint timePoint, Bearing boatBearing) throws NoWindException { - final Wind wind = getWind(where, timePoint); - Tack result; - if (wind == null) { - throw new NoWindException("Can't determine wind direction in position " + where + " at " + timePoint - + ", therefore cannot determine tack"); - } - Bearing windBearing = wind.getBearing(); - Bearing difference = windBearing.getDifferenceTo(boatBearing); - result = difference.getDegrees() <= 0 ? Tack.PORT : Tack.STARBOARD; - return result; - } - - @Override - public String toString() { - return "TrackedRace for " + getRace(); - } - - @Override - public List approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) { - DouglasPeucker douglasPeucker = new DouglasPeucker( - getTrack(competitor)); - return douglasPeucker.approximate(maxDistance, from, to); - } - - protected void triggerManeuverCacheRecalculationForAllCompetitors() { - final List shuffledCompetitors = new ArrayList<>(); - for (Competitor competitor : (getRace().getCompetitors())) { - shuffledCompetitors.add(competitor); - } - Collections.shuffle(shuffledCompetitors); - for (Competitor competitor : shuffledCompetitors) { - triggerManeuverCacheRecalculation(competitor); - } - } - - protected void triggerManeuverCacheRecalculation(final Competitor competitor) { - maneuverCache.triggerUpdate(competitor, /* updateInterval */null); - } - - private Triple> computeManeuvers(Competitor competitor) throws NoWindException { - logger.finest("computeManeuvers(" + competitor.getName() + ") called in tracked race " + this); - long startedAt = System.currentTimeMillis(); - // compute the maneuvers for competitor - Triple> result = null; - NavigableSet markPassings = getMarkPassings(competitor); - boolean markPassingsNotEmpty; - TimePoint extendedFrom = null; - MarkPassing crossedFinishLine = null; - // getLastWaypoint() will wait for a read lock on the course; do this outside the synchronized block to avoid - // deadlocks - final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint(); - lockForRead(markPassings); - try { - markPassingsNotEmpty = markPassings != null && !markPassings.isEmpty(); - if (markPassingsNotEmpty) { - extendedFrom = markPassings.iterator().next().getTimePoint(); - crossedFinishLine = getMarkPassing(competitor, lastWaypoint); - } - } finally { - unlockAfterRead(markPassings); - } - if (markPassingsNotEmpty) { - TimePoint extendedTo; - if (crossedFinishLine != null) { - extendedTo = crossedFinishLine.getTimePoint(); - } else { - final GPSFixMoving lastRawFix = getTrack(competitor).getLastRawFix(); - if (lastRawFix != null) { - extendedTo = lastRawFix.getTimePoint(); - } else { - extendedTo = null; - } - } - if (extendedTo != null) { - try { - List extendedResultForCache = detectManeuvers( - competitor, - approximate(competitor, - getRace().getBoatClass().getMaximumDistanceForCourseApproximation(), extendedFrom, - extendedTo)); - result = new Triple>(extendedFrom, extendedTo, - extendedResultForCache); - } catch (NoWindException ex) { - // Catching the NoWindException here without letting it propagate thru other handlers. - // This is mainly to avoid having logs flooded with stack traces. It is safe to catch - // it here because we can assume that this exception does not hide any severe problem - // other than that there is no wind. Because maneuvers are mostly computed using a - // future cache and need wind (like getTack) they will often fail before the wind has been - // loaded from database. We can safely return null here because we can be sure that - // cache will be updated when new fixes are fed into the stream therefore leading to a - // recomputation. - logger.fine("NoWindException during computation of maneuvers for " + competitor.getName()); - } - } // else competitor has no fixes to consider; remove any maneuver cache entry - } // else competitor hasn't started yet; remove any maneuver cache entry - logger.finest("computeManeuvers(" + competitor.getName() + ") called in tracked race " + this + " took " - + (System.currentTimeMillis() - startedAt) + "ms"); - return result; - } - - /** - * Tries to detect maneuvers on the competitor's track based on a number of approximating fixes. The - * fixes contain bearing information, but this is not the bearing leading to the next approximation fix but the - * bearing the boat had at the time of the approximating fix which is taken from the original track.

- * - * The time period assumed for a maneuver duration is taken from the - * {@link BoatClass#getApproximateManeuverDurationInMilliseconds() boat class}. If no maneuver is detected, an empty - * list is returned. Maneuvers can only be expected to be detected if at least three fixes are provided in - * approximatedFixesToAnalyze. For the inner approximating fixes (all except the first and the last - * approximating fix), their course changes according to the approximated path (and not the underlying actual - * tracked fixes) are computed. Subsequent course changes to the same direction are then grouped. Those in closer - * timely distance than {@link #getApproximateManeuverDurationInMilliseconds()} (including single course changes - * that have no surrounding other course changes to group) are grouped into one {@link Maneuver}. - * - * @return an empty list if no maneuver is detected for competitor between from and - * to, or else the list of maneuvers detected. - */ - private List detectManeuvers(Competitor competitor, List approximatingFixesToAnalyze) - throws NoWindException { - List result = new ArrayList(); - if (approximatingFixesToAnalyze.size() > 2) { - List> courseChangeSequenceInSameDirection = new ArrayList>(); - Iterator approximationPointsIter = approximatingFixesToAnalyze.iterator(); - GPSFixMoving previous = approximationPointsIter.next(); - GPSFixMoving current = approximationPointsIter.next(); - // the bearings in these variables are between approximation points - SpeedWithBearing speedWithBearingOnApproximationFromPreviousToCurrent = previous - .getSpeedAndBearingRequiredToReach(current); - SpeedWithBearing speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges = speedWithBearingOnApproximationFromPreviousToCurrent; - SpeedWithBearing speedWithBearingOnApproximationFromCurrentToNext; // will certainly be assigned because - // iter's collection's size > 2 - do { - GPSFixMoving next = approximationPointsIter.next(); - speedWithBearingOnApproximationFromCurrentToNext = current.getSpeedAndBearingRequiredToReach(next); - // compute course change on "approximation track" - CourseChange courseChange = speedWithBearingOnApproximationFromPreviousToCurrent - .getCourseChangeRequiredToReach(speedWithBearingOnApproximationFromCurrentToNext); - Pair courseChangeAtFix = new Pair(current, - courseChange); - if (!courseChangeSequenceInSameDirection.isEmpty() - && Math.signum(courseChangeSequenceInSameDirection.get(0).getB().getCourseChangeInDegrees()) != Math - .signum(courseChange.getCourseChangeInDegrees())) { - // course change in different direction; cluster the course changes in same direction so far, then - // start new list - List maneuvers = groupChangesInSameDirectionIntoManeuvers(competitor, - speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges, - courseChangeSequenceInSameDirection); - result.addAll(maneuvers); - courseChangeSequenceInSameDirection.clear(); - speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges = speedWithBearingOnApproximationFromPreviousToCurrent; - } - courseChangeSequenceInSameDirection.add(courseChangeAtFix); - previous = current; - current = next; - speedWithBearingOnApproximationFromPreviousToCurrent = speedWithBearingOnApproximationFromCurrentToNext; - } while (approximationPointsIter.hasNext()); - if (!courseChangeSequenceInSameDirection.isEmpty()) { - result.addAll(groupChangesInSameDirectionIntoManeuvers(competitor, - speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges, - courseChangeSequenceInSameDirection)); - } - } - return result; - } - - /** - * Fetches results from {@link #maneuverCache}. The cache is updated asynchronously after relevant updates have been - * received (see {@link #triggerManeuverCacheRecalculation(Competitor)} and - * {@link #triggerManeuverCacheRecalculationForAllCompetitors()}). Callers can choose whether to wait for any - * ongoing updates by using the waitForLatest parameter. From the cache the interval requested is then - * {@link #extractInterval(TimePoint, TimePoint, List) extracted}. - * - * @param waitForLatest - * if true, any currently ongoing maneuver recalculation for competitor is - * waited for before returning the result; otherwise, whatever is in the {@link #maneuverCache} for - * competitor, reduced to the interval requested, will be returned. - */ - @Override - public List getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest) - throws NoWindException { - Triple> allManeuvers = maneuverCache.get(competitor, waitForLatest); - List result; - if (allManeuvers == null) { - result = Collections.emptyList(); - } else { - result = extractInterval(from, to, allManeuvers.getC()); - } - return result; - } - - private List extractInterval(TimePoint from, TimePoint to, List listOfTimed) { - List result; - result = new LinkedList(); - for (T timed : listOfTimed) { - if (timed.getTimePoint().compareTo(from) >= 0 && timed.getTimePoint().compareTo(to) <= 0) { - result.add(timed); - } - } - return result; - } - - /** - * Groups the {@link CourseChange} sequence into groups where the times of the fixes at which the course changes - * took place are no further apart than {@link #getApproximateManeuverDurationInMilliseconds()} milliseconds or - * where the distances of those course changes are less than two hull lengths apart. For those, a single - * {@link Maneuver} object is created and added to the resulting list. The maneuver sums up the direction changes of - * the individual {@link CourseChange} objects. This can result in direction changes of more than 180 degrees in one - * direction which may, e.g., represent a penalty circle or a mark rounding maneuver. As the maneuver's time point, - * the average time point of the course changes that went into the maneuver construction is used. - *

- * - * @param speedWithBearingOnApproximationAtBeginning - * the speed/bearing before the first approximating fix passed in - * courseChangeSequenceInSameDirection - * @param courseChangeSequenceInSameDirection - * all expected to have equal {@link CourseChange#to()} values - * - * @return a non-null list - */ - private List groupChangesInSameDirectionIntoManeuvers(Competitor competitor, - SpeedWithBearing speedWithBearingOnApproximationAtBeginning, - List> courseChangeSequenceInSameDirection) throws NoWindException { - List result = new ArrayList(); - List> group = new ArrayList>(); - if (!courseChangeSequenceInSameDirection.isEmpty()) { - Distance threeHullLengths = competitor.getBoat().getBoatClass().getHullLength().scale(3); - SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing - // before group - SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing - // before current - // course change - Iterator> iter = courseChangeSequenceInSameDirection.iterator(); - double totalCourseChangeInDegrees = 0.0; - long totalMilliseconds = 0l; - SpeedWithBearing afterCurrentCourseChange = null; // sure to be set because iter's collection is not empty - // and the first use requires group not to be empty which can only happen after the first group.add - do { - Pair currentFixAndCourseChange = iter.next(); - if (!group.isEmpty() - // TODO use different maneuver times for upwind / reaching / downwind / cross-leg (mark passing) - // group contains complete maneuver if the next fix is too late or too far away to belong to the - // same maneuver - // FIXME penalty circles slow down the boat so much that time limit may get exceeded although - // distance limit is matched - && currentFixAndCourseChange.getA().getTimePoint().asMillis() - - group.get(group.size() - 1).getA().getTimePoint().asMillis() > getApproximateManeuverDurationInMilliseconds() - && currentFixAndCourseChange.getA().getPosition() - .getDistance(group.get(group.size() - 1).getA().getPosition()) - .compareTo(threeHullLengths) > 0) { - // if next is more then approximate maneuver duration later or further apart than two hull lengths, - // turn the current group into a maneuver and add to result - Util.addAll(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation, - group, afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds), result); - group.clear(); - totalCourseChangeInDegrees = 0.0; - totalMilliseconds = 0l; - beforeGroupOnApproximation = beforeCurrentCourseChangeOnApproximation; - } - afterCurrentCourseChange = beforeCurrentCourseChangeOnApproximation - .applyCourseChange(currentFixAndCourseChange.getB()); - totalMilliseconds += currentFixAndCourseChange.getA().getTimePoint().asMillis(); - totalCourseChangeInDegrees += currentFixAndCourseChange.getB().getCourseChangeInDegrees(); - group.add(currentFixAndCourseChange); - beforeCurrentCourseChangeOnApproximation = afterCurrentCourseChange; // speed/bearing after course - // change - } while (iter.hasNext()); - if (!group.isEmpty()) { - Util.addAll(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation, group, - afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds), result); - } - } - return result; - } - - private Iterable createManeuverFromGroupOfCourseChanges(Competitor competitor, - SpeedWithBearing speedWithBearingOnApproximationAtBeginning, List> group, - SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees, - long totalMilliseconds) throws NoWindException { - List result = new ArrayList<>(); - MillisecondsTimePoint timePointBeforeManeuver = new MillisecondsTimePoint(group.get(0).getA().getTimePoint() - .asMillis() - - getApproximateManeuverDurationInMilliseconds() / 2); - MillisecondsTimePoint timePointAfterManeuver = new MillisecondsTimePoint(group.get(group.size() - 1).getA() - .getTimePoint().asMillis() - + getApproximateManeuverDurationInMilliseconds() / 2); - TimePoint maneuverTimePoint = computeManeuverTimepoint(competitor, timePointBeforeManeuver, - timePointAfterManeuver); - final GPSFixTrack competitorTrack = getTrack(competitor); - Position maneuverPosition = competitorTrack - .getEstimatedPosition(maneuverTimePoint, /* extrapolate */false); - Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver, - speedWithBearingOnApproximationAtEnd.getBearing()); - ManeuverType maneuverType; - Distance maneuverLoss = null; - // the TrackedLegOfCompetitor variables may be null, e.g., in case the time points are before or after the race - TrackedLegOfCompetitor legBeforeManeuver = getTrackedLeg(competitor, timePointBeforeManeuver); - TrackedLegOfCompetitor legAfterManeuver = getTrackedLeg(competitor, timePointAfterManeuver); - Waypoint waypointPassed = null; // set for MARK_PASSING maneuvers only - NauticalSide sideToWhichWaypointWasPassed = null; // set for MARK_PASSING maneuvers only - final NauticalSide courseChangedTo = totalCourseChangeInDegrees < 0 ? NauticalSide.PORT : NauticalSide.STARBOARD; - // check for mask passing first; a tacking / jibe-setting mark rounding thus takes precedence over being - // detected as a penalty circle - if (legBeforeManeuver != legAfterManeuver - // a maneuver at the start line is not to be considered a MARK_PASSING maneuver; show a tack as a tack - && legAfterManeuver != null - && legAfterManeuver.getLeg().getFrom() != getRace().getCourse().getFirstWaypoint()) { - maneuverType = ManeuverType.MARK_PASSING; - waypointPassed = legAfterManeuver.getLeg().getFrom(); - sideToWhichWaypointWasPassed = courseChangedTo; - // produce an additional mark passing maneuver; continue to analyze to catch jibe sets and kiwi drops - result.add(new MarkPassingManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition, - maneuverTimePoint, speedWithBearingOnApproximationAtBeginning, - speedWithBearingOnApproximationAtEnd, totalCourseChangeInDegrees, maneuverLoss, waypointPassed, - sideToWhichWaypointWasPassed)); - } - final Wind wind = getWind(maneuverPosition, maneuverTimePoint); - final SpeedWithBearing estimatedSpeedBeforeManeuver = competitorTrack.getEstimatedSpeed(timePointBeforeManeuver); - final SpeedWithBearing estimatedSpeedAfterManeuver = competitorTrack.getEstimatedSpeed(timePointAfterManeuver); - if (wind != null && estimatedSpeedBeforeManeuver != null && estimatedSpeedAfterManeuver != null) { - BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE; - final Bearing courseBeforeManeuver = estimatedSpeedBeforeManeuver.getBearing(); - final Bearing courseAfterManeuver = estimatedSpeedAfterManeuver.getBearing(); - boolean jibed = bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees, - courseAfterManeuver, wind.getBearing()); - boolean tacked = bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees, - courseAfterManeuver, wind.getFrom()); - if (tacked && jibed) { - maneuverType = ManeuverType.PENALTY_CIRCLE; - if (legBeforeManeuver != null) { - maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, - timePointAfterManeuver); - } - } else if (tacked) { - maneuverType = ManeuverType.TACK; - if (legBeforeManeuver != null) { - maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, - timePointAfterManeuver); - } - } else if (jibed) { - maneuverType = ManeuverType.JIBE; - if (legBeforeManeuver != null) { - maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, - timePointAfterManeuver); - } - } else { - // heading up or bearing away - Bearing windBearing = wind.getBearing(); - Bearing toWindBeforeManeuver = windBearing.getDifferenceTo(speedWithBearingOnApproximationAtBeginning.getBearing()); - Bearing toWindAfterManeuver = windBearing.getDifferenceTo(speedWithBearingOnApproximationAtEnd.getBearing()); - maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver.getDegrees()) ? ManeuverType.HEAD_UP - : ManeuverType.BEAR_AWAY; - } - final Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition, - maneuverTimePoint, speedWithBearingOnApproximationAtBeginning, - speedWithBearingOnApproximationAtEnd, totalCourseChangeInDegrees, maneuverLoss); - result.add(maneuver); - } - return result; - } - - /** - * Computes the maneuver time point as the time point along between maneuver start and end where the competitor's - * track has greatest change in course. - */ - private TimePoint computeManeuverTimepoint(Competitor competitor, MillisecondsTimePoint timePointBeforeManeuver, - MillisecondsTimePoint timePointAfterManeuver) { - TimePoint result = timePointBeforeManeuver; - GPSFixTrack track = getTrack(competitor); - GPSFixMoving lastFix = null; - double maxAngleSpeedInDegreesPerSecond = 0; - track.lockForRead(); - try { - for (Iterator i = track.getFixesIterator(timePointBeforeManeuver, /* inclusive */true); i - .hasNext();) { - GPSFixMoving fix = i.next(); - if (fix.getTimePoint().after(timePointAfterManeuver)) { - break; - } - if (lastFix != null) { - final SpeedWithBearing lastEstimatedSpeed = track.getEstimatedSpeed(lastFix.getTimePoint()); - if (lastEstimatedSpeed != null) { - Bearing courseAtLastFix = lastEstimatedSpeed.getBearing(); - final SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(fix.getTimePoint()); - if (estimatedSpeed != null) { - Bearing courseAtFix = estimatedSpeed.getBearing(); - double angleSpeedInDegreesPerSecond = Math.abs((courseAtFix - .getDifferenceTo(courseAtLastFix).getDegrees()) - / (double) (fix.getTimePoint().asMillis() - lastFix.getTimePoint().asMillis())); - if (angleSpeedInDegreesPerSecond > maxAngleSpeedInDegreesPerSecond) { - maxAngleSpeedInDegreesPerSecond = angleSpeedInDegreesPerSecond; - result = lastFix.getTimePoint(); - } - } - } - } - lastFix = fix; - } - } finally { - track.unlockAfterRead(); - } - return result; - } - - /** - * Fetches the boat class-specific parameter - */ - private double getManeuverDegreeAngleThreshold() { - return getRace().getBoatClass().getManeuverDegreeAngleThreshold(); - } - - private double getMinimumAngleBetweenDifferentTacksDownwind() { - return getRace().getBoatClass().getMinimumAngleBetweenDifferentTacksDownwind(); - } - - private double getMinimumAngleBetweenDifferentTacksUpwind() { - return getRace().getBoatClass().getMinimumAngleBetweenDifferentTacksUpwind(); - } - - private long getApproximateManeuverDurationInMilliseconds() { - return getRace().getBoatClass().getApproximateManeuverDurationInMilliseconds(); - } - - private class StartToNextMarkCacheInvalidationListener implements GPSTrackListener { - private static final long serialVersionUID = 3540278554797445085L; - private final GPSFixTrack listeningTo; - - public StartToNextMarkCacheInvalidationListener(GPSFixTrack listeningTo) { - this.listeningTo = listeningTo; - } - - public void stopListening() { - listeningTo.removeListener(this); - } - - @Override - public void gpsFixReceived(GPSFix fix, Mark mark) { - clearDirectionFromStartToNextMarkCache(); - } - - @Override - public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { - } - - @Override - public boolean isTransient() { - return false; - } - } - - @Override - public Distance getWindwardDistanceToOverallLeader(Competitor competitor, TimePoint timePoint) - throws NoWindException { - final TrackedLegOfCompetitor trackedLeg = getTrackedLeg(competitor, timePoint); - return trackedLeg == null ? null : trackedLeg.getWindwardDistanceToOverallLeader(timePoint); - } - - @Override - public Iterable getMarks() { - while (true) { - try { - return new HashSet(markTracks.keySet()); - } catch (ConcurrentModificationException cme) { - logger.info("Caught " + cme + "; trying again."); - } - } - } - - @Override - public Iterable getCourseSidelines() { - return new ArrayList(courseSidelines.values()); - } - - @Override - public long getDelayToLiveInMillis() { - return delayToLiveInMillis; - } - - protected void setDelayToLiveInMillis(long delayToLiveInMillis) { - this.delayToLiveInMillis = delayToLiveInMillis; - } - - @Override - public TrackedRaceStatus getStatus() { - return status; - } - - /** - * Changes to the {@link #status} variable are synchronized on the {@link #statusNotifier} field. - * - * @return - */ - protected Object getStatusNotifier() { - return statusNotifier; - } - - protected void setStatus(TrackedRaceStatus newStatus) { - assert newStatus != null; - final TrackedRaceStatusEnum oldStatus; - synchronized (getStatusNotifier()) { - oldStatus = getStatus().getStatus(); - this.status = newStatus; - getStatusNotifier().notifyAll(); - } - if (newStatus.getStatus() == TrackedRaceStatusEnum.LOADING && oldStatus != TrackedRaceStatusEnum.LOADING) { - suspendAllCachesNotUpdatingWhileLoading(); - } else if (oldStatus == TrackedRaceStatusEnum.LOADING && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) { - resumeAllCachesNotUpdatingWhileLoading(); - } - - } - - private void suspendAllCachesNotUpdatingWhileLoading() { - crossTrackErrorCache.suspend(); - maneuverCache.suspend(); - } - - private void resumeAllCachesNotUpdatingWhileLoading() { - crossTrackErrorCache.resume(); - maneuverCache.resume(); - } - - /** - * Waits on the current ("old") status object which is notified in {@link #setStatus(TrackedRaceStatus)} when the - * status is changed. The change as well as the check synchronize on the old status object. - */ - @Override - public void waitUntilNotLoading() { - synchronized (getStatusNotifier()) { - while (getStatus().getStatus() == TrackedRaceStatusEnum.LOADING) { - try { - getStatusNotifier().wait(); - } catch (InterruptedException e) { - logger.info("waitUntilNotLoading on tracked race " + this + " interrupted: " + e.getMessage() - + ". Continuing to wait."); - } - } - } - } - - @Override - public void attachRaceLog(RaceLog raceLog) { - if (raceLog != null) { - this.attachedRaceLogs.put(raceLog.getId(), raceLog); - } else { - logger.severe("Got a request to attach race log for an empty race log!"); - } - } - - @Override - public void detachRaceLog(Serializable identifier) { - this.attachedRaceLogs.remove(identifier); - } - - @Override - public void detachAllRaceLogs() { - this.attachedRaceLogs.clear(); - } - - @Override - public RaceLog getRaceLog(Serializable identifier) { - return attachedRaceLogs.get(identifier); - } - - @Override - public Distance getDistanceToStartLine(Competitor competitor, long millisecondsBeforeRaceStart) { - final Distance result; - if (getStartOfRace() == null) { - result = null; - } else { - TimePoint beforeStart = new MillisecondsTimePoint(getStartOfRace().asMillis() - millisecondsBeforeRaceStart); - result = getDistanceToStartLine(competitor, beforeStart); - } - return result; - } - - @Override - public Distance getDistanceToStartLine(Competitor competitor, TimePoint timePoint) { - Waypoint startWaypoint = getRace().getCourse().getFirstWaypoint(); - final Distance result; - if (startWaypoint == null) { - result = null; - } else { - Position competitorPosition = getTrack(competitor).getEstimatedPosition(timePoint, /* extrapolate */false); - if (competitorPosition == null) { - result = null; - } else { - Iterable marks = startWaypoint.getControlPoint().getMarks(); - Iterator marksIterator = marks.iterator(); - Mark first = marksIterator.next(); - Position firstPosition = getOrCreateTrack(first).getEstimatedPosition(timePoint, /* extrapolate */false); - if (firstPosition == null) { - result = null; - } else { - if (marksIterator.hasNext()) { - // it's a line / gate - Mark second = marksIterator.next(); - Position secondPosition = getOrCreateTrack(second).getEstimatedPosition(timePoint, /* extrapolate */ - false); - final Bearing bearingGreatCircle = firstPosition.getBearingGreatCircle(secondPosition); - if (bearingGreatCircle == null) { - result = null; - } else { - Position competitorProjectedOntoStartLine = competitorPosition.projectToLineThrough( - firstPosition, bearingGreatCircle); - result = competitorPosition.getDistance(competitorProjectedOntoStartLine); - } - } else { - result = competitorPosition.getDistance(firstPosition); - } - } - } - } - return result; - } - - @Override - public Speed getSpeed(Competitor competitor, long millisecondsBeforeRaceStart) { - if (getStartOfRace() == null) { - return null; - } - - TimePoint beforeStart = new MillisecondsTimePoint(getStartOfRace().asMillis() - millisecondsBeforeRaceStart); - return getTrack(competitor).getEstimatedSpeed(beforeStart); - } - - @Override - public Distance getDistanceFromStarboardSideOfStartLineWhenPassingStart(Competitor competitor) { - final Distance result; - TrackedLegOfCompetitor firstTrackedLegOfCompetitor = getTrackedLeg(competitor, getRace().getCourse().getFirstLeg()); - TimePoint competitorStartTime = firstTrackedLegOfCompetitor.getStartTime(); - if (competitorStartTime != null) { - Position competitorPositionWhenPassingStart = getTrack(competitor).getEstimatedPosition( - competitorStartTime, /* extrapolate */false); - final Position starboardMarkPosition = getStarboardMarkOfStartlinePosition(competitorStartTime); - if (competitorPositionWhenPassingStart != null && starboardMarkPosition != null) { - result = starboardMarkPosition == null ? null : competitorPositionWhenPassingStart.getDistance(starboardMarkPosition); - } else { - result = null; - } - } else { - result = null; - } - return result; - } - - @Override - public Distance getDistanceFromStarboardSideOfStartLine(Competitor competitor, TimePoint timePoint) { - final Distance result; - if (timePoint != null) { - Position competitorPositionWhenPassingStart = getTrack(competitor).getEstimatedPosition( - timePoint, /* extrapolate */false); - final Position starboardMarkPosition = getStarboardMarkOfStartlinePosition(timePoint); - if (competitorPositionWhenPassingStart != null && starboardMarkPosition != null) { - result = starboardMarkPosition == null ? null : competitorPositionWhenPassingStart.getDistance(starboardMarkPosition); - } else { - result = null; - } - } else { - result = null; - } - return result; - } - - /** - * Based on the bearing from the start waypoint to the next mark, identifies which of the two marks of the start - * line is on starboard. If the start waypoint has only one mark, that mark is returned. If the start line has two - * marks but the course has no other waypoint, - * null is returned. If the course has no waypoints at all, null is returned.

- * - * The method has protected visibility largely for testing purposes. - */ - protected Mark getStarboardMarkOfStartlineOrSingleStartMark(TimePoint at) { - LineDetails startLine = getStartLine(at); - final Mark result; - if (startLine != null) { - result = startLine.getStarboardMarkWhileApproachingLine(); - } else { - Waypoint startWaypoint = getRace().getCourse().getFirstWaypoint(); - if (startWaypoint != null && startWaypoint.getMarks().iterator().hasNext()) { - result = startWaypoint.getMarks().iterator().next(); - } else { - result = null; - } - } - return result; - } - - /** - * Based on the bearing from the start waypoint to the - * next mark, identifies which of the two marks of the start line is on starboard and returns its position. If the start waypoint has only - * one mark, that mark is returned. If the start line has two marks but the course has no other waypoint, - * null is returned. If the course has no waypoints at all, null is returned. - */ - private Position getStarboardMarkOfStartlinePosition(TimePoint at) { - Mark starboardMark = getStarboardMarkOfStartlineOrSingleStartMark(at); - if (starboardMark != null) { - return getOrCreateTrack(starboardMark).getEstimatedPosition(at, /*extrapolate*/ false); - } - return null; - } - - protected NamedReentrantReadWriteLock getWindLoadingLock() { - return windLoadingLock; - } - - /** - * If the waypoint is not a line, or no position can be determined for one of its marks at timePoint, - * null is returned. If no wind information is available but required to compute the advantage, null values - * are returned in those fields that depend on wind data. If the waypoint is null - * or is the only waypoint, null is returned because no reasonable statement can be - * made about the direction from which the line is to be passed. - */ - private LineDetails getLineLengthAndAdvantage(TimePoint timePoint, Waypoint waypoint) { - List markPositions = new ArrayList(); - int numberOfMarks = 0; - boolean allMarksHavePositions = true; - LineDetails result = null; - if (waypoint != null) { - for (Mark lineMark : waypoint.getMarks()) { - numberOfMarks++; - final Position estimatedMarkPosition = getOrCreateTrack(lineMark).getEstimatedPosition(timePoint, /* extrapolate */ - false); - if (estimatedMarkPosition != null) { - markPositions.add(estimatedMarkPosition); - } else { - allMarksHavePositions = false; - } - } - final List legs = getRace().getCourse().getLegs(); - // need at least one leg to make sense of a line - if (!legs.isEmpty()) { - try { - if (allMarksHavePositions && numberOfMarks == 2) { - final int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(waypoint); - final boolean isStartLine = indexOfWaypoint == 0; - final TrackedLeg legDeterminingDirection = getTrackedLeg(legs.get(isStartLine ? 0 - : indexOfWaypoint - 1)); - Distance crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint = markPositions.get(0) - .crossTrackError(markPositions.get(1), legDeterminingDirection.getLegBearing(timePoint)); - final Mark starboardMarkWhileApproachingLine; - final Mark portMarkWhileApproachingLine; - final Position starboardMarkPositionWhileApproachingLine; - final Position portMarkPositionWhileApproachingLine; - if (crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint.getMeters() < 0) { - portMarkWhileApproachingLine = Util.get(waypoint.getMarks(), 0); - starboardMarkWhileApproachingLine = Util.get(waypoint.getMarks(), 1); - } else { - portMarkWhileApproachingLine = Util.get(waypoint.getMarks(), 1); - starboardMarkWhileApproachingLine = Util.get(waypoint.getMarks(), 0); - } - portMarkPositionWhileApproachingLine = getOrCreateTrack(portMarkWhileApproachingLine) - .getEstimatedPosition(timePoint, /* extrapolate */false); - starboardMarkPositionWhileApproachingLine = getOrCreateTrack(starboardMarkWhileApproachingLine) - .getEstimatedPosition(timePoint, /* extrapolate */false); - final Bearing differenceToCombinedWind; - final NauticalSide advantageousSideWhileApproachingLine; - final Distance distanceAdvantage; - Wind combinedWind = getWind(markPositions.get(0), timePoint); - if (combinedWind != null) { - differenceToCombinedWind = portMarkPositionWhileApproachingLine.getBearingGreatCircle( - starboardMarkPositionWhileApproachingLine).getDifferenceTo(combinedWind.getFrom()); - Distance windwardDistanceFromFirstToSecondMark; - windwardDistanceFromFirstToSecondMark = legDeterminingDirection.getWindwardDistance( - markPositions.get(0), markPositions.get(1), timePoint); - final Position worseMarkPosition; - final Position betterMarkPosition; - if ((isStartLine && windwardDistanceFromFirstToSecondMark.getMeters() > 0) - || (!isStartLine && windwardDistanceFromFirstToSecondMark.getMeters() < 0)) { - // first mark is worse than second mark - worseMarkPosition = markPositions.get(0); - betterMarkPosition = markPositions.get(1); - } else { - // second mark is worse than first mark - worseMarkPosition = markPositions.get(1); - betterMarkPosition = markPositions.get(0); - } - if (windwardDistanceFromFirstToSecondMark.getMeters() >= 0) { - distanceAdvantage = windwardDistanceFromFirstToSecondMark; - } else { - distanceAdvantage = new CentralAngleDistance( - -windwardDistanceFromFirstToSecondMark.getCentralAngleRad()); - } - if (betterMarkPosition.crossTrackError(worseMarkPosition, - legDeterminingDirection.getLegBearing(timePoint)).getCentralAngleRad() > 0) { - advantageousSideWhileApproachingLine = NauticalSide.STARBOARD; - } else { - advantageousSideWhileApproachingLine = NauticalSide.PORT; - } - } else { - differenceToCombinedWind = null; - advantageousSideWhileApproachingLine = null; - distanceAdvantage = null; - } - result = new LineDetailsImpl(timePoint, waypoint, - portMarkPositionWhileApproachingLine - .getDistance(starboardMarkPositionWhileApproachingLine), - differenceToCombinedWind, advantageousSideWhileApproachingLine, distanceAdvantage, - portMarkWhileApproachingLine, starboardMarkWhileApproachingLine); - } - } catch (NoWindException e) { - // result remains null; - } - } - } - return result; - } - - - @Override - public LineDetails getStartLine(TimePoint at) { - return getLineLengthAndAdvantage(at, getRace().getCourse().getFirstWaypoint()); - } - - @Override - public LineDetails getFinishLine(TimePoint at) { - return getLineLengthAndAdvantage(at, getRace().getCourse().getLastWaypoint()); - } - - @Override - public SpeedWithConfidence getAverageWindSpeedWithConfidence(long resolutionInMillis) { - SpeedWithConfidence result = null; - if (getEndOfRace() != null) { - TimePoint fromTimePoint = getStartOfRace(); - TimePoint toTimePoint = getEndOfRace(); - - List windSourcesToDeliver = new ArrayList(); - WindSourceImpl windSource = new WindSourceImpl(WindSourceType.COMBINED); - windSourcesToDeliver.add(windSource); - - double sumWindSpeed = 0.0; - double sumWindSpeedConfidence = 0.0; - int speedCounter = 0; - - int numberOfFixes = (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis()) / resolutionInMillis); - WindTrack windTrack = getOrCreateWindTrack(windSource); - TimePoint timePoint = fromTimePoint; - for (int i = 0; i < numberOfFixes && toTimePoint != null && timePoint.compareTo(toTimePoint) < 0; i++) { - WindWithConfidence> averagedWindWithConfidence = windTrack - .getAveragedWindWithConfidence(null, timePoint); - if (averagedWindWithConfidence != null) { - double windSpeedinKnots = averagedWindWithConfidence.getObject().getKnots(); - double confidence = averagedWindWithConfidence.getConfidence(); - - sumWindSpeed += windSpeedinKnots; - sumWindSpeedConfidence += confidence; - - speedCounter++; - } - timePoint = new MillisecondsTimePoint(timePoint.asMillis() + resolutionInMillis); - } - if (speedCounter > 0) { - Speed averageWindSpeed = new KnotSpeedImpl(sumWindSpeed / speedCounter); - double averageWindSpeedConfidence = sumWindSpeedConfidence / speedCounter; - result = new SpeedWithConfidenceImpl(averageWindSpeed, averageWindSpeedConfidence, toTimePoint); - } - } - return result; - } - - @Override - public Distance getCourseLength() { - List legs = getRace().getCourse().getLegs(); - Distance raceDistance = new NauticalMileDistance(0); - for (Leg leg : legs) { - Waypoint from = leg.getFrom(); - Iterable markPassings = getMarkPassingsInOrder(from); - Iterator markPassingsIterator = markPassings.iterator(); - if (!markPassingsIterator.hasNext()) { - return null; - } - MarkPassing firstPassing = markPassingsIterator.next(); - TimePoint timePointOfFirstPassing = firstPassing.getTimePoint(); - Waypoint to = leg.getTo(); - Position fromPos = getApproximatePosition(from, timePointOfFirstPassing); - Position toPos = getApproximatePosition(to, timePointOfFirstPassing); - Distance legDistance = fromPos.getDistance(toPos); - raceDistance = raceDistance.add(legDistance); - } - return raceDistance; - } - - @Override - public Speed getSpeedWhenCrossingStartLine(Competitor competitor) { - NavigableSet competitorMarkPassings = getMarkPassings(competitor); - Speed competitorSpeedWhenPassingStart = null; - lockForRead(competitorMarkPassings); - try { - if (!competitorMarkPassings.isEmpty()) { - TimePoint competitorStartTime = competitorMarkPassings.first().getTimePoint(); - competitorSpeedWhenPassingStart = getTrack(competitor).getEstimatedSpeed( - competitorStartTime); - } - } finally { - unlockAfterRead(competitorMarkPassings); - } - return competitorSpeedWhenPassingStart; - } -} +package com.sap.sailing.domain.tracking.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.BearingWithConfidence; +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.CourseListener; +import com.sap.sailing.domain.base.Leg; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.base.SpeedWithConfidence; +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; +import com.sap.sailing.domain.base.impl.DouglasPeucker; +import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.BearingChangeAnalyzer; +import com.sap.sailing.domain.common.CourseChange; +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.NauticalSide; +import com.sap.sailing.domain.common.NoWindError; +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.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimingConstants; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.impl.CentralAngleDistance; +import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.NauticalMileDistance; +import com.sap.sailing.domain.common.impl.WindSourceImpl; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; +import com.sap.sailing.domain.confidence.ConfidenceFactory; +import com.sap.sailing.domain.confidence.HasConfidence; +import com.sap.sailing.domain.confidence.Weigher; +import com.sap.sailing.domain.confidence.impl.HyperbolicTimeDifferenceWeigher; +import com.sap.sailing.domain.confidence.impl.PositionAndTimePointWeigher; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.analyzing.impl.StartTimeFinder; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.GPSTrackListener; +import com.sap.sailing.domain.tracking.LineDetails; +import com.sap.sailing.domain.tracking.Maneuver; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.Track; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; +import com.sap.sailing.domain.tracking.TrackedRaceWithWindEssentials; +import com.sap.sailing.domain.tracking.TrackedRegatta; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.util.SmartFutureCache; +import com.sap.sailing.util.SmartFutureCache.AbstractCacheUpdater; +import com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval; +import com.sap.sailing.util.impl.ArrayListNavigableSet; +import com.sap.sailing.util.impl.LockUtil; +import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sse.common.Util; + +public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials implements CourseListener { + private static final long serialVersionUID = -4825546964220003507L; + + private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName()); + + // TODO make this variable + private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500; + + public static final long TIME_BEFORE_START_TO_TRACK_WIND_MILLIS = 4 * 60 * 1000l; // let wind start four minutes before race + + private TrackedRaceStatus status; + + private final Object statusNotifier; + + /** + * By default, all wind sources are used, none are excluded. However, e.g., for performance reasons, particular wind + * sources such as the track-based estimation wind source, may be excluded by adding them to this set. + */ + private final Set windSourcesToExclude; + + /** + * Keeps the oldest timestamp that is fed into this tracked race, either from a boat fix, a mark fix, a race + * start/finish or a course definition. + */ + private TimePoint timePointOfOldestEvent; + + /** + * The start of tracking time as announced by the tracking infrastructure. + */ + private TimePoint startOfTrackingReceived; + + /** + * The end of tracking time as announced by the tracking infrastructure. + */ + private TimePoint endOfTrackingReceived; + + /** + * Race start time as announced by the tracking infrastructure + */ + private TimePoint startTimeReceived; + + /** + * The calculated race start time + */ + private TimePoint startTime; + + /** + * The calculated race end time + */ + private TimePoint endTime; + + /** + * The first and last passing times of all course waypoints + */ + private transient List>> markPassingsTimes; + + /** + * The latest time point contained by any of the events received and processed + */ + private TimePoint timePointOfNewestEvent; + + /** + * Time stamp that the event received last from the underlying push service carried on it + */ + private TimePoint timePointOfLastEvent; + + private long updateCount; + + private transient Map> competitorRankings; + + /** + * The locks managed here correspond with the {@link #competitorRankings} structure. When + * {@link #getCompetitorsFromBestToWorst(TimePoint)} starts to compute rankings, it locks the write lock for the + * time point. Readers use the read lock. Checking / entering a lock into this map uses synchronized on + * the map itself. + */ + private transient Map competitorRankingsLocks; + + /** + * legs appear in the order in which they appear in the race's course + */ + private final LinkedHashMap trackedLegs; + + private final Map> tracks; + + private final Map> markPassingsForCompetitor; + + /** + * The mark passing sets used as values are ordered by time stamp. + */ + private final Map> markPassingsForWaypoint; + + /** + * Values are the from and to time points between which the maneuvers have been previously + * computed. Clients wanting to know maneuvers for the competitor outside of this time interval need to (re-)compute + * them. + */ + private transient SmartFutureCache>, EmptyUpdateInterval> maneuverCache; + + private transient Map> directionFromStartToNextMarkCache; + + private final ConcurrentHashMap> markTracks; + + private final Map courseSidelines; + + protected long millisecondsOverWhichToAverageSpeed; + + private final Map startToNextMarkCacheInvalidationListeners; + + private transient Timer cacheInvalidationTimer; + private transient Object cacheInvalidationTimerLock; + + protected transient HashMap attachedRaceLogs; + + /** + * The time delay to the current point in time in milliseconds. + */ + private long delayToLiveInMillis; + + private enum LoadingFromStoresState { NOT_STARTED, RUNNING, FINISHED }; + + /** + * The constructor loads wind fixes from the {@link #windStore} asynchronously. + * When completed all threads currently waiting on this object are notified. + */ + private LoadingFromStoresState loadingFromWindStoreState = LoadingFromStoresState.NOT_STARTED; + + /** + * @see #loadingFromWindStoreState but for GPSFixStore + */ + private LoadingFromStoresState loadingFromGPSFixStoreState = LoadingFromStoresState.NOT_STARTED; + private Object loadingFromGPSFixStoreStateMonitor = ""; // String, not Object, to keep it serializable + + private transient CrossTrackErrorCache crossTrackErrorCache; + + private final GPSFixStore gpsFixStore; + + /** + * Wind and loading is started in a background thread during object construction. If a client needs to + * ensure that wind loading either has terminated or has not yet begun, it can obtain the read lock of + * this lock. The wind loading procedure will obtain the write lock before it starts loading wind fixes. + */ + private final NamedReentrantReadWriteLock loadingFromWindStoreLock; + + /** + * @see #loadingFromWindStoreLock but for GPSFixStore + */ + private final NamedReentrantReadWriteLock loadingFromGPSFixStoreLock; + + private final Map, NamedReentrantReadWriteLock> locksForMarkPassings; + + public TrackedRaceImpl(final TrackedRegatta trackedRegatta, RaceDefinition race, final Iterable sidelines, final WindStore windStore, final GPSFixStore gpsFixStore, + long delayToLiveInMillis, final long millisecondsOverWhichToAverageWind, + long millisecondsOverWhichToAverageSpeed, long delayForWindEstimationCacheInvalidation) { + super(race, trackedRegatta, windStore, millisecondsOverWhichToAverageWind); + locksForMarkPassings = new IdentityHashMap<>(); + attachedRaceLogs = new HashMap<>(); + this.status = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.PREPARED, 0.0); + this.statusNotifier = new Object[0]; + this.loadingFromWindStoreLock = new NamedReentrantReadWriteLock("Loading from wind store lock for tracked race " + + race.getName(), /* fair */ false); + this.loadingFromGPSFixStoreLock = new NamedReentrantReadWriteLock("Loading from GPSFix store lock for tracked race " + + race.getName(), /* fair */ false); + this.cacheInvalidationTimerLock = new Object(); + this.updateCount = 0; + this.windSourcesToExclude = new HashSet(); + this.directionFromStartToNextMarkCache = new HashMap>(); + this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed; + this.delayToLiveInMillis = delayToLiveInMillis; + this.startToNextMarkCacheInvalidationListeners = new ConcurrentHashMap(); + this.maneuverCache = createManeuverCache(); + this.markTracks = new ConcurrentHashMap>(); + this.crossTrackErrorCache = new CrossTrackErrorCache(this); + this.gpsFixStore = gpsFixStore; + int i = 0; + for (Waypoint waypoint : race.getCourse().getWaypoints()) { + for (Mark mark : waypoint.getMarks()) { + getOrCreateTrack(mark); + if (i < 2) { + // add cache invalidation listeners for first and second waypoint's marks for + // directionFromStartToNextMarkCache + addStartToNextMarkCacheInvalidationListener(mark); + } + } + i++; + } + courseSidelines = new LinkedHashMap(); + for (Sideline sideline : sidelines) { + courseSidelines.put(sideline.getName(), sideline); + for (Mark mark : sideline.getMarks()) { + getOrCreateTrack(mark); + } + } + + trackedLegs = new LinkedHashMap(); + race.getCourse().lockForRead(); + try { + for (Leg leg : race.getCourse().getLegs()) { + trackedLegs.put(leg, createTrackedLeg(leg)); + } + getRace().getCourse().addCourseListener(this); + } finally { + race.getCourse().unlockAfterRead(); + } + markPassingsForCompetitor = new HashMap>(); + tracks = new HashMap>(); + for (Competitor competitor : race.getCompetitors()) { + markPassingsForCompetitor.put(competitor, new ConcurrentSkipListSet( + MarkPassingByTimeComparator.INSTANCE)); + tracks.put(competitor, new DynamicGPSFixMovingTrackImpl(competitor, + millisecondsOverWhichToAverageSpeed)); + } + markPassingsForWaypoint = new ConcurrentHashMap>(); + for (Waypoint waypoint : race.getCourse().getWaypoints()) { + markPassingsForWaypoint.put(waypoint, new ConcurrentSkipListSet( + MarkPassingByTimeComparator.INSTANCE)); + } + markPassingsTimes = new ArrayList>>(); + loadingFromWindStoreState = LoadingFromStoresState.NOT_STARTED; + // When this tracked race is to be serialized, wait for the loading from stores to complete. + new Thread("Mongo wind loader for tracked race " + getRace().getName()) { + @Override + public void run() { + LockUtil.lockForRead(getSerializationLock()); + LockUtil.lockForWrite(getLoadingFromWindStoreLock()); + synchronized (TrackedRaceImpl.this) { + loadingFromWindStoreState = LoadingFromStoresState.RUNNING; // indicates that the serialization lock is now safely held + TrackedRaceImpl.this.notifyAll(); + } + try { + logger.info("Started loading wind tracks for " + getRace().getName()); + final Map loadedWindTracks = windStore.loadWindTracks( + trackedRegatta.getRegatta().getName(), TrackedRaceImpl.this, millisecondsOverWhichToAverageWind); + windTracks.putAll(loadedWindTracks); + updateEventTimePoints(loadedWindTracks.values()); + logger.info("Finished loading wind tracks for " + getRace().getName() + "! Found " + windTracks.size() + " wind tracks for this race!"); + + } finally { + synchronized (TrackedRaceImpl.this) { + loadingFromWindStoreState = LoadingFromStoresState.FINISHED; + TrackedRaceImpl.this.notifyAll(); + } + synchronized (loadingFromWindStoreState) { + loadingFromWindStoreState.notifyAll(); + } + LockUtil.unlockAfterWrite(getLoadingFromWindStoreLock()); + LockUtil.unlockAfterRead(getSerializationLock()); + } + } + }.start(); + // by default, a tracked race offers one course-based wind estimation and one track-based wind estimation track; + // other wind tracks may be added as fixes are received for them and as they are loaded from the persistent + // store + WindSource courseBasedWindSource = new WindSourceImpl(WindSourceType.COURSE_BASED); + windTracks.put(courseBasedWindSource, + getOrCreateWindTrack(courseBasedWindSource, delayForWindEstimationCacheInvalidation)); + WindSource trackBasedWindSource = new WindSourceImpl(WindSourceType.TRACK_BASED_ESTIMATION); + windTracks.put(trackBasedWindSource, + getOrCreateWindTrack(trackBasedWindSource, delayForWindEstimationCacheInvalidation)); + competitorRankings = new HashMap>(); + competitorRankingsLocks = new HashMap(); + // now wait until wind loading has at least started; then we know that the serialization lock is safely held by the loader + try { + waitUntilLoadingFromWindStoreComplete(); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Waiting for loading from stores to finish was interrupted", e); + } + } + + /** + * Assuming that the tracks were loaded from the persistent store, this method updates + * the time stamps that frame the data held by this tracked race. See {@link #timePointOfLastEvent}, {@link #timePointOfNewestEvent} + * and {@link #timePointOfOldestEvent}. + */ + private void updateEventTimePoints(Iterable> tracks) { + for (Track track : tracks) { + track.lockForRead(); + + try { + for (Timed fix : track.getRawFixes()) { + updated(fix.getTimePoint()); + } + } finally { + track.unlockAfterRead(); + } + } + } + + /** + * Object serialization obtains a read lock for the course so that in cannot change while serializing this object. + */ + private void writeObject(ObjectOutputStream s) throws IOException { + LockUtil.lockForWrite(getSerializationLock()); + try { + s.defaultWriteObject(); + } finally { + LockUtil.unlockAfterWrite(getSerializationLock()); + } + } + + /** + * Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}. + * When de-serializing, a possibly remote {@link #windStore} is ignored because it is transient. Instead, an + * {@link EmptyWindStore} is used for the de-serialized instance. + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + attachedRaceLogs = new HashMap<>(); + markPassingsTimes = new ArrayList>>(); + cacheInvalidationTimerLock = new Object(); + windStore = EmptyWindStore.INSTANCE; + competitorRankings = new HashMap>(); + competitorRankingsLocks = new HashMap(); + directionFromStartToNextMarkCache = new HashMap>(); + crossTrackErrorCache = new CrossTrackErrorCache(this); + maneuverCache = createManeuverCache(); + logger.info("Deserialized race " + getRace().getName()); + } + + @Override + public synchronized void waitUntilLoadingFromWindStoreComplete() throws InterruptedException { + while (loadingFromWindStoreState != LoadingFromStoresState.FINISHED) { + wait(); + } + } + + @Override + public synchronized void waitUntilLoadingFromGPSFixStoreComplete() throws InterruptedException { + synchronized (loadingFromGPSFixStoreStateMonitor) { + while (loadingFromGPSFixStoreState != LoadingFromStoresState.FINISHED) { + loadingFromGPSFixStoreStateMonitor.wait(); + } + } + } + + private SmartFutureCache>, EmptyUpdateInterval> createManeuverCache() { + return new SmartFutureCache>, EmptyUpdateInterval>( + new AbstractCacheUpdater>, EmptyUpdateInterval>() { + @Override + public com.sap.sse.common.Util.Triple> computeCacheUpdate(Competitor competitor, + EmptyUpdateInterval updateInterval) throws NoWindException { + return computeManeuvers(competitor); + } + }, /* nameForLocks */"Maneuver cache for race " + getRace().getName()); + } + + /** + * Precondition: race has already been set, e.g., in constructor before this method is called + */ + abstract protected TrackedLeg createTrackedLeg(Leg leg); + + public RegattaAndRaceIdentifier getRaceIdentifier() { + return new RegattaNameAndRaceName(getTrackedRegatta().getRegatta().getName(), getRace().getName()); + } + + @Override + public NavigableSet getMarkPassings(Competitor competitor) { + return markPassingsForCompetitor.get(competitor); + } + + protected NavigableSet getMarkPassingsInOrderAsNavigableSet(Waypoint waypoint) { + return markPassingsForWaypoint.get(waypoint); + } + + @Override + public WindStore getWindStore() { + return windStore; + } + + @Override + public Iterable getMarkPassingsInOrder(Waypoint waypoint) { + return getMarkPassingsInOrderAsNavigableSet(waypoint); + } + + protected NavigableSet getOrCreateMarkPassingsInOrderAsNavigableSet(Waypoint waypoint) { + NavigableSet result = getMarkPassingsInOrderAsNavigableSet(waypoint); + if (result == null) { + result = createMarkPassingsCollectionForWaypoint(waypoint); + } + return result; + } + + protected NavigableSet createMarkPassingsCollectionForWaypoint(Waypoint waypoint) { + final ConcurrentSkipListSet result = new ConcurrentSkipListSet( + MarkPassingByTimeComparator.INSTANCE); + LockUtil.lockForRead(getSerializationLock()); + try { + markPassingsForWaypoint.put(waypoint, result); + } finally { + LockUtil.unlockAfterRead(getSerializationLock()); + } + return result; + } + + @Override + public TimePoint getStartOfTracking() { + return startOfTrackingReceived; + } + + @Override + public TimePoint getEndOfTracking() { + return endOfTrackingReceived; + } + + public void invalidateStartTime() { + startTime = null; + } + + public void invalidateEndTime() { + endTime = null; + } + + protected void invalidateMarkPassingTimes() { + synchronized (markPassingsTimes) { + markPassingsTimes.clear(); + } + } + + /** + * Calculates the start time of the race from various sources. The highest precedence take the {@link #attachedRaceLogs race logs}, + * followed by the field {@link #startTimeReceived} which can explicitly be set using {@link #setStartTimeReceived(TimePoint)}. + * If that does not provide any start time either, a start time is attempted to be inferred from the time points + * of the start mark passing events. + */ + @Override + public TimePoint getStartOfRace() { + if (startTime == null) { + for (RaceLog raceLog : attachedRaceLogs.values()) { + startTime = new StartTimeFinder(raceLog).analyze(); + if (startTime != null) { + break; + } + } + if (startTime == null) { + startTime = getStartTimeReceived(); + // If not null, check if the first mark passing for the start line is too much after the + // startTimeReceived; if so, return an adjusted, later start time. + // If no official start time was received, try to estimate the start time using the mark passings for + // the start line. + final Waypoint firstWaypoint = getRace().getCourse().getFirstWaypoint(); + if (firstWaypoint != null) { + if (startTimeReceived != null) { + TimePoint timeOfFirstMarkPassing = getFirstPassingTime(firstWaypoint); + if (timeOfFirstMarkPassing != null) { + long startTimeReceived2timeOfFirstMarkPassingFirstMark = timeOfFirstMarkPassing.asMillis() + - startTimeReceived.asMillis(); + if (startTimeReceived2timeOfFirstMarkPassingFirstMark > MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS) { + startTime = new MillisecondsTimePoint(timeOfFirstMarkPassing.asMillis() + - MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS); + } else { + startTime = startTimeReceived; + } + } + } else { + final NavigableSet markPassingsForFirstWaypointInOrder = getMarkPassingsInOrderAsNavigableSet(firstWaypoint); + if (markPassingsForFirstWaypointInOrder != null) { + startTime = calculateStartOfRaceFromMarkPassings(markPassingsForFirstWaypointInOrder, + getRace().getCompetitors()); + } + } + } + } + } + return startTime; + } + + /** + * Calculates the end time of the race from the mark passings of the last course waypoint + */ + @Override + public TimePoint getEndOfRace() { + if (endTime == null) { + endTime = getLastPassingOfFinishLine(); + } + return endTime; + } + + private TimePoint getLastPassingOfFinishLine() { + TimePoint passingTime = null; + final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint(); + if (lastWaypoint != null) { + Iterable markPassingsInOrder = getMarkPassingsInOrder(lastWaypoint); + if (markPassingsInOrder != null) { + lockForRead(markPassingsInOrder); + try { + for (MarkPassing passingFinishLine : markPassingsInOrder) { + passingTime = passingFinishLine.getTimePoint(); + } + } finally { + unlockAfterRead(markPassingsInOrder); + } + } + } + return passingTime; + } + + private TimePoint getFirstPassingTime(Waypoint waypoint) { + NavigableSet markPassingsInOrder = getMarkPassingsInOrderAsNavigableSet(waypoint); + MarkPassing firstMarkPassing = null; + if (markPassingsInOrder != null) { + lockForRead(markPassingsInOrder); + try { + if (!markPassingsInOrder.isEmpty()) { + firstMarkPassing = markPassingsInOrder.first(); + } + } finally { + unlockAfterRead(markPassingsInOrder); + } + } + TimePoint timeOfFirstMarkPassing = null; + if (firstMarkPassing != null) { + timeOfFirstMarkPassing = firstMarkPassing.getTimePoint(); + } + return timeOfFirstMarkPassing; + } + + private TimePoint calculateStartOfRaceFromMarkPassings(NavigableSet markPassings, + Iterable competitors) { + TimePoint startOfRace = null; + // Find the first mark passing within the largest cluster crossing the line within one minute. + final long ONE_MINUTE_IN_MILLIS = 60 * 1000; + lockForRead(markPassings); + try { + if (markPassings != null) { + int largestStartGroupWithinOneMinuteSize = 0; + MarkPassing startOfLargestGroupSoFar = null; + int candiateGroupSize = 0; + MarkPassing candidateForStartOfLargestGroupSoFar = null; + Iterator iterator = markPassings.iterator(); + // sweep over all start mark passings and for each element find the number of competitors that passed + // the start up to one minute later; + // pick the start mark passing of the competitor leading the largest such group + while (iterator.hasNext()) { + MarkPassing currentMarkPassing = iterator.next(); + if (candidateForStartOfLargestGroupSoFar == null) { + // first start mark passing + candidateForStartOfLargestGroupSoFar = currentMarkPassing; + candiateGroupSize = 1; + startOfLargestGroupSoFar = currentMarkPassing; + largestStartGroupWithinOneMinuteSize = 1; + } else { + if (currentMarkPassing.getTimePoint().asMillis() + - candidateForStartOfLargestGroupSoFar.getTimePoint().asMillis() <= ONE_MINUTE_IN_MILLIS) { + // currentMarkPassing is within one minute of candidateForStartOfLargestGroupSoFar; extend + // candidate group... + candiateGroupSize++; + if (candiateGroupSize > largestStartGroupWithinOneMinuteSize) { + // ...and remember as best fit if greater than largest group so far + startOfLargestGroupSoFar = candidateForStartOfLargestGroupSoFar; + largestStartGroupWithinOneMinuteSize = candiateGroupSize; + } + } else { + // currentMarkPassing is more than a minute after candidateForStartOfLargestGroupSoFar; + // advance + // candidateForStartOfLargestGroupSoFar and reduce group size counter, until + // candidateForStartOfLargestGroupSoFar + // is again within the one-minute interval; may catch up all the way to currentMarkPassing + // if that was + // more than a minute after its predecessor + while (currentMarkPassing.getTimePoint().asMillis() + - candidateForStartOfLargestGroupSoFar.getTimePoint().asMillis() > ONE_MINUTE_IN_MILLIS) { + candidateForStartOfLargestGroupSoFar = markPassings + .higher(candidateForStartOfLargestGroupSoFar); + candiateGroupSize--; + } + } + } + } + startOfRace = startOfLargestGroupSoFar == null ? null : startOfLargestGroupSoFar.getTimePoint(); + } + } finally { + unlockAfterRead(markPassings); + } + return startOfRace; + } + + @Override + public boolean hasStarted(TimePoint at) { + return getStartOfRace() != null && getStartOfRace().compareTo(at) <= 0; + } + + @Override + public boolean isLive(TimePoint at) { + final Date startOfLivePeriod; + final Date endOfLivePeriod; + if (!hasGPSData() || !hasWindData()) { + startOfLivePeriod = null; + endOfLivePeriod = null; + } else { + if (getStartOfRace() == null) { + startOfLivePeriod = getStartOfTracking().asDate(); + } else { + startOfLivePeriod = new Date(getStartOfRace().asMillis() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS); + } + if (getEndOfRace() == null) { + if (getTimePointOfNewestEvent() != null) { + endOfLivePeriod = new Date(getTimePointOfNewestEvent().asMillis() + + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS); + } else { + endOfLivePeriod = null; + } + } else { + endOfLivePeriod = new Date(getEndOfRace().asMillis() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS); + } + } + + // if an empty timepoint is given then take the start of the race + if (at == null) { + at = new MillisecondsTimePoint(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() <= at.asMillis() && + at.asMillis() <= endOfLivePeriod.getTime(); + return result; + } + + @Override + public RaceDefinition getRace() { + return race; + } + + @Override + public Iterable getTrackedLegs() { + // ensure that no course modification is carried out while copying the tracked legs + getRace().getCourse().lockForRead(); + try { + return new ArrayList(trackedLegs.values()); + } finally { + getRace().getCourse().unlockAfterRead(); + } + } + + @Override + public Iterable>> getMarkPassingsTimes() { + getRace().getCourse().lockForRead(); // ensure the list of waypoints doesn't change while we're updating the + // markPassingTimes structure + try { + synchronized (markPassingsTimes) { + if (markPassingsTimes.isEmpty()) { + // Remark: sometimes it can happen that a mark passing with a wrong time stamp breaks the right time + // order of the waypoint times + Date previousLegPassingTime = null; + for (Waypoint waypoint : getRace().getCourse().getWaypoints()) { + TimePoint firstPassingTime = null; + TimePoint lastPassingTime = null; + NavigableSet markPassings = getMarkPassingsInOrderAsNavigableSet(waypoint); + if (markPassings != null && !markPassings.isEmpty()) { + // ensure the leg times are in the right time order; there may perhaps be left-overs for + // marks to be reached later that + // claim it has been passed in the past which may have been an accidental tracker + // read-out; + // the results of getMarkPassingsInOrder(to) has by definition an ascending time-point + // ordering + lockForRead(markPassings); + try { + for (MarkPassing currentMarkPassing : markPassings) { + Date currentPassingDate = currentMarkPassing.getTimePoint().asDate(); + if (previousLegPassingTime == null + || currentPassingDate.after(previousLegPassingTime)) { + firstPassingTime = currentMarkPassing.getTimePoint(); + previousLegPassingTime = currentPassingDate; + break; + } + } + } finally { + unlockAfterRead(markPassings); + } + } + com.sap.sse.common.Util.Pair timesPair = new com.sap.sse.common.Util.Pair(firstPassingTime, + lastPassingTime); + markPassingsTimes.add(new com.sap.sse.common.Util.Pair>(waypoint, timesPair)); + } + } + return markPassingsTimes; + } + } finally { + getRace().getCourse().unlockAfterRead(); + } + } + + @Override + public Distance getDistanceTraveled(Competitor competitor, TimePoint timePoint) { + final Distance result; + NavigableSet markPassings = getMarkPassings(competitor); + try { + lockForRead(markPassings); + if (markPassings.isEmpty()) { + result = null; + } else { + TimePoint end = timePoint; + if (markPassings.last().getWaypoint() == getRace().getCourse().getLastWaypoint() + && timePoint.compareTo(markPassings.last().getTimePoint()) > 0) { + // competitor has finished race; use time point of crossing the finish line + end = markPassings.last().getTimePoint(); + } else if (markPassings.last().getWaypoint() != getRace().getCourse().getLastWaypoint() + && ((getEndOfTracking() != null && timePoint.after(getEndOfTracking())) + || getStatus().getStatus() == TrackedRaceStatusEnum.FINISHED)) { + // If the race is no longer tracking and hence no more data can be expected, and the competitor + // hasn't finished the race, no valid distance traveled can be determined + // for the competitor in this race. + end = null; + } + if (end == null) { + result = null; + } else { + result = getTrack(competitor).getDistanceTraveled(markPassings.first().getTimePoint(), end); + } + } + return result; + } finally { + unlockAfterRead(markPassings); + } + } + + @Override + public GPSFixTrack getTrack(Competitor competitor) { + return tracks.get(competitor); + } + + @Override + public TrackedLeg getTrackedLegFinishingAt(Waypoint endOfLeg) { + getRace().getCourse().lockForRead(); + try { + int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(endOfLeg); + if (indexOfWaypoint == -1) { + throw new IllegalArgumentException("Waypoint " + endOfLeg + " not found in " + getRace().getCourse()); + } else if (indexOfWaypoint == 0) { + throw new IllegalArgumentException("Waypoint " + endOfLeg + " isn't start of any leg in " + + getRace().getCourse()); + } + return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint - 1)); + } finally { + getRace().getCourse().unlockAfterRead(); + } + } + + @Override + public TrackedLeg getTrackedLegStartingAt(Waypoint startOfLeg) { + getRace().getCourse().lockForRead(); + try { + int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(startOfLeg); + if (indexOfWaypoint == -1) { + throw new IllegalArgumentException("Waypoint " + startOfLeg + " not found in " + getRace().getCourse()); + } else if (indexOfWaypoint == Util.size(getRace().getCourse().getWaypoints()) - 1) { + throw new IllegalArgumentException("Waypoint " + startOfLeg + " isn't start of any leg in " + + getRace().getCourse()); + } + return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint)); + } finally { + getRace().getCourse().unlockAfterRead(); + } + } + + @Override + public TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, TimePoint at) { + NavigableSet roundings = getMarkPassings(competitor); + lockForRead(roundings); + try { + NavigableSet localRoundings = new ArrayListNavigableSet<>(roundings.size(), + new TimedComparator()); + localRoundings.addAll(roundings); + } finally { + unlockAfterRead(roundings); + } + TrackedLegOfCompetitor result = null; + if (roundings != null) { + TrackedLeg trackedLeg; + // obtain last waypoint before obtaining mark passings monitor because obtaining the last waypoint + // obtains the read lock for the course + final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint(); + MarkPassing lastBeforeOrAt = roundings.floor(new DummyMarkPassingWithTimePointOnly(at)); + // already finished the race? + if (lastBeforeOrAt != null) { + // and not at or after last mark passing + if (lastWaypoint != lastBeforeOrAt.getWaypoint()) { + trackedLeg = getTrackedLegStartingAt(lastBeforeOrAt.getWaypoint()); + } else { + // exactly *at* last mark passing? + if (!roundings.isEmpty() && at.equals(roundings.last().getTimePoint())) { + // exactly at finish line; return last leg + trackedLeg = getTrackedLegFinishingAt(lastBeforeOrAt.getWaypoint()); + } else { + // no, then we're after the last mark passing + trackedLeg = null; + } + } + } else { + // before beginning of race + trackedLeg = null; + } + if (trackedLeg != null) { + result = trackedLeg.getTrackedLeg(competitor); + } + } + return result; + } + + public TrackedLeg getTrackedLeg(Leg leg) { + getRace().getCourse().lockForRead(); + try { + return trackedLegs.get(leg); + } finally { + getRace().getCourse().unlockAfterRead(); + } + } + + @Override + public TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, Leg leg) { + final TrackedLeg trackedLeg = getTrackedLeg(leg); + return trackedLeg == null ? null : trackedLeg.getTrackedLeg(competitor); + } + + @Override + public long getUpdateCount() { + return updateCount; + } + + @Override + public int getRankDifference(Competitor competitor, Leg leg, TimePoint timePoint) { + int previousRank; + if (leg == getRace().getCourse().getFirstLeg()) { + // first leg; report rank difference from 0 + previousRank = 0; + } else { + TrackedLeg previousLeg = getTrackedLegFinishingAt(leg.getFrom()); + previousRank = previousLeg.getTrackedLeg(competitor).getRank(timePoint); + } + int currentRank = getTrackedLeg(competitor, leg).getRank(timePoint); + return currentRank - previousRank; + } + + @Override + public int getRank(Competitor competitor) throws NoWindException { + return getRank(competitor, MillisecondsTimePoint.now()); + } + + @Override + public Competitor getOverallLeader(TimePoint timePoint) throws NoWindException { + try { + Competitor result = null; + List ranks = getCompetitorsFromBestToWorst(timePoint); + if (ranks != null && !ranks.isEmpty()) { + result = ranks.iterator().next(); + } + return result; + } catch (NoWindError e) { + throw e.getCause(); + } + } + + @Override + public int getRank(Competitor competitor, TimePoint timePoint) throws NoWindException { + try { + int result; + if (getMarkPassings(competitor).isEmpty()) { + result = 0; + } else { + result = getCompetitorsFromBestToWorst(timePoint).indexOf(competitor) + 1; + } + return result; + } catch (NoWindError e) { + throw e.getCause(); + } + } + + @Override + public List getCompetitorsFromBestToWorst(TimePoint timePoint) throws NoWindException { + NamedReentrantReadWriteLock readWriteLock; + synchronized (competitorRankingsLocks) { + readWriteLock = competitorRankingsLocks.get(timePoint); + if (readWriteLock == null) { + readWriteLock = new NamedReentrantReadWriteLock("competitor rankings for race " + getRace().getName() + + " for time point " + timePoint, /* fair */false); + competitorRankingsLocks.put(timePoint, readWriteLock); + } + } + List rankedCompetitors; + final boolean lockForWrite; + synchronized (competitorRankings) { + rankedCompetitors = competitorRankings.get(timePoint); + if (rankedCompetitors == null) { + lockForWrite = true; + } else { + lockForWrite = false; + } + } + if (lockForWrite) { + LockUtil.lockForWrite(readWriteLock); + } else { + LockUtil.lockForRead(readWriteLock); + } + try { + if (rankedCompetitors == null) { + rankedCompetitors = competitorRankings.get(timePoint); // try again; maybe a writer released the write + // lock after updating the cache + if (rankedCompetitors == null) { + RaceRankComparator comparator = new RaceRankComparator(this, timePoint); + rankedCompetitors = new ArrayList(); + for (Competitor c : getRace().getCompetitors()) { + rankedCompetitors.add(c); + } + Collections.sort(rankedCompetitors, comparator); + synchronized (competitorRankings) { + competitorRankings.put(timePoint, rankedCompetitors); + } + } + } + return rankedCompetitors; + } finally { + if (lockForWrite) { + LockUtil.unlockAfterWrite(readWriteLock); + } else { + LockUtil.unlockAfterRead(readWriteLock); + } + } + } + + @Override + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) + throws NoWindException { + NavigableSet markPassings = getMarkPassings(competitor); + TimePoint from = null; + lockForRead(markPassings); + try { + if (markPassings != null && !markPassings.isEmpty()) { + from = markPassings.iterator().next().getTimePoint(); + } + } finally { + unlockAfterRead(markPassings); + } + Distance result; + if (from != null) { + result = getAverageAbsoluteCrossTrackError(competitor, from, timePoint, /* upwindOnly */true, waitForLatestAnalysis); + } else { + result = null; + } + return result; + } + + @Override + public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) + throws NoWindException { + NavigableSet markPassings = getMarkPassings(competitor); + TimePoint from = null; + lockForRead(markPassings); + try { + if (markPassings != null && !markPassings.isEmpty()) { + from = markPassings.iterator().next().getTimePoint(); + } + } finally { + unlockAfterRead(markPassings); + } + Distance result; + if (from != null) { + result = getAverageSignedCrossTrackError(competitor, from, timePoint, /* upwindOnly */true, waitForLatestAnalysis); + } else { + result = null; + } + return result; + } + + @Override + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, + boolean waitForLatestAnalysis) throws NoWindException { + Distance result; + result = crossTrackErrorCache + .getAverageAbsoluteCrossTrackError(competitor, from, to, upwindOnly, waitForLatestAnalysis); + return result; + } + + @Override + public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, + boolean waitForLatestAnalysis) throws NoWindException { + Distance result; + result = crossTrackErrorCache + .getAverageSignedCrossTrackError(competitor, from, to, upwindOnly, waitForLatestAnalysis); + return result; + } + + @Override + public TrackedLegOfCompetitor getCurrentLeg(Competitor competitor, TimePoint timePoint) { + NavigableSet competitorMarkPassings = markPassingsForCompetitor.get(competitor); + DummyMarkPassingWithTimePointOnly markPassingTimePoint = new DummyMarkPassingWithTimePointOnly(timePoint); + TrackedLegOfCompetitor result = null; + if (!competitorMarkPassings.isEmpty()) { + MarkPassing lastMarkPassingAtOfBeforeTimePoint = competitorMarkPassings.floor(markPassingTimePoint); + if (lastMarkPassingAtOfBeforeTimePoint != null) { + Waypoint waypointPassedLastAtOrBeforeTimePoint = lastMarkPassingAtOfBeforeTimePoint.getWaypoint(); + // don't return a leg if competitor has already finished last leg and therefore the race + if (waypointPassedLastAtOrBeforeTimePoint != getRace().getCourse().getLastWaypoint()) { + result = getTrackedLegStartingAt(waypointPassedLastAtOrBeforeTimePoint).getTrackedLeg(competitor); + } + } + } + return result; + } + + @Override + public TrackedLeg getCurrentLeg(TimePoint timePoint) { + Waypoint lastWaypointPassed = null; + int indexOfLastWaypointPassed = -1; + for (Map.Entry> entry : markPassingsForWaypoint.entrySet()) { + if (!entry.getValue().isEmpty()) { + MarkPassing first = entry.getValue().first(); + // Did the mark passing happen at or before the requested time point? + if (first.getTimePoint().compareTo(timePoint) <= 0) { + int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(entry.getKey()); + if (indexOfWaypoint > indexOfLastWaypointPassed) { + indexOfLastWaypointPassed = indexOfWaypoint; + lastWaypointPassed = entry.getKey(); + } + } + } + } + TrackedLeg result = null; + if (lastWaypointPassed != null && lastWaypointPassed != getRace().getCourse().getLastWaypoint()) { + result = getTrackedLegStartingAt(lastWaypointPassed); + } + return result; + } + + @Override + public MarkPassing getMarkPassing(Competitor competitor, Waypoint waypoint) { + final NavigableSet markPassings = getMarkPassings(competitor); + if (markPassings != null) { + lockForRead(markPassings); + try { + for (MarkPassing markPassing : markPassings) { + if (markPassing.getWaypoint() == waypoint) { + return markPassing; + } + } + } finally { + unlockAfterRead(markPassings); + } + } + return null; + } + + /** + * This method was a synchronization bottleneck when it was using a regular HashMap for {@link #markTracks}. It is + * frequently used, and the most frequent case is that the get call on {@link #markTracks} succeeds + * with a non-null result. To improve performance for this case, {@link #markTracks} now is a + * {@link ConcurrentHashMap} that can be read while writes are going on without locking or synchronization. Only if + * the get call does not provide a result, the entire procedure is repeated, this time with + * synchronization to avoid duplicate track creation for the same mark. + */ + @Override + public GPSFixTrack getOrCreateTrack(Mark mark) { + GPSFixTrack result = markTracks.get(mark); + if (result == null) { + // try again, this time with more expensive synchronization + synchronized (markTracks) { + LockUtil.lockForRead(getSerializationLock()); + try { + result = markTracks.get(mark); + if (result == null) { + result = createMarkTrack(mark); + markTracks.put(mark, result); + } + } finally { + LockUtil.unlockAfterRead(getSerializationLock()); + } + } + } + return result; + } + + protected DynamicGPSFixTrackImpl createMarkTrack(Mark mark) { + return new DynamicGPSFixTrackImpl(mark, millisecondsOverWhichToAverageSpeed); + } + + @Override + public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) { + Position result = null; + for (Mark mark : waypoint.getMarks()) { + Position nextPos = getOrCreateTrack(mark).getEstimatedPosition(timePoint, /* extrapolate */false); + if (result == null) { + result = nextPos; + } else if (nextPos != null) { + result = result.translateGreatCircle(result.getBearingGreatCircle(nextPos), result.getDistance(nextPos) + .scale(0.5)); + } + } + return result; + } + + @Override + public boolean hasWindData() { + boolean result = false; + Course course = getRace().getCourse(); + Waypoint firstWaypoint = course.getFirstWaypoint(); + TimePoint timepoint = startTime != null ? startTime : startOfTrackingReceived; + if (firstWaypoint != null && timepoint != null) { + Position position = getApproximatePosition(firstWaypoint, timepoint); + if (position != null) { + Wind wind = getWind(position, timepoint); + if (wind != null) { + result = true; + } + } + } + return result; + } + + @Override + public boolean hasGPSData() { + boolean result = false; + if (!tracks.values().isEmpty()) { + for (GPSFixTrack gpsTrack : tracks.values()) { + if (gpsTrack.getFirstRawFix() != null) { + result = true; + break; + } + } + } + return result; + } + + @Override + public Wind getWind(Position p, TimePoint at) { + return getWind(p, at, getWindSourcesToExclude()); + } + + @Override + public Wind getWind(Position p, TimePoint at, Iterable windSourcesToExclude) { + final WindWithConfidence> windWithConfidence = getWindWithConfidence(p, at, + windSourcesToExclude); + return windWithConfidence == null ? null : windWithConfidence.getObject(); + } + + @Override + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { + return getWindWithConfidence(p, at, getWindSourcesToExclude()); + } + + @Override + public Iterable getWindSourcesToExclude() { + synchronized (windSourcesToExclude) { + return Collections.unmodifiableCollection(windSourcesToExclude); + } + } + + @Override + public void setWindSourcesToExclude(Iterable windSourcesToExclude) { + Set old = new HashSet<>(this.windSourcesToExclude); + synchronized (this.windSourcesToExclude) { + LockUtil.lockForRead(getSerializationLock()); + try { + this.windSourcesToExclude.clear(); + for (WindSource windSourceToExclude : windSourcesToExclude) { + this.windSourcesToExclude.add(windSourceToExclude); + } + } finally { + LockUtil.unlockAfterRead(getSerializationLock()); + } + } + if (!old.equals(this.windSourcesToExclude)) { + clearAllCachesExceptManeuvers(); + triggerManeuverCacheRecalculationForAllCompetitors(); + } + } + + @Override + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, + Iterable windSourcesToExclude) { + boolean canUseSpeedOfAtLeastOneWindSource = false; + Weigher> timeWeigherThatPretendsToAlsoWeighPositions = new PositionAndTimePointWeigher( + /* halfConfidenceAfterMilliseconds */10000l); + ConfidenceBasedWindAverager> averager = ConfidenceFactory.INSTANCE + .createWindAverager(timeWeigherThatPretendsToAlsoWeighPositions); + List>> windFixesWithConfidences = new ArrayList>>(); + for (WindSource windSource : getWindSources()) { + // TODO consider parallelizing and consider caching + if (!Util.contains(windSourcesToExclude, windSource)) { + WindTrack track = getOrCreateWindTrack(windSource); + WindWithConfidence> windWithConfidence = track.getAveragedWindWithConfidence(p, at); + if (windWithConfidence != null) { + windFixesWithConfidences.add(windWithConfidence); + canUseSpeedOfAtLeastOneWindSource = canUseSpeedOfAtLeastOneWindSource + || windSource.getType().useSpeed(); + } + } + } + HasConfidence> average = averager.getAverage( + windFixesWithConfidences, new com.sap.sse.common.Util.Pair(p, at)); + WindWithConfidence> result = average == null ? null + : new WindWithConfidenceImpl>(average.getObject(), average.getConfidence(), + new com.sap.sse.common.Util.Pair(p, at), canUseSpeedOfAtLeastOneWindSource); + return result; + } + + @Override + public Wind getDirectionFromStartToNextMark(final TimePoint at) { + Future future; + FutureTask newFuture = null; + synchronized (directionFromStartToNextMarkCache) { + future = directionFromStartToNextMarkCache.get(at); + if (future == null) { + newFuture = new FutureTask(new Callable() { + @Override + public Wind call() { + Wind result; + Leg firstLeg = getRace().getCourse().getFirstLeg(); + if (firstLeg != null) { + Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at); + Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at); + if (firstLegStart != null && firstLegEnd != null) { + result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(1.0, + firstLegEnd.getBearingGreatCircle(firstLegStart))); + } else { + result = null; + } + } else { + result = null; + } + return result; + } + }); + directionFromStartToNextMarkCache.put(at, newFuture); + } + } + if (newFuture != null) { + newFuture.run(); + future = newFuture; + } + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public TimePoint getTimePointOfOldestEvent() { + return timePointOfOldestEvent; + } + + @Override + public TimePoint getTimePointOfNewestEvent() { + return timePointOfNewestEvent; + } + + @Override + public TimePoint getTimePointOfLastEvent() { + return timePointOfLastEvent; + } + + /** + * @param timeOfEvent + * may be null meaning to only unblock waiters but not update any time points + */ + protected void updated(TimePoint timeOfEvent) { + updateCount++; + clearAllCachesExceptManeuvers(); + if (timeOfEvent != null) { + if (timePointOfNewestEvent == null || timePointOfNewestEvent.compareTo(timeOfEvent) < 0) { + timePointOfNewestEvent = timeOfEvent; + } + if (timePointOfOldestEvent == null || timePointOfOldestEvent.compareTo(timeOfEvent) > 0) { + timePointOfOldestEvent = timeOfEvent; + } + timePointOfLastEvent = timeOfEvent; + } + synchronized (this) { + notifyAll(); + } + } + + protected void setStartTimeReceived(TimePoint start) { + if ((start == null) != (startTimeReceived == null) || (start != null && !start.equals(startTimeReceived))) { + this.startTimeReceived = start; + invalidateStartTime(); + invalidateMarkPassingTimes(); + } + } + + @Override + public TimePoint getStartTimeReceived() { + return startTimeReceived; + } + + protected void setStartOfTrackingReceived(TimePoint startOfTracking) { + this.startOfTrackingReceived = startOfTracking; + } + + protected void setEndOfTrackingReceived(TimePoint endOfTracking) { + this.endOfTrackingReceived = endOfTracking; + } + + /** + * Schedules the clearing of the caches. If a cache clearing is already scheduled, this is a no-op. + */ + private void clearAllCachesExceptManeuvers() { + synchronized (cacheInvalidationTimerLock) { + if (cacheInvalidationTimer == null) { + cacheInvalidationTimer = new Timer("Cache invalidation timer for TrackedRaceImpl " + + getRace().getName()); + cacheInvalidationTimer.schedule(new TimerTask() { + @Override + public void run() { + synchronized (cacheInvalidationTimerLock) { + cacheInvalidationTimer.cancel(); + cacheInvalidationTimer = null; + } + synchronized (competitorRankings) { + competitorRankings.clear(); + } + synchronized (competitorRankingsLocks) { + competitorRankingsLocks.clear(); + } + } + }, DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS); + } + } + } + + @Override + public synchronized void waitForNextUpdate(int sinceUpdate) throws InterruptedException { + while (updateCount <= sinceUpdate) { + wait(); // ...until updated(...) notifies us + } + } + + @Override + public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { + // expecting to hold the course's write lock + invalidateMarkPassingTimes(); + LockUtil.lockForRead(getSerializationLock()); + try { + // assuming that getRace().getCourse()'s write lock is held by the current thread + updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointAdded(zeroBasedIndex, waypointThatGotAdded); + getOrCreateMarkPassingsInOrderAsNavigableSet(waypointThatGotAdded); + for (Mark mark : waypointThatGotAdded.getMarks()) { + getOrCreateTrack(mark); + } + // a waypoint got added; this means that a leg got added as well; but we shouldn't claim we know where + // in the leg list of the course the leg was added; that's an implementation secret of CourseImpl. So try: + LinkedHashMap reorderedTrackedLegs = new LinkedHashMap(); + List newLegs = getRace().getCourse().getLegs(); + for (Leg leg : newLegs) { + TrackedLeg trackedLeg = trackedLegs.get(leg); + if (trackedLeg != null) { + reorderedTrackedLegs.put(leg, trackedLeg); + } else { + reorderedTrackedLegs.put(leg, createTrackedLeg(leg)); + } + } + // now ensure that the iteration order is in sync with the leg iteration order + trackedLegs.clear(); + for (Map.Entry entry : reorderedTrackedLegs.entrySet()) { + trackedLegs.put(entry.getKey(), entry.getValue()); + entry.getValue().waypointsMayHaveChanges(); + } + updated(/* time point */null); // no maneuver cache invalidation required because we don't yet have mark + // passings for new waypoint + } finally { + LockUtil.unlockAfterRead(getSerializationLock()); + } + } + + private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointAdded(int zeroBasedIndex, + Waypoint waypointThatGotAdded) { + if (zeroBasedIndex < 2) { + // the observing listener on any previous mark will be GCed; we need to ensure + // that the cache is recomputed + clearDirectionFromStartToNextMarkCache(); + Iterator waypointsIter = getRace().getCourse().getWaypoints().iterator(); + waypointsIter.next(); // skip first + if (waypointsIter.hasNext()) { + waypointsIter.next(); // skip second + if (waypointsIter.hasNext()) { + Waypoint oldSecond = waypointsIter.next(); + stopAndRemoveStartToNextMarkCacheInvalidationListener(oldSecond); + } + } + } + addStartToNextMarkCacheInvalidationListener(waypointThatGotAdded); + } + + private void clearDirectionFromStartToNextMarkCache() { + synchronized (directionFromStartToNextMarkCache) { + directionFromStartToNextMarkCache.clear(); + } + } + + private void addStartToNextMarkCacheInvalidationListener(Waypoint waypoint) { + for (Mark mark : waypoint.getMarks()) { + addStartToNextMarkCacheInvalidationListener(mark); + } + } + + private void addStartToNextMarkCacheInvalidationListener(Mark mark) { + GPSFixTrack track = getOrCreateTrack(mark); + StartToNextMarkCacheInvalidationListener listener = new StartToNextMarkCacheInvalidationListener(track); + LockUtil.lockForRead(getSerializationLock()); + try { + startToNextMarkCacheInvalidationListeners.put(mark, listener); + } finally { + LockUtil.unlockAfterRead(getSerializationLock()); + } + track.addListener(listener); + } + + private void stopAndRemoveStartToNextMarkCacheInvalidationListener(Waypoint waypoint) { + for (Mark mark : waypoint.getMarks()) { + stopAndRemoveStartToNextMarkCacheInvalidationListener(mark); + } + } + + private void stopAndRemoveStartToNextMarkCacheInvalidationListener(Mark mark) { + StartToNextMarkCacheInvalidationListener listener = startToNextMarkCacheInvalidationListeners.get(mark); + if (listener != null) { + listener.stopListening(); + LockUtil.lockForRead(getSerializationLock()); + try { + startToNextMarkCacheInvalidationListeners.remove(mark); + } finally { + LockUtil.unlockAfterRead(getSerializationLock()); + } + } + } + + @Override + public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { + invalidateMarkPassingTimes(); + LockUtil.lockForRead(getSerializationLock()); + try { + // assuming that getRace().getCourse()'s write lock is held by the current thread + updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(zeroBasedIndex, + waypointThatGotRemoved); + Leg toRemove = null; + Leg last = null; + int i = 0; + for (Map.Entry e : trackedLegs.entrySet()) { + last = e.getKey(); + if (i == zeroBasedIndex) { + toRemove = e.getKey(); + break; + } + i++; + } + if (toRemove == null && !trackedLegs.isEmpty()) { + // last waypoint removed + toRemove = last; + } + if (toRemove != null) { + logger.info("Removing tracked leg at zero-based index " + zeroBasedIndex + " from tracked race " + + getRace().getName()); + LinkedHashMap newTrackedLegs = new LinkedHashMap<>(); + for (Map.Entry trackedLegsEntry : trackedLegs.entrySet()) { + if (trackedLegsEntry.getKey() == toRemove) { + break; + } else { + newTrackedLegs.put(trackedLegsEntry.getKey(), trackedLegsEntry.getValue()); + } + } + trackedLegs.clear(); + trackedLegs.putAll(newTrackedLegs); + List newLegs = getRace().getCourse().getLegs(); + for (int j = zeroBasedIndex; j < newLegs.size(); j++) { + trackedLegs.put(newLegs.get(j), createTrackedLeg(newLegs.get(j))); + } + updated(/* time point */null); + } + // remove all corresponding markpassings if a waypoint has been removed + NavigableSet markPassingsRemoved; + markPassingsRemoved = markPassingsForWaypoint.remove(waypointThatGotRemoved); + for (NavigableSet markPassingsForOneCompetitor : markPassingsForCompetitor.values()) { + if (!markPassingsForOneCompetitor.isEmpty()) { + final Competitor competitor = markPassingsForOneCompetitor.iterator().next().getCompetitor(); + LockUtil.lockForWrite(getMarkPassingsLock(markPassingsForOneCompetitor)); + try { + markPassingsForOneCompetitor.removeAll(markPassingsRemoved); + } finally { + LockUtil.unlockAfterWrite(getMarkPassingsLock(markPassingsForOneCompetitor)); + } + triggerManeuverCacheRecalculation(competitor); + } + } + } finally { + LockUtil.unlockAfterRead(getSerializationLock()); + } + } + + protected NamedReentrantReadWriteLock getMarkPassingsLock(Iterable markPassings) { + synchronized (locksForMarkPassings) { + NamedReentrantReadWriteLock lock = locksForMarkPassings.get(markPassings); + if (lock == null) { + lock = new NamedReentrantReadWriteLock("mark passings lock for tracked race " + getRace().getName(), /* fair */ + false); + locksForMarkPassings.put(markPassings, lock); + } + return lock; + } + } + + private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(int zeroBasedIndex, + Waypoint waypointThatGotRemoved) { + if (zeroBasedIndex < 2) { + // the observing listener on any previous mark will be GCed; we need to ensure + // that the cache is recomputed + clearDirectionFromStartToNextMarkCache(); + stopAndRemoveStartToNextMarkCacheInvalidationListener(waypointThatGotRemoved); + Iterator waypointsIter = getRace().getCourse().getWaypoints().iterator(); + if (waypointsIter.hasNext()) { // catches the case of a course being empty + waypointsIter.next(); // skip first + if (waypointsIter.hasNext()) { + waypointsIter.next(); // skip second + if (waypointsIter.hasNext()) { + Waypoint newSecond = waypointsIter.next(); + addStartToNextMarkCacheInvalidationListener(newSecond); + } + } + } + } + } + + @Override + public TrackedRegatta getTrackedRegatta() { + return trackedRegatta; + } + + @Override + public Wind getEstimatedWindDirection(Position position, TimePoint timePoint) { + WindWithConfidence estimatedWindWithConfidence = getEstimatedWindDirectionWithConfidence(position, + timePoint); + return estimatedWindWithConfidence == null ? null : estimatedWindWithConfidence.getObject(); + } + + @Override + public WindWithConfidence getEstimatedWindDirectionWithConfidence(Position position, TimePoint timePoint) { + DummyMarkPassingWithTimePointOnly dummyMarkPassingForNow = new DummyMarkPassingWithTimePointOnly(timePoint); + Weigher weigher = ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( + // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero + getMillisecondsOverWhichToAverageSpeed(), /* minimum confidence */0.0000000001); + Map> bearings = clusterBearingsByLegType(timePoint, position, + dummyMarkPassingForNow, weigher); + // use the minimum confidence of the four "quadrants" as the result's confidence + BearingWithConfidenceImpl reversedUpwindAverage = null; + int upwindNumberOfRelevantBoats = 0; + double confidence = 0; + int numberOfBoatsRelevantForEstimate = 0; + BearingWithConfidence resultBearing = null; + if (bearings != null) { + BearingWithConfidenceCluster[] bearingClustersUpwind = bearings.get(LegType.UPWIND).splitInTwo( + getMinimumAngleBetweenDifferentTacksUpwind(), timePoint); + if (!bearingClustersUpwind[0].isEmpty() && !bearingClustersUpwind[1].isEmpty()) { + BearingWithConfidence average0 = bearingClustersUpwind[0].getAverage(timePoint); + BearingWithConfidence average1 = bearingClustersUpwind[1].getAverage(timePoint); + upwindNumberOfRelevantBoats = Math + .min(bearingClustersUpwind[0].size(), bearingClustersUpwind[1].size()); + confidence = Math.min(average0.getConfidence(), average1.getConfidence()) + * getRace().getBoatClass().getUpwindWindEstimationConfidence(upwindNumberOfRelevantBoats); + reversedUpwindAverage = new BearingWithConfidenceImpl(average0.getObject() + .middle(average1.getObject()).reverse(), confidence, timePoint); + } + BearingWithConfidenceImpl downwindAverage = null; + int downwindNumberOfRelevantBoats = 0; + BearingWithConfidenceCluster[] bearingClustersDownwind = bearings.get(LegType.DOWNWIND) + .splitInTwo(getMinimumAngleBetweenDifferentTacksDownwind(), timePoint); + if (!bearingClustersDownwind[0].isEmpty() && !bearingClustersDownwind[1].isEmpty()) { + BearingWithConfidence average0 = bearingClustersDownwind[0].getAverage(timePoint); + BearingWithConfidence average1 = bearingClustersDownwind[1].getAverage(timePoint); + downwindNumberOfRelevantBoats = Math.min(bearingClustersDownwind[0].size(), + bearingClustersDownwind[1].size()); + confidence = Math.min(average0.getConfidence(), average1.getConfidence()) + * getRace().getBoatClass().getDownwindWindEstimationConfidence(downwindNumberOfRelevantBoats); + downwindAverage = new BearingWithConfidenceImpl(average0.getObject().middle( + average1.getObject()), confidence, timePoint); + } + numberOfBoatsRelevantForEstimate = upwindNumberOfRelevantBoats + downwindNumberOfRelevantBoats; + BearingWithConfidenceCluster resultCluster = new BearingWithConfidenceCluster(weigher); + assert upwindNumberOfRelevantBoats == 0 || reversedUpwindAverage != null; + if (upwindNumberOfRelevantBoats > 0) { + resultCluster.add(reversedUpwindAverage); + } + assert downwindNumberOfRelevantBoats == 0 || downwindAverage != null; + if (downwindNumberOfRelevantBoats > 0) { + resultCluster.add(downwindAverage); + } + resultBearing = resultCluster.getAverage(timePoint); + } + return resultBearing == null ? null : new WindWithConfidenceImpl(new WindImpl(null, timePoint, + new KnotSpeedWithBearingImpl( + /* speedInKnots */numberOfBoatsRelevantForEstimate, resultBearing.getObject())), + resultBearing.getConfidence(), resultBearing.getRelativeTo(), /* useSpeed */false); + } + + // TODO confidences need to be computed not only based on timePoint but also on position: boats far away don't + // contribute as confidently as boats close by + private Map> clusterBearingsByLegType(TimePoint timePoint, + Position position, DummyMarkPassingWithTimePointOnly dummyMarkPassingForNow, Weigher weigher) { + Weigher weigherForMarkPassingProximity = new HyperbolicTimeDifferenceWeigher( + getMillisecondsOverWhichToAverageSpeed() * 5); + Map> bearings = new HashMap>(); + for (LegType legType : LegType.values()) { + bearings.put(legType, new BearingWithConfidenceCluster(weigher)); + } + Map legTypesCache = new HashMap(); + getRace().getCourse().lockForRead(); // ensure the course doesn't change, particularly lose the leg we're + // interested in, while we're running + try { + for (Competitor competitor : getRace().getCompetitors()) { + TrackedLegOfCompetitor leg; + try { + leg = getTrackedLeg(competitor, timePoint); + } catch (IllegalArgumentException iae) { + logger.warning("Caught " + iae + " during wind estimation; ignoring seemingly broken leg"); + logger.log(Level.SEVERE, "clusterBearingsByLegType", iae); + // supposedly, we got a "Waypoint X isn't start of any leg in Y" exception; leg not found + leg = null; + } + // if bearings was set to null this indicates there was an exception; no need for further calculations, + // return null + if (bearings != null && leg != null) { + TrackedLeg trackedLeg = getTrackedLeg(leg.getLeg()); + LegType legType; + try { + legType = legTypesCache.get(trackedLeg); + if (legType == null) { + legType = trackedLeg.getLegType(timePoint); + legTypesCache.put(trackedLeg, legType); + } + if (legType != LegType.REACHING) { + GPSFixTrack track = getTrack(competitor); + if (!track.hasDirectionChange(timePoint, + /* be even more conservative than maneuver detection to really try to get "straight line" behavior */ + getManeuverDegreeAngleThreshold()/2.)) { + SpeedWithBearingWithConfidence estimatedSpeedWithConfidence = track + .getEstimatedSpeed(timePoint, weigher); + if (estimatedSpeedWithConfidence != null + && estimatedSpeedWithConfidence.getObject() != null && + // Mark passings may be missing or far off. This can lead to boats apparently + // going "backwards" regarding the leg's direction; ignore those + isNavigatingForward(estimatedSpeedWithConfidence.getObject().getBearing(), + trackedLeg, timePoint)) { + // additionally to generally excluding maneuvers, reduce confidence around mark + // passings: + NavigableSet markPassings = getMarkPassings(competitor); + double markPassingProximityConfidenceReduction = 1.0; + lockForRead(markPassings); + try { + NavigableSet prevMarkPassing = markPassings.headSet( + dummyMarkPassingForNow, /* inclusive */true); + NavigableSet nextMarkPassing = markPassings.tailSet( + dummyMarkPassingForNow, /* inclusive */true); + if (prevMarkPassing != null && !prevMarkPassing.isEmpty()) { + markPassingProximityConfidenceReduction *= Math.max(0.0, + 1.0 - weigherForMarkPassingProximity.getConfidence(prevMarkPassing + .last().getTimePoint(), timePoint)); + } + if (nextMarkPassing != null && !nextMarkPassing.isEmpty()) { + markPassingProximityConfidenceReduction *= Math.max(0.0, + 1.0 - weigherForMarkPassingProximity.getConfidence(nextMarkPassing + .first().getTimePoint(), timePoint)); + } + } finally { + unlockAfterRead(markPassings); + } + BearingWithConfidence bearing = new BearingWithConfidenceImpl( + estimatedSpeedWithConfidence.getObject() == null ? null + : estimatedSpeedWithConfidence.getObject().getBearing(), + markPassingProximityConfidenceReduction + * estimatedSpeedWithConfidence.getConfidence(), + estimatedSpeedWithConfidence.getRelativeTo()); + BearingWithConfidenceCluster bearingClusterForLegType = bearings + .get(legType); + bearingClusterForLegType.add(bearing); + } + } + } + } catch (NoWindException e) { + logger.fine("Unable to determine leg type for race " + getRace().getName() + + " while trying to estimate wind (Background: I've got a NoWindException)"); + bearings = null; + } + } + } + } finally { + getRace().getCourse().unlockAfterRead(); + } + return bearings; + } + + /** + * Checks if the bearing generally moves in the direction that the trackedLeg has at time + * point at. + */ + private boolean isNavigatingForward(Bearing bearing, TrackedLeg trackedLeg, TimePoint at) { + Bearing legBearing = trackedLeg.getLegBearing(at); + return Math.abs(bearing.getDifferenceTo(legBearing).getDegrees()) < 90; + } + + /** + * This is probably best explained by example. If the wind bearing is from port to starboard, the situation looks + * like this: + * + *

+     *                                 ^
+     *                 Wind            | Boat
+     *               ----------->      |
+     *                                 |
+     * 
+     * 
+ * + * In this case, the boat gets the wind from port, so the result has to be {@link Tack#PORT}. The angle between the + * boat's heading (which we can only approximate by the boat's course over ground) and the wind bearing in this case + * is 90 degrees. wind.{@link Bearing#getDifferenceTo(Bearing) getDifferenceTo}(boat) in this case will + * return a bearing representing -90 degrees. + *

+ * + * If the wind is blowing the other way, the angle returned by {@link Bearing#getDifferenceTo(Bearing)} will + * correspond to +90 degrees. In other words, a negative angle means starboard tack, a positive angle represents + * port tack. + *

+ * + * For the unlikely case of 0 degrees difference, {@link Tack#STARBOARD} will result. + * + * @return null in case the boat's bearing cannot be determined for timePoint + * @throws NoWindException + */ + @Override + public Tack getTack(Competitor competitor, TimePoint timePoint) throws NoWindException { + final SpeedWithBearing estimatedSpeed = getTrack(competitor).getEstimatedSpeed(timePoint); + Tack result = null; + if (estimatedSpeed != null) { + result = getTack(getTrack(competitor).getEstimatedPosition(timePoint, /* extrapolate */false), timePoint, + estimatedSpeed.getBearing()); + } + return result; + } + + /** + * Based on the wind direction at timePoint and at position where, compares the + * boatBearing to the wind's bearing at that time and place and determined the tack. + * + * @throws NoWindException + * in case the wind cannot be determined because without a wind direction, the tack cannot be determined + * either + */ + private Tack getTack(Position where, TimePoint timePoint, Bearing boatBearing) throws NoWindException { + final Wind wind = getWind(where, timePoint); + Tack result; + if (wind == null) { + throw new NoWindException("Can't determine wind direction in position " + where + " at " + timePoint + + ", therefore cannot determine tack"); + } + Bearing windBearing = wind.getBearing(); + Bearing difference = windBearing.getDifferenceTo(boatBearing); + result = difference.getDegrees() <= 0 ? Tack.PORT : Tack.STARBOARD; + return result; + } + + @Override + public String toString() { + return "TrackedRace for " + getRace(); + } + + @Override + public List approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) { + DouglasPeucker douglasPeucker = new DouglasPeucker( + getTrack(competitor)); + return douglasPeucker.approximate(maxDistance, from, to); + } + + protected void triggerManeuverCacheRecalculationForAllCompetitors() { + final List shuffledCompetitors = new ArrayList<>(); + for (Competitor competitor : (getRace().getCompetitors())) { + shuffledCompetitors.add(competitor); + } + Collections.shuffle(shuffledCompetitors); + for (Competitor competitor : shuffledCompetitors) { + triggerManeuverCacheRecalculation(competitor); + } + } + + protected void triggerManeuverCacheRecalculation(final Competitor competitor) { + maneuverCache.triggerUpdate(competitor, /* updateInterval */null); + } + + private com.sap.sse.common.Util.Triple> computeManeuvers(Competitor competitor) throws NoWindException { + logger.finest("computeManeuvers(" + competitor.getName() + ") called in tracked race " + this); + long startedAt = System.currentTimeMillis(); + // compute the maneuvers for competitor + com.sap.sse.common.Util.Triple> result = null; + NavigableSet markPassings = getMarkPassings(competitor); + boolean markPassingsNotEmpty; + TimePoint extendedFrom = null; + MarkPassing crossedFinishLine = null; + // getLastWaypoint() will wait for a read lock on the course; do this outside the synchronized block to avoid + // deadlocks + final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint(); + lockForRead(markPassings); + try { + markPassingsNotEmpty = markPassings != null && !markPassings.isEmpty(); + if (markPassingsNotEmpty) { + extendedFrom = markPassings.iterator().next().getTimePoint(); + crossedFinishLine = getMarkPassing(competitor, lastWaypoint); + } + } finally { + unlockAfterRead(markPassings); + } + if (markPassingsNotEmpty) { + TimePoint extendedTo; + if (crossedFinishLine != null) { + extendedTo = crossedFinishLine.getTimePoint(); + } else { + final GPSFixMoving lastRawFix = getTrack(competitor).getLastRawFix(); + if (lastRawFix != null) { + extendedTo = lastRawFix.getTimePoint(); + } else { + extendedTo = null; + } + } + if (extendedTo != null) { + try { + List extendedResultForCache = detectManeuvers(competitor, extendedFrom, extendedTo, /* ignoreMarkPassings */ false); + result = new com.sap.sse.common.Util.Triple>(extendedFrom, extendedTo, + extendedResultForCache); + } catch (NoWindException ex) { + // Catching the NoWindException here without letting it propagate thru other handlers. + // This is mainly to avoid having logs flooded with stack traces. It is safe to catch + // it here because we can assume that this exception does not hide any severe problem + // other than that there is no wind. Because maneuvers are mostly computed using a + // future cache and need wind (like getTack) they will often fail before the wind has been + // loaded from database. We can safely return null here because we can be sure that + // cache will be updated when new fixes are fed into the stream therefore leading to a + // recomputation. + logger.fine("NoWindException during computation of maneuvers for " + competitor.getName()); + } + } // else competitor has no fixes to consider; remove any maneuver cache entry + } // else competitor hasn't started yet; remove any maneuver cache entry + logger.finest("computeManeuvers(" + competitor.getName() + ") called in tracked race " + this + " took " + + (System.currentTimeMillis() - startedAt) + "ms"); + return result; + } + + /** + * @param ignoreMarkPassings + * When true, no {@link ManeuverType#MARK_PASSING} maneuvers will be identified, and the + * fact that a mark passing would split up what else may be a penalty circle is ignored. This is helpful + * for recursive calls, e.g., after identifying a tack and a jibe around a mark passing and trying to + * identify for the time before and after the mark passing which maneuvers exist on which side of the + * passing. + * @return a valid but possibly empty list + * @see #detectManeuvers(Competitor, List, boolean, TimePoint, TimePoint) + */ + private List detectManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean ignoreMarkPassings) throws NoWindException { + return detectManeuvers( + competitor, approximate(competitor, getRace().getBoatClass().getMaximumDistanceForCourseApproximation(), + from, to), ignoreMarkPassings, from, to); + } + + /** + * Tries to detect maneuvers on the competitor's track based on a number of approximating fixes. The + * fixes contain bearing information, but this is not the bearing leading to the next approximation fix but the + * bearing the boat had at the time of the approximating fix which is taken from the original track. + *

+ * + * The time period assumed for a maneuver duration is taken from the + * {@link BoatClass#getApproximateManeuverDurationInMilliseconds() boat class}. If no maneuver is detected, an empty + * list is returned. Maneuvers can only be expected to be detected if at least three fixes are provided in + * approximatedFixesToAnalyze. For the inner approximating fixes (all except the first and the last + * approximating fix), their course changes according to the approximated path (and not the underlying actual + * tracked fixes) are computed. Subsequent course changes to the same direction are then grouped. Those in closer + * timely distance than {@link #getApproximateManeuverDurationInMilliseconds()} (including single course changes + * that have no surrounding other course changes to group) are grouped into one {@link Maneuver}. + * + * @param ignoreMarkPassings + * When true, no {@link ManeuverType#MARK_PASSING} maneuvers will be identified, and the + * fact that a mark passing would split up what else may be a penalty circle is ignored. This is helpful + * for recursive calls, e.g., after identifying a tack and a jibe around a mark passing and trying to + * identify for the time before and after the mark passing which maneuvers exist on which side of the + * passing. + * @param earliestManeuverStart + * maneuver start will not be before this time point; if a maneuver is found whose time point is at or + * after this time point, no matter how close it is, its start regarding speed and course into the + * maneuver and the leg before the maneuver is not taken from an earlier time point, even if half the + * maneuver duration before the maneuver time point were before this time point. + * @param latestManeuverEnd + * maneuver end will not be after this time point; if a maneuver is found whose time point is at or + * before this time point, no matter how close it is, its end regarding speed and course out of the + * maneuver and the leg after the maneuver is not taken from a later time point, even if half the + * maneuver duration after the maneuver time point were after this time point. + * + * @return an empty list if no maneuver is detected for competitor between from and + * to, or else the list of maneuvers detected. + */ + private List detectManeuvers(Competitor competitor, List approximatingFixesToAnalyze, + boolean ignoreMarkPassings, TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) + throws NoWindException { + List result = new ArrayList(); + if (approximatingFixesToAnalyze.size() > 2) { + List> courseChangeSequenceInSameDirection = new ArrayList>(); + Iterator approximationPointsIter = approximatingFixesToAnalyze.iterator(); + GPSFixMoving previous = approximationPointsIter.next(); + GPSFixMoving current = approximationPointsIter.next(); + // the bearings in these variables are between approximation points + SpeedWithBearing speedWithBearingOnApproximationFromPreviousToCurrent = previous + .getSpeedAndBearingRequiredToReach(current); + SpeedWithBearing speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges = speedWithBearingOnApproximationFromPreviousToCurrent; + SpeedWithBearing speedWithBearingOnApproximationFromCurrentToNext; // will certainly be assigned because + // iter's collection's size > 2 + do { + GPSFixMoving next = approximationPointsIter.next(); + speedWithBearingOnApproximationFromCurrentToNext = current.getSpeedAndBearingRequiredToReach(next); + // compute course change on "approximation track" + CourseChange courseChange = speedWithBearingOnApproximationFromPreviousToCurrent + .getCourseChangeRequiredToReach(speedWithBearingOnApproximationFromCurrentToNext); + com.sap.sse.common.Util.Pair courseChangeAtFix = new com.sap.sse.common.Util.Pair(current, + courseChange); + if (!courseChangeSequenceInSameDirection.isEmpty() + && Math.signum(courseChangeSequenceInSameDirection.get(0).getB().getCourseChangeInDegrees()) != Math + .signum(courseChange.getCourseChangeInDegrees())) { + // course change in different direction; cluster the course changes in same direction so far, then + // start new list + List maneuvers = groupChangesInSameDirectionIntoManeuvers(competitor, + speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges, + courseChangeSequenceInSameDirection, ignoreMarkPassings, earliestManeuverStart, latestManeuverEnd); + result.addAll(maneuvers); + courseChangeSequenceInSameDirection.clear(); + speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges = speedWithBearingOnApproximationFromPreviousToCurrent; + } + courseChangeSequenceInSameDirection.add(courseChangeAtFix); + previous = current; + current = next; + speedWithBearingOnApproximationFromPreviousToCurrent = speedWithBearingOnApproximationFromCurrentToNext; + } while (approximationPointsIter.hasNext()); + if (!courseChangeSequenceInSameDirection.isEmpty()) { + result.addAll(groupChangesInSameDirectionIntoManeuvers(competitor, + speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges, + courseChangeSequenceInSameDirection, ignoreMarkPassings, earliestManeuverStart, latestManeuverEnd)); + } + } + return result; + } + + /** + * Fetches results from {@link #maneuverCache}. The cache is updated asynchronously after relevant updates have been + * received (see {@link #triggerManeuverCacheRecalculation(Competitor)} and + * {@link #triggerManeuverCacheRecalculationForAllCompetitors()}). Callers can choose whether to wait for any + * ongoing updates by using the waitForLatest parameter. From the cache the interval requested is then + * {@link #extractInterval(TimePoint, TimePoint, List) extracted}. + * + * @param waitForLatest + * if true, any currently ongoing maneuver recalculation for competitor is + * waited for before returning the result; otherwise, whatever is in the {@link #maneuverCache} for + * competitor, reduced to the interval requested, will be returned. + */ + @Override + public List getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest) + throws NoWindException { + com.sap.sse.common.Util.Triple> allManeuvers = maneuverCache.get(competitor, waitForLatest); + List result; + if (allManeuvers == null) { + result = Collections.emptyList(); + } else { + result = extractInterval(from, to, allManeuvers.getC()); + } + return result; + } + + private List extractInterval(TimePoint from, TimePoint to, List listOfTimed) { + List result; + result = new LinkedList(); + for (T timed : listOfTimed) { + if (timed.getTimePoint().compareTo(from) >= 0 && timed.getTimePoint().compareTo(to) <= 0) { + result.add(timed); + } + } + return result; + } + + /** + * Groups the {@link CourseChange} sequence into groups where the times of the fixes at which the course changes + * took place are no further apart than {@link #getApproximateManeuverDurationInMilliseconds()} milliseconds or + * where the distances of those course changes are less than two hull lengths apart. For those, a single + * {@link Maneuver} object is created and added to the resulting list. The maneuver sums up the direction changes of + * the individual {@link CourseChange} objects. This can result in direction changes of more than 180 degrees in one + * direction which may, e.g., represent a penalty circle or a mark rounding maneuver. As the maneuver's time point, + * the average time point of the course changes that went into the maneuver construction is used. + *

+ * @param speedWithBearingOnApproximationAtBeginning + * the speed/bearing before the first approximating fix passed in + * courseChangeSequenceInSameDirection + * @param courseChangeSequenceInSameDirection + * all expected to have equal {@link CourseChange#to()} values + * @param ignoreMarkPassings + * When true, no {@link ManeuverType#MARK_PASSING} maneuvers will be identified, and the + * fact that a mark passing would split up what else may be a penalty circle is ignored. This is helpful + * for recursive calls, e.g., after identifying a tack and a jibe around a mark passing and trying to + * identify for the time before and after the mark passing which maneuvers exist on which side of the + * passing. + * @param earliestManeuverStart + * maneuver start will not be before this time point; if a maneuver is found whose time point is at or + * after this time point, no matter how close it is, its start regarding speed and course into the + * maneuver and the leg before the maneuver is not taken from an earlier time point, even if half the + * maneuver duration before the maneuver time point were before this time point. + * @param latestManeuverEnd + * maneuver end will not be after this time point; if a maneuver is found whose time point is at or + * before this time point, no matter how close it is, its end regarding speed and course out of the + * maneuver and the leg after the maneuver is not taken from a later time point, even if half the + * maneuver duration after the maneuver time point were after this time point. + * + * @return a non-null list + */ + private List groupChangesInSameDirectionIntoManeuvers(Competitor competitor, + SpeedWithBearing speedWithBearingOnApproximationAtBeginning, + List> courseChangeSequenceInSameDirection, boolean ignoreMarkPassings, + TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) throws NoWindException { + List result = new ArrayList(); + List> group = new ArrayList>(); + if (!courseChangeSequenceInSameDirection.isEmpty()) { + Distance threeHullLengths = competitor.getBoat().getBoatClass().getHullLength().scale(3); + SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing + // before group + SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing + // before current + // course change + Iterator> iter = courseChangeSequenceInSameDirection.iterator(); + double totalCourseChangeInDegrees = 0.0; + long totalMilliseconds = 0l; + SpeedWithBearing afterCurrentCourseChange = null; // sure to be set because iter's collection is not empty + // and the first use requires group not to be empty which can only happen after the first group.add + do { + com.sap.sse.common.Util.Pair currentFixAndCourseChange = iter.next(); + if (!group.isEmpty() + // TODO use different maneuver times for upwind / reaching / downwind / cross-leg (mark passing) + // group contains complete maneuver if the next fix is too late or too far away to belong to the + // same maneuver + // FIXME penalty circles slow down the boat so much that time limit may get exceeded although + // distance limit is matched + && currentFixAndCourseChange.getA().getTimePoint().asMillis() + - group.get(group.size() - 1).getA().getTimePoint().asMillis() > getApproximateManeuverDurationInMilliseconds() + && currentFixAndCourseChange.getA().getPosition() + .getDistance(group.get(group.size() - 1).getA().getPosition()) + .compareTo(threeHullLengths) > 0) { + // if next is more then approximate maneuver duration later or further apart than two hull lengths, + // turn the current group into a maneuver and add to result + Util.addAll(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation, + group, afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds, earliestManeuverStart, latestManeuverEnd), result); + group.clear(); + totalCourseChangeInDegrees = 0.0; + totalMilliseconds = 0l; + beforeGroupOnApproximation = beforeCurrentCourseChangeOnApproximation; + } + afterCurrentCourseChange = beforeCurrentCourseChangeOnApproximation + .applyCourseChange(currentFixAndCourseChange.getB()); + totalMilliseconds += currentFixAndCourseChange.getA().getTimePoint().asMillis(); + totalCourseChangeInDegrees += currentFixAndCourseChange.getB().getCourseChangeInDegrees(); + group.add(currentFixAndCourseChange); + beforeCurrentCourseChangeOnApproximation = afterCurrentCourseChange; // speed/bearing after course + // change + } while (iter.hasNext()); + if (!group.isEmpty()) { + Util.addAll(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation, group, + afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds, earliestManeuverStart, latestManeuverEnd), result); + } + } + return result; + } + + private Iterable createManeuverFromGroupOfCourseChanges(Competitor competitor, + SpeedWithBearing speedWithBearingOnApproximationAtBeginning, List> group, + SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees, + long totalMilliseconds, TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) throws NoWindException { + List result = new ArrayList<>(); + TimePoint timePointBeforeManeuver = Collections.max(Arrays.asList(new MillisecondsTimePoint(group.get(0).getA().getTimePoint() + .asMillis() - getApproximateManeuverDurationInMilliseconds() / 2), earliestManeuverStart)); + TimePoint timePointAfterManeuver = Collections.min(Arrays.asList(new MillisecondsTimePoint(group.get(group.size() - 1).getA() + .getTimePoint().asMillis() + getApproximateManeuverDurationInMilliseconds() / 2), latestManeuverEnd)); + TimePoint maneuverTimePoint = computeManeuverTimepoint(competitor, timePointBeforeManeuver, timePointAfterManeuver); + final GPSFixTrack competitorTrack = getTrack(competitor); + Position maneuverPosition = competitorTrack.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false); + Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver, + speedWithBearingOnApproximationAtEnd.getBearing()); + ManeuverType maneuverType; + Distance maneuverLoss = null; + // the TrackedLegOfCompetitor variables may be null, e.g., in case the time points are before or after the race + TrackedLegOfCompetitor legBeforeManeuver = getTrackedLeg(competitor, timePointBeforeManeuver); + TrackedLegOfCompetitor legAfterManeuver = getTrackedLeg(competitor, timePointAfterManeuver); + Waypoint waypointPassed = null; // set for MARK_PASSING maneuvers only + NauticalSide sideToWhichWaypointWasPassed = null; // set for MARK_PASSING maneuvers only + final NauticalSide courseChangedTo = totalCourseChangeInDegrees < 0 ? NauticalSide.PORT : NauticalSide.STARBOARD; + // check for mask passing first; a tacking / jibe-setting mark rounding thus takes precedence over being + // detected as a penalty circle + final TimePoint markPassingTimePoint; + if (legBeforeManeuver != legAfterManeuver + // a maneuver at the start line is not to be considered a MARK_PASSING maneuver; show a tack as a tack + && legAfterManeuver != null + && legAfterManeuver.getLeg().getFrom() != getRace().getCourse().getFirstWaypoint()) { + waypointPassed = legAfterManeuver.getLeg().getFrom(); + MarkPassing markPassing = getMarkPassing(competitor, waypointPassed); + markPassingTimePoint = markPassing != null ? markPassing.getTimePoint() : maneuverTimePoint; + Position markPassingPosition = markPassing != null ? competitorTrack.getEstimatedPosition(markPassingTimePoint, /* extrapolate */false) : maneuverPosition; + sideToWhichWaypointWasPassed = courseChangedTo; + // produce an additional mark passing maneuver; continue to analyze to catch jibe sets and kiwi drops + result.add(new MarkPassingManeuverImpl(ManeuverType.MARK_PASSING, tackAfterManeuver, markPassingPosition, + markPassingTimePoint, speedWithBearingOnApproximationAtBeginning, + speedWithBearingOnApproximationAtEnd, totalCourseChangeInDegrees, maneuverLoss, waypointPassed, + sideToWhichWaypointWasPassed)); + } else { + markPassingTimePoint = null; + } + final Wind wind = getWind(maneuverPosition, maneuverTimePoint); + final SpeedWithBearing estimatedSpeedBeforeManeuver = competitorTrack.getEstimatedSpeed(timePointBeforeManeuver); + final SpeedWithBearing estimatedSpeedAfterManeuver = competitorTrack.getEstimatedSpeed(timePointAfterManeuver); + if (wind != null && estimatedSpeedBeforeManeuver != null && estimatedSpeedAfterManeuver != null) { + BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE; + final Bearing courseBeforeManeuver = estimatedSpeedBeforeManeuver.getBearing(); + final Bearing courseAfterManeuver = estimatedSpeedAfterManeuver.getBearing(); + boolean jibed = bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees, + courseAfterManeuver, wind.getBearing()); + boolean tacked = bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees, + courseAfterManeuver, wind.getFrom()); + if (markPassingTimePoint != null && (tacked || jibed)) { + // In case of a mark passing we need to split the maneuver analysis into the phase before and after + // the mark passing. First of all, this is important to identify the correct maneuver time point for + // each tack and jibe, second it is essential to call a penalty which is only the case if the tack and + // the jibe are on the same side of the mark passing; otherwise this may have been a jibe set or a + // kiwi drop. + // Therefore, we recursively detect the maneuvers for the segment before and the segment after the + // mark passing and add the results to our result. + result.addAll(detectManeuvers(competitor, timePointBeforeManeuver, markPassingTimePoint.minus(1), /* ignoreMarkPassings */ true)); + result.addAll(detectManeuvers(competitor, markPassingTimePoint.plus(1), timePointAfterManeuver, /* ignoreMarkPassings */ true)); + } else { + // Either there was no mark passing, or the mark passing was not accompanied by a tack or a jibe + if (tacked && jibed && markPassingTimePoint == null) { + maneuverType = ManeuverType.PENALTY_CIRCLE; + if (legBeforeManeuver != null) { + maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, + timePointAfterManeuver); + } + } else if (tacked) { + maneuverType = ManeuverType.TACK; + if (legBeforeManeuver != null) { + maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, + timePointAfterManeuver); + } + } else if (jibed) { + maneuverType = ManeuverType.JIBE; + if (legBeforeManeuver != null) { + maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, + timePointAfterManeuver); + } + } else { + // heading up or bearing away + Bearing windBearing = wind.getBearing(); + Bearing toWindBeforeManeuver = windBearing + .getDifferenceTo(speedWithBearingOnApproximationAtBeginning.getBearing()); + Bearing toWindAfterManeuver = windBearing.getDifferenceTo(speedWithBearingOnApproximationAtEnd + .getBearing()); + maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver + .getDegrees()) ? ManeuverType.HEAD_UP : ManeuverType.BEAR_AWAY; + } + final Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition, + maneuverTimePoint, speedWithBearingOnApproximationAtBeginning, + speedWithBearingOnApproximationAtEnd, totalCourseChangeInDegrees, maneuverLoss); + result.add(maneuver); + } + } + return result; + } + + /** + * Computes the maneuver time point as the time point along between maneuver start and end where the competitor's + * track has greatest change in course. + */ + private TimePoint computeManeuverTimepoint(Competitor competitor, TimePoint timePointBeforeManeuver, + TimePoint timePointAfterManeuver) { + TimePoint result = timePointBeforeManeuver; + GPSFixTrack track = getTrack(competitor); + GPSFixMoving lastFix = null; + double maxAngleSpeedInDegreesPerSecond = 0; + track.lockForRead(); + try { + for (Iterator i = track.getFixesIterator(timePointBeforeManeuver, /* inclusive */true); i + .hasNext();) { + GPSFixMoving fix = i.next(); + if (fix.getTimePoint().after(timePointAfterManeuver)) { + break; + } + if (lastFix != null) { + final SpeedWithBearing lastEstimatedSpeed = track.getEstimatedSpeed(lastFix.getTimePoint()); + if (lastEstimatedSpeed != null) { + Bearing courseAtLastFix = lastEstimatedSpeed.getBearing(); + final SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(fix.getTimePoint()); + if (estimatedSpeed != null) { + Bearing courseAtFix = estimatedSpeed.getBearing(); + double angleSpeedInDegreesPerSecond = Math.abs((courseAtFix + .getDifferenceTo(courseAtLastFix).getDegrees()) + / (double) (fix.getTimePoint().asMillis() - lastFix.getTimePoint().asMillis())); + if (angleSpeedInDegreesPerSecond > maxAngleSpeedInDegreesPerSecond) { + maxAngleSpeedInDegreesPerSecond = angleSpeedInDegreesPerSecond; + result = lastFix.getTimePoint(); + } + } + } + } + lastFix = fix; + } + } finally { + track.unlockAfterRead(); + } + return result; + } + + /** + * Fetches the boat class-specific parameter + */ + private double getManeuverDegreeAngleThreshold() { + return getRace().getBoatClass().getManeuverDegreeAngleThreshold(); + } + + private double getMinimumAngleBetweenDifferentTacksDownwind() { + return getRace().getBoatClass().getMinimumAngleBetweenDifferentTacksDownwind(); + } + + private double getMinimumAngleBetweenDifferentTacksUpwind() { + return getRace().getBoatClass().getMinimumAngleBetweenDifferentTacksUpwind(); + } + + private long getApproximateManeuverDurationInMilliseconds() { + return getRace().getBoatClass().getApproximateManeuverDurationInMilliseconds(); + } + + private class StartToNextMarkCacheInvalidationListener implements GPSTrackListener { + private static final long serialVersionUID = 3540278554797445085L; + private final GPSFixTrack listeningTo; + + public StartToNextMarkCacheInvalidationListener(GPSFixTrack listeningTo) { + this.listeningTo = listeningTo; + } + + public void stopListening() { + listeningTo.removeListener(this); + } + + @Override + public void gpsFixReceived(GPSFix fix, Mark mark) { + clearDirectionFromStartToNextMarkCache(); + } + + @Override + public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { + } + + @Override + public boolean isTransient() { + return false; + } + } + + @Override + public Distance getWindwardDistanceToOverallLeader(Competitor competitor, TimePoint timePoint) + throws NoWindException { + final TrackedLegOfCompetitor trackedLeg = getTrackedLeg(competitor, timePoint); + return trackedLeg == null ? null : trackedLeg.getWindwardDistanceToOverallLeader(timePoint); + } + + @Override + public Iterable getMarks() { + while (true) { + try { + return new HashSet(markTracks.keySet()); + } catch (ConcurrentModificationException cme) { + logger.info("Caught " + cme + "; trying again."); + } + } + } + + @Override + public Iterable getCourseSidelines() { + return new ArrayList(courseSidelines.values()); + } + + @Override + public long getDelayToLiveInMillis() { + return delayToLiveInMillis; + } + + protected void setDelayToLiveInMillis(long delayToLiveInMillis) { + this.delayToLiveInMillis = delayToLiveInMillis; + } + + @Override + public TrackedRaceStatus getStatus() { + return status; + } + + /** + * Changes to the {@link #status} variable are synchronized on the {@link #statusNotifier} field. + * + * @return + */ + protected Object getStatusNotifier() { + return statusNotifier; + } + + protected void setStatus(TrackedRaceStatus newStatus) { + assert newStatus != null; + final TrackedRaceStatusEnum oldStatus; + synchronized (getStatusNotifier()) { + oldStatus = getStatus().getStatus(); + this.status = newStatus; + getStatusNotifier().notifyAll(); + } + if (newStatus.getStatus() == TrackedRaceStatusEnum.LOADING && oldStatus != TrackedRaceStatusEnum.LOADING) { + suspendAllCachesNotUpdatingWhileLoading(); + } else if (oldStatus == TrackedRaceStatusEnum.LOADING && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) { + resumeAllCachesNotUpdatingWhileLoading(); + } + + } + + private void suspendAllCachesNotUpdatingWhileLoading() { + crossTrackErrorCache.suspend(); + maneuverCache.suspend(); + } + + private void resumeAllCachesNotUpdatingWhileLoading() { + crossTrackErrorCache.resume(); + maneuverCache.resume(); + } + + /** + * Waits on the current ("old") status object which is notified in {@link #setStatus(TrackedRaceStatus)} when the + * status is changed. The change as well as the check synchronize on the old status object. + */ + @Override + public void waitUntilNotLoading() { + synchronized (getStatusNotifier()) { + while (getStatus().getStatus() == TrackedRaceStatusEnum.LOADING) { + try { + getStatusNotifier().wait(); + } catch (InterruptedException e) { + logger.info("waitUntilNotLoading on tracked race " + this + " interrupted: " + e.getMessage() + + ". Continuing to wait."); + } + } + } + } + + @Override + public void attachRaceLog(final RaceLog raceLog) { + if (raceLog != null) { + this.attachedRaceLogs.put(raceLog.getId(), raceLog); + // Use the new race log, that possibly contains device mappings, to load GPSFix tracks from the DB + loadingFromGPSFixStoreState = LoadingFromStoresState.NOT_STARTED; + // When this tracked race is to be serialized, wait for the loading from stores to complete. + new Thread("Mongo mark and competitor track loader for tracked race " + getRace().getName()) { + @Override + public void run() { + LockUtil.lockForRead(getSerializationLock()); + LockUtil.lockForWrite(getLoadingFromGPSFixStoreLock()); + synchronized (TrackedRaceImpl.this) { + loadingFromGPSFixStoreState = LoadingFromStoresState.RUNNING; // indicates that the serialization + // lock is now safely held + TrackedRaceImpl.this.notifyAll(); + } + try { + logger.info("Started loading competitor tracks for " + getRace().getName()); + for (Competitor competitor : race.getCompetitors()) { + try { + gpsFixStore.loadCompetitorTrack( + (DynamicGPSFixTrack) tracks.get(competitor), raceLog, + competitor); + } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { + logger.log(Level.WARNING, "Could not load track for " + competitor); + } + } + logger.info("Finished loading competitor tracks for " + getRace().getName()); + logger.info("Started loading mark tracks for " + getRace().getName()); + for (Mark mark : getMarks()) { + try { + gpsFixStore.loadMarkTrack((DynamicGPSFixTrack) markTracks.get(mark), raceLog, + mark); + } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { + logger.log(Level.WARNING, "Could not load track for " + mark); + } + } + logger.info("Finished loading mark tracks for " + getRace().getName()); + + } finally { + synchronized (TrackedRaceImpl.this) { + loadingFromGPSFixStoreState = LoadingFromStoresState.FINISHED; + TrackedRaceImpl.this.notifyAll(); + } + synchronized (loadingFromGPSFixStoreStateMonitor) { + loadingFromGPSFixStoreStateMonitor.notifyAll(); + } + LockUtil.unlockAfterWrite(getLoadingFromGPSFixStoreLock()); + LockUtil.unlockAfterRead(getSerializationLock()); + } + } + }.start(); + } else { + logger.severe("Got a request to attach race log for an empty race log!"); + } + } + + @Override + public void detachRaceLog(Serializable identifier) { + this.attachedRaceLogs.remove(identifier); + } + + @Override + public void detachAllRaceLogs() { + this.attachedRaceLogs.clear(); + } + + @Override + public RaceLog getRaceLog(Serializable identifier) { + return attachedRaceLogs.get(identifier); + } + + @Override + public Distance getDistanceToStartLine(Competitor competitor, long millisecondsBeforeRaceStart) { + final Distance result; + if (getStartOfRace() == null) { + result = null; + } else { + TimePoint beforeStart = new MillisecondsTimePoint(getStartOfRace().asMillis() - millisecondsBeforeRaceStart); + result = getDistanceToStartLine(competitor, beforeStart); + } + return result; + } + + @Override + public Distance getDistanceToStartLine(Competitor competitor, TimePoint timePoint) { + Waypoint startWaypoint = getRace().getCourse().getFirstWaypoint(); + final Distance result; + if (startWaypoint == null) { + result = null; + } else { + Position competitorPosition = getTrack(competitor).getEstimatedPosition(timePoint, /* extrapolate */false); + if (competitorPosition == null) { + result = null; + } else { + Iterable marks = startWaypoint.getControlPoint().getMarks(); + Iterator marksIterator = marks.iterator(); + Mark first = marksIterator.next(); + Position firstPosition = getOrCreateTrack(first).getEstimatedPosition(timePoint, /* extrapolate */false); + if (firstPosition == null) { + result = null; + } else { + if (marksIterator.hasNext()) { + // it's a line / gate + Mark second = marksIterator.next(); + Position secondPosition = getOrCreateTrack(second).getEstimatedPosition(timePoint, /* extrapolate */ + false); + final Bearing bearingGreatCircle = firstPosition.getBearingGreatCircle(secondPosition); + if (bearingGreatCircle == null) { + result = null; + } else { + Position competitorProjectedOntoStartLine = competitorPosition.projectToLineThrough( + firstPosition, bearingGreatCircle); + result = competitorPosition.getDistance(competitorProjectedOntoStartLine); + } + } else { + result = competitorPosition.getDistance(firstPosition); + } + } + } + } + return result; + } + + @Override + public Speed getSpeed(Competitor competitor, long millisecondsBeforeRaceStart) { + if (getStartOfRace() == null) { + return null; + } + + TimePoint beforeStart = new MillisecondsTimePoint(getStartOfRace().asMillis() - millisecondsBeforeRaceStart); + return getTrack(competitor).getEstimatedSpeed(beforeStart); + } + + @Override + public Distance getDistanceFromStarboardSideOfStartLineWhenPassingStart(Competitor competitor) { + final Distance result; + TrackedLegOfCompetitor firstTrackedLegOfCompetitor = getTrackedLeg(competitor, getRace().getCourse().getFirstLeg()); + TimePoint competitorStartTime = firstTrackedLegOfCompetitor.getStartTime(); + if (competitorStartTime != null) { + Position competitorPositionWhenPassingStart = getTrack(competitor).getEstimatedPosition( + competitorStartTime, /* extrapolate */false); + final Position starboardMarkPosition = getStarboardMarkOfStartlinePosition(competitorStartTime); + if (competitorPositionWhenPassingStart != null && starboardMarkPosition != null) { + result = starboardMarkPosition == null ? null : competitorPositionWhenPassingStart.getDistance(starboardMarkPosition); + } else { + result = null; + } + } else { + result = null; + } + return result; + } + + @Override + public Distance getDistanceFromStarboardSideOfStartLine(Competitor competitor, TimePoint timePoint) { + final Distance result; + if (timePoint != null) { + Position competitorPositionWhenPassingStart = getTrack(competitor).getEstimatedPosition( + timePoint, /* extrapolate */false); + final Position starboardMarkPosition = getStarboardMarkOfStartlinePosition(timePoint); + if (competitorPositionWhenPassingStart != null && starboardMarkPosition != null) { + result = starboardMarkPosition == null ? null : competitorPositionWhenPassingStart.getDistance(starboardMarkPosition); + } else { + result = null; + } + } else { + result = null; + } + return result; + } + + /** + * Based on the bearing from the start waypoint to the next mark, identifies which of the two marks of the start + * line is on starboard. If the start waypoint has only one mark, that mark is returned. If the start line has two + * marks but the course has no other waypoint, + * null is returned. If the course has no waypoints at all, null is returned.

+ * + * The method has protected visibility largely for testing purposes. + */ + protected Mark getStarboardMarkOfStartlineOrSingleStartMark(TimePoint at) { + LineDetails startLine = getStartLine(at); + final Mark result; + if (startLine != null) { + result = startLine.getStarboardMarkWhileApproachingLine(); + } else { + Waypoint startWaypoint = getRace().getCourse().getFirstWaypoint(); + if (startWaypoint != null && startWaypoint.getMarks().iterator().hasNext()) { + result = startWaypoint.getMarks().iterator().next(); + } else { + result = null; + } + } + return result; + } + + /** + * Based on the bearing from the start waypoint to the + * next mark, identifies which of the two marks of the start line is on starboard and returns its position. If the start waypoint has only + * one mark, that mark is returned. If the start line has two marks but the course has no other waypoint, + * null is returned. If the course has no waypoints at all, null is returned. + */ + private Position getStarboardMarkOfStartlinePosition(TimePoint at) { + Mark starboardMark = getStarboardMarkOfStartlineOrSingleStartMark(at); + if (starboardMark != null) { + return getOrCreateTrack(starboardMark).getEstimatedPosition(at, /*extrapolate*/ false); + } + return null; + } + + protected NamedReentrantReadWriteLock getLoadingFromWindStoreLock() { + return loadingFromWindStoreLock; + } + + protected NamedReentrantReadWriteLock getLoadingFromGPSFixStoreLock() { + return loadingFromGPSFixStoreLock; + } + + /** + * If the waypoint is not a line, or no position can be determined for one of its marks at timePoint, + * null is returned. If no wind information is available but required to compute the advantage, null values + * are returned in those fields that depend on wind data. If the waypoint is null + * or is the only waypoint, null is returned because no reasonable statement can be + * made about the direction from which the line is to be passed. + */ + private LineDetails getLineLengthAndAdvantage(TimePoint timePoint, Waypoint waypoint) { + List markPositions = new ArrayList(); + int numberOfMarks = 0; + boolean allMarksHavePositions = true; + LineDetails result = null; + if (waypoint != null) { + for (Mark lineMark : waypoint.getMarks()) { + numberOfMarks++; + final Position estimatedMarkPosition = getOrCreateTrack(lineMark).getEstimatedPosition(timePoint, /* extrapolate */ + false); + if (estimatedMarkPosition != null) { + markPositions.add(estimatedMarkPosition); + } else { + allMarksHavePositions = false; + } + } + final List legs = getRace().getCourse().getLegs(); + // need at least one leg to make sense of a line + if (!legs.isEmpty()) { + try { + if (allMarksHavePositions && numberOfMarks == 2) { + final int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(waypoint); + final boolean isStartLine = indexOfWaypoint == 0; + final TrackedLeg legDeterminingDirection = getTrackedLeg(legs.get(isStartLine ? 0 + : indexOfWaypoint - 1)); + Distance crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint = markPositions.get(0) + .crossTrackError(markPositions.get(1), legDeterminingDirection.getLegBearing(timePoint)); + final Mark starboardMarkWhileApproachingLine; + final Mark portMarkWhileApproachingLine; + final Position starboardMarkPositionWhileApproachingLine; + final Position portMarkPositionWhileApproachingLine; + if (crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint.getMeters() < 0) { + portMarkWhileApproachingLine = Util.get(waypoint.getMarks(), 0); + starboardMarkWhileApproachingLine = Util.get(waypoint.getMarks(), 1); + } else { + portMarkWhileApproachingLine = Util.get(waypoint.getMarks(), 1); + starboardMarkWhileApproachingLine = Util.get(waypoint.getMarks(), 0); + } + portMarkPositionWhileApproachingLine = getOrCreateTrack(portMarkWhileApproachingLine) + .getEstimatedPosition(timePoint, /* extrapolate */false); + starboardMarkPositionWhileApproachingLine = getOrCreateTrack(starboardMarkWhileApproachingLine) + .getEstimatedPosition(timePoint, /* extrapolate */false); + final Bearing differenceToCombinedWind; + final NauticalSide advantageousSideWhileApproachingLine; + final Distance distanceAdvantage; + Wind combinedWind = getWind(markPositions.get(0), timePoint); + if (combinedWind != null) { + differenceToCombinedWind = portMarkPositionWhileApproachingLine.getBearingGreatCircle( + starboardMarkPositionWhileApproachingLine).getDifferenceTo(combinedWind.getFrom()); + Distance windwardDistanceFromFirstToSecondMark; + windwardDistanceFromFirstToSecondMark = legDeterminingDirection.getWindwardDistance( + markPositions.get(0), markPositions.get(1), timePoint); + final Position worseMarkPosition; + final Position betterMarkPosition; + if ((isStartLine && windwardDistanceFromFirstToSecondMark.getMeters() > 0) + || (!isStartLine && windwardDistanceFromFirstToSecondMark.getMeters() < 0)) { + // first mark is worse than second mark + worseMarkPosition = markPositions.get(0); + betterMarkPosition = markPositions.get(1); + } else { + // second mark is worse than first mark + worseMarkPosition = markPositions.get(1); + betterMarkPosition = markPositions.get(0); + } + if (windwardDistanceFromFirstToSecondMark.getMeters() >= 0) { + distanceAdvantage = windwardDistanceFromFirstToSecondMark; + } else { + distanceAdvantage = new CentralAngleDistance( + -windwardDistanceFromFirstToSecondMark.getCentralAngleRad()); + } + if (betterMarkPosition.crossTrackError(worseMarkPosition, + legDeterminingDirection.getLegBearing(timePoint)).getCentralAngleRad() > 0) { + advantageousSideWhileApproachingLine = NauticalSide.STARBOARD; + } else { + advantageousSideWhileApproachingLine = NauticalSide.PORT; + } + } else { + differenceToCombinedWind = null; + advantageousSideWhileApproachingLine = null; + distanceAdvantage = null; + } + result = new LineDetailsImpl(timePoint, waypoint, + portMarkPositionWhileApproachingLine + .getDistance(starboardMarkPositionWhileApproachingLine), + differenceToCombinedWind, advantageousSideWhileApproachingLine, distanceAdvantage, + portMarkWhileApproachingLine, starboardMarkWhileApproachingLine); + } + } catch (NoWindException e) { + // result remains null; + } + } + } + return result; + } + + + @Override + public LineDetails getStartLine(TimePoint at) { + return getLineLengthAndAdvantage(at, getRace().getCourse().getFirstWaypoint()); + } + + @Override + public LineDetails getFinishLine(TimePoint at) { + return getLineLengthAndAdvantage(at, getRace().getCourse().getLastWaypoint()); + } + + @Override + public SpeedWithConfidence getAverageWindSpeedWithConfidence(long resolutionInMillis) { + SpeedWithConfidence result = null; + if (getEndOfRace() != null) { + TimePoint fromTimePoint = getStartOfRace(); + TimePoint toTimePoint = getEndOfRace(); + + List windSourcesToDeliver = new ArrayList(); + WindSourceImpl windSource = new WindSourceImpl(WindSourceType.COMBINED); + windSourcesToDeliver.add(windSource); + + double sumWindSpeed = 0.0; + double sumWindSpeedConfidence = 0.0; + int speedCounter = 0; + + int numberOfFixes = (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis()) / resolutionInMillis); + WindTrack windTrack = getOrCreateWindTrack(windSource); + TimePoint timePoint = fromTimePoint; + for (int i = 0; i < numberOfFixes && toTimePoint != null && timePoint.compareTo(toTimePoint) < 0; i++) { + WindWithConfidence> averagedWindWithConfidence = windTrack + .getAveragedWindWithConfidence(null, timePoint); + if (averagedWindWithConfidence != null) { + double windSpeedinKnots = averagedWindWithConfidence.getObject().getKnots(); + double confidence = averagedWindWithConfidence.getConfidence(); + + sumWindSpeed += windSpeedinKnots; + sumWindSpeedConfidence += confidence; + + speedCounter++; + } + timePoint = new MillisecondsTimePoint(timePoint.asMillis() + resolutionInMillis); + } + if (speedCounter > 0) { + Speed averageWindSpeed = new KnotSpeedImpl(sumWindSpeed / speedCounter); + double averageWindSpeedConfidence = sumWindSpeedConfidence / speedCounter; + result = new SpeedWithConfidenceImpl(averageWindSpeed, averageWindSpeedConfidence, toTimePoint); + } + } + return result; + } + + @Override + public Distance getCourseLength() { + List legs = getRace().getCourse().getLegs(); + Distance raceDistance = new NauticalMileDistance(0); + for (Leg leg : legs) { + Waypoint from = leg.getFrom(); + Iterable markPassings = getMarkPassingsInOrder(from); + Iterator markPassingsIterator = markPassings.iterator(); + if (!markPassingsIterator.hasNext()) { + return null; + } + MarkPassing firstPassing = markPassingsIterator.next(); + TimePoint timePointOfFirstPassing = firstPassing.getTimePoint(); + Waypoint to = leg.getTo(); + Position fromPos = getApproximatePosition(from, timePointOfFirstPassing); + Position toPos = getApproximatePosition(to, timePointOfFirstPassing); + Distance legDistance = fromPos.getDistance(toPos); + raceDistance = raceDistance.add(legDistance); + } + return raceDistance; + } + + @Override + public Speed getSpeedWhenCrossingStartLine(Competitor competitor) { + NavigableSet competitorMarkPassings = getMarkPassings(competitor); + Speed competitorSpeedWhenPassingStart = null; + lockForRead(competitorMarkPassings); + try { + if (!competitorMarkPassings.isEmpty()) { + TimePoint competitorStartTime = competitorMarkPassings.first().getTimePoint(); + competitorSpeedWhenPassingStart = getTrack(competitor).getEstimatedSpeed( + competitorStartTime); + } + } finally { + unlockAfterRead(competitorMarkPassings); + } + return competitorSpeedWhenPassingStart; + } + + @Override + public GPSFixStore getGPSFixStore() { + return gpsFixStore; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java index c127fb84ebd..37d2c02afbc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java @@ -19,6 +19,7 @@ import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.RaceListener; @@ -168,12 +169,12 @@ public class TrackedRegattaImpl implements TrackedRegatta { } @Override - public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, long delayToLiveInMillis, + public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { logger.log(Level.INFO, "Creating DynamicTrackedRaceImpl for RaceDefinition " + raceDefinition.getName()); DynamicTrackedRaceImpl result = new DynamicTrackedRaceImpl(this, raceDefinition, sidelines, - windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed); + windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed); // adding the raceDefinition to the raceDefinitionSetToUpdate BEFORE calling addTrackedRace helps those who // are called back by RaceListener.raceAdded(TrackedRace) and who then expect the update to have happened if (raceDefinitionSetToUpdate != null) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindFixesAsNavigableSet.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindFixesAsNavigableSet.java index 70cc4c93634..fb385c29c87 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindFixesAsNavigableSet.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindFixesAsNavigableSet.java @@ -47,9 +47,10 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl this(track, trackedRace, null, null, resolutionInMilliseconds); } - public long getResolutionInMilliseconds() { - return resolutionInMilliseconds; - } + /** + * Compute the {@link Wind} fix for the specified position and time point to deliver in this virtual track. + */ + abstract protected Wind getWind(Position p, TimePoint timePoint); /** * @param from expected to be an integer multiple of {@link #resolutionInMilliseconds} or null @@ -66,6 +67,10 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl this.resolutionInMilliseconds = resolutionInMilliseconds; } + public long getResolutionInMilliseconds() { + return resolutionInMilliseconds; + } + protected WindTrack getTrack() { return track; } @@ -153,11 +158,6 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl return timePoint.compareTo(getFrom()) < 0 ? null : getWind(w.getPosition(), timePoint); } - /** - * Compute the {@link Wind} fix for the specified position and time point to deliver in this virtual track. - */ - abstract protected Wind getWind(Position p, TimePoint timePoint); - @Override public Wind floor(Wind w) { TimePoint timePoint = floorToResolution(w.getTimePoint()); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindTrackImpl.java index f4c6dbd2dd2..83f46f02258 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindTrackImpl.java @@ -4,10 +4,10 @@ import java.util.NavigableSet; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sse.common.Util; /** * A virtual wind track based on some virtual sequence of raw wind fixes. Subclasses should override @@ -41,6 +41,15 @@ public abstract class VirtualWindTrackImpl extends WindTrackImpl { }; } + /** + * Delegates to {@link #getAveragedWindUnsynchronized(Position, TimePoint)} + */ + @Override + public Wind getAveragedWind(Position p, TimePoint at) { + final WindWithConfidence> windWithConfidence = getAveragedWindUnsynchronized(p, at); + return windWithConfidence == null ? null : windWithConfidence.getObject(); + } + /** * This redefinition avoids very long searches in case at is before the race start or after the race's * newest event. Should at be out of this range, it is set to the closest border of this range before @@ -48,14 +57,8 @@ public abstract class VirtualWindTrackImpl extends WindTrackImpl { * time point is used instead. If both time points are not known, null is returned immediately. */ @Override - public Wind getAveragedWind(Position p, TimePoint at) { - final WindWithConfidence> windWithConfidence = getAveragedWindUnsynchronized(p, at); - return windWithConfidence == null ? null : windWithConfidence.getObject(); - } - - @Override - public WindWithConfidence> getAveragedWindWithConfidence(Position p, TimePoint at) { - WindWithConfidence> result = null; + public WindWithConfidence> getAveragedWindWithConfidence(Position p, TimePoint at) { + WindWithConfidence> result = null; TimePoint adjustedAt; final TimePoint startOfRace = getTrackedRace().getStartOfRace(); TimePoint timePointOfNewestEvent = getTrackedRace().getTimePointOfNewestEvent(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index 5aa0d2c8379..204f3d40ad2 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -19,7 +19,6 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; import com.sap.sailing.domain.confidence.ConfidenceFactory; import com.sap.sailing.domain.confidence.impl.PositionAndTimePointWeigher; @@ -28,6 +27,7 @@ import com.sap.sailing.domain.tracking.WindListener; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.util.impl.ArrayListNavigableSet; +import com.sap.sse.common.Util; /** * Records {@link Wind} objects over time and offers to average the last so many of them into an @@ -154,17 +154,23 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { */ @Override public Wind getAveragedWind(Position p, TimePoint at) { - final WindWithConfidence> estimatedWindUnsynchronized = getAveragedWindUnsynchronized(p, at); + final WindWithConfidence> estimatedWindUnsynchronized = getAveragedWindUnsynchronized(p, at); return estimatedWindUnsynchronized == null ? null : estimatedWindUnsynchronized.getObject(); } @Override - public WindWithConfidence> getAveragedWindWithConfidence(Position p, TimePoint at) { + public WindWithConfidence> getAveragedWindWithConfidence(Position p, TimePoint at) { return getAveragedWindUnsynchronized(p, at); } /** * This method implements the functionality of the {@link #getAveragedWind(Position, TimePoint)} interface method. + * It does so by collecting (smoothened, outliers removed)) wind fixes around the at time point up to + * an interval length as specified by {@link #getMillisecondsOverWhichToAverageWind()}. At least one fix that is + * closest to at will be picked up. If the track is empty, null is returned. Otherwise, + * the wind fixes in the interval constructed are averaged using a {@link ConfidenceBasedWindAverager}. + * + *

* However, not being synchronized, it does not obtain this object's monitor. Subclasses may use this * carefully if they can guarantee there are no concurrency issues with the internal fixes while iterating over the * result of {@link #getInternalFixes()}. @@ -173,16 +179,16 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { * if null, the averaged position of the original wind fixes is returned; otherwise, * p is used as the result's position and may be used for confidence determination. */ - protected WindWithConfidence> getAveragedWindUnsynchronized(Position p, TimePoint at) { + protected WindWithConfidence> getAveragedWindUnsynchronized(Position p, TimePoint at) { lockForRead(); try { - List>> windFixesToAverage = new ArrayList>>(); + List>> windFixesToAverage = new ArrayList>>(); // don't measure speed with separate confidence; return confidence obtained from averaging bearings - ConfidenceBasedWindAverager> windAverager = ConfidenceFactory.INSTANCE + ConfidenceBasedWindAverager> windAverager = ConfidenceFactory.INSTANCE .createWindAverager(new PositionAndTimePointWeigher( /* halfConfidenceAfterMilliseconds */getMillisecondsOverWhichToAverageWind() / 10)); DummyWind atTimed = new DummyWind(at); - Pair relativeTo = new Pair(p, at); + Util.Pair relativeTo = new Util.Pair(p, at); NavigableSet beforeSet = getInternalFixes().headSet(atTimed, /* inclusive */false); NavigableSet afterSet = getInternalFixes().tailSet(atTimed, /* inclusive */true); Iterator beforeIter = beforeSet.descendingIterator(); @@ -205,8 +211,8 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { } do { if (beforeWind != null && (beforeDistanceToAt <= afterDistanceToAt || afterWind == null)) { - windFixesToAverage.add(new WindWithConfidenceImpl>(beforeWind, - getBaseConfidence(), new Pair(beforeWind.getPosition(), beforeWind + windFixesToAverage.add(new WindWithConfidenceImpl>(beforeWind, + getBaseConfidence(), new Util.Pair(beforeWind.getPosition(), beforeWind .getTimePoint()), useSpeed)); if (beforeIntervalEnd == null) { beforeIntervalEnd = beforeWind.getTimePoint(); @@ -219,8 +225,8 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { beforeWind = null; } } else if (afterWind != null) { - windFixesToAverage.add(new WindWithConfidenceImpl>(afterWind, - getBaseConfidence(), new Pair(afterWind.getPosition(), afterWind + windFixesToAverage.add(new WindWithConfidenceImpl>(afterWind, + getBaseConfidence(), new Util.Pair(afterWind.getPosition(), afterWind .getTimePoint()), useSpeed)); if (afterIntervalStart == null) { afterIntervalStart = afterWind.getTimePoint(); @@ -238,8 +244,7 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { if (windFixesToAverage.isEmpty()) { return null; } else { - WindWithConfidence> average = windAverager.getAverage(windFixesToAverage, - relativeTo); + WindWithConfidence> average = windAverager.getAverage(windFixesToAverage, relativeTo); return average; } } finally { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/util/CourseAsWaypointList.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/CourseAsWaypointList.java index a833e4e4d7a..7b95802b99c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/util/CourseAsWaypointList.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/CourseAsWaypointList.java @@ -9,7 +9,7 @@ import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.impl.MarkImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sse.common.Util; import difflib.Patch; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/util/WeakIdentityHashMap.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/WeakIdentityHashMap.java index 2f43544d5f9..efaf06a3cba 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/util/WeakIdentityHashMap.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/WeakIdentityHashMap.java @@ -7,7 +7,6 @@ package com.sap.sailing.util; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; - import java.util.HashMap; import java.util.Map; diff --git a/java/com.sap.sailing.ess40.resultimport.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.ess40.resultimport.test/META-INF/MANIFEST.MF index c52f09cfd60..90e8497974d 100755 --- a/java/com.sap.sailing.ess40.resultimport.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.ess40.resultimport.test/META-INF/MANIFEST.MF @@ -8,4 +8,5 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Fragment-Host: com.sap.sailing.ess40.resultimport Import-Package: org.osgi.framework;version="1.3.0" Bundle-ActivationPolicy: lazy -Require-Bundle: org.junit4;bundle-version="4.8.2" +Require-Bundle: org.junit4;bundle-version="4.8.2", + com.sap.sse.common diff --git a/java/com.sap.sailing.ess40.resultimport.test/src/com/sap/sailing/ess40/resultimport/test/SimpleESS40ResultImportTest.java b/java/com.sap.sailing.ess40.resultimport.test/src/com/sap/sailing/ess40/resultimport/test/SimpleESS40ResultImportTest.java index 7691ccbfc39..4cbd45a6293 100755 --- a/java/com.sap.sailing.ess40.resultimport.test/src/com/sap/sailing/ess40/resultimport/test/SimpleESS40ResultImportTest.java +++ b/java/com.sap.sailing.ess40.resultimport.test/src/com/sap/sailing/ess40/resultimport/test/SimpleESS40ResultImportTest.java @@ -16,21 +16,21 @@ import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForC import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace; import com.sap.sailing.domain.common.ScoreCorrectionProvider; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.ess40.resultimport.impl.ScoreCorrectionProviderImpl; +import com.sap.sse.common.Util; public class SimpleESS40ResultImportTest { @Test public void testProviderFound() throws Exception { ScoreCorrectionProvider scp = new ScoreCorrectionProviderImpl(); - Map>> hasResult = scp.getHasResultsForBoatClassFromDateByEventName(); + Map>> hasResult = scp.getHasResultsForBoatClassFromDateByEventName(); assertTrue(hasResult.containsKey("qingdao")); } @Test public void testQingdaoResults() throws Exception { ScoreCorrectionProvider scp = new ScoreCorrectionProviderImpl(); - Map>> hasResult = scp.getHasResultsForBoatClassFromDateByEventName(); + Map>> hasResult = scp.getHasResultsForBoatClassFromDateByEventName(); RegattaScoreCorrections qingdaoResults = scp.getScoreCorrections("qingdao", "extreme 40", hasResult.get("qingdao").iterator().next().getB()); assertNotNull(qingdaoResults); for (ScoreCorrectionsForRace scfr : qingdaoResults.getScoreCorrectionsForRaces()) { diff --git a/java/com.sap.sailing.ess40.resultimport/META-INF/MANIFEST.MF b/java/com.sap.sailing.ess40.resultimport/META-INF/MANIFEST.MF index 60f0ad63256..e2e38baeab3 100755 --- a/java/com.sap.sailing.ess40.resultimport/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.ess40.resultimport/META-INF/MANIFEST.MF @@ -9,5 +9,6 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: org.osgi.framework;version="1.3.0" Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sailing.domain, - com.sap.sailing.domain.common + com.sap.sailing.domain.common, + com.sap.sse.common Export-Package: com.sap.sailing.ess40.resultimport diff --git a/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/RegattaScoreCorrectionsImpl.java b/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/RegattaScoreCorrectionsImpl.java index 5478c8b4823..c6f263acec8 100755 --- a/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/RegattaScoreCorrectionsImpl.java +++ b/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/RegattaScoreCorrectionsImpl.java @@ -6,14 +6,14 @@ import java.util.Map; import com.sap.sailing.domain.common.RegattaScoreCorrections; import com.sap.sailing.domain.common.ScoreCorrectionProvider; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public class RegattaScoreCorrectionsImpl implements RegattaScoreCorrections { private final ScoreCorrectionProviderImpl provider; private final Iterable scoreCorrectionsForRaces; public RegattaScoreCorrectionsImpl(ScoreCorrectionProviderImpl scoreCorrectionProviderImpl, - Map>> actResults) { + Map>> actResults) { this.provider = scoreCorrectionProviderImpl; List scfr = new ArrayList(); int maxSize = getMaxListSize(actResults); @@ -23,9 +23,9 @@ public class RegattaScoreCorrectionsImpl implements RegattaScoreCorrections { this.scoreCorrectionsForRaces = scfr; } - private int getMaxListSize(Map>> actResults) { + private int getMaxListSize(Map>> actResults) { int result = 0; - for (Map.Entry>> e : actResults.entrySet()) { + for (Map.Entry>> e : actResults.entrySet()) { result = Math.max(result, e.getValue().size()); } return result; diff --git a/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionForCompetitorInRaceImpl.java b/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionForCompetitorInRaceImpl.java index e9f3b92bc1b..6ce1c32f655 100755 --- a/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionForCompetitorInRaceImpl.java +++ b/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionForCompetitorInRaceImpl.java @@ -4,7 +4,7 @@ import java.util.logging.Logger; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForCompetitorInRace; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public class ScoreCorrectionForCompetitorInRaceImpl implements ScoreCorrectionForCompetitorInRace { private static final Logger logger = Logger.getLogger(ScoreCorrectionForCompetitorInRaceImpl.class.getName()); @@ -13,7 +13,7 @@ public class ScoreCorrectionForCompetitorInRaceImpl implements ScoreCorrectionFo private final Double points; private final MaxPointsReason maxPointsReason; - public ScoreCorrectionForCompetitorInRaceImpl(String teamName, Pair rankAndPoints) { + public ScoreCorrectionForCompetitorInRaceImpl(String teamName, Util.Pair rankAndPoints) { this.teamName = teamName; if (rankAndPoints == null) { points = 0.0; diff --git a/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionForRaceImpl.java b/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionForRaceImpl.java index 9b693cf4e15..d44f7e8e192 100755 --- a/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionForRaceImpl.java +++ b/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionForRaceImpl.java @@ -7,18 +7,18 @@ import java.util.Set; import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForCompetitorInRace; import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public class ScoreCorrectionForRaceImpl implements ScoreCorrectionsForRace { private final int raceNumberStartingWithOne; private final Map scoreCorrectionsByTeamName; - public ScoreCorrectionForRaceImpl(Map>> actResults, int raceNumberStartingWithZero) { + public ScoreCorrectionForRaceImpl(Map>> actResults, int raceNumberStartingWithZero) { this.raceNumberStartingWithOne = raceNumberStartingWithZero+1; this.scoreCorrectionsByTeamName = new HashMap(); - for (Map.Entry>> e : actResults.entrySet()) { + for (Map.Entry>> e : actResults.entrySet()) { String teamName = e.getKey(); - Pair rankAndPoints; + Util.Pair rankAndPoints; if (raceNumberStartingWithZero < e.getValue().size()) { rankAndPoints = e.getValue().get(raceNumberStartingWithZero); } else { diff --git a/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionProviderImpl.java b/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionProviderImpl.java index 96d647e5bc0..d3202a1614b 100755 --- a/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionProviderImpl.java +++ b/java/com.sap.sailing.ess40.resultimport/src/com/sap/sailing/ess40/resultimport/impl/ScoreCorrectionProviderImpl.java @@ -21,7 +21,7 @@ import com.sap.sailing.domain.common.RegattaScoreCorrections; import com.sap.sailing.domain.common.ScoreCorrectionProvider; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public class ScoreCorrectionProviderImpl implements ScoreCorrectionProvider { private static final long serialVersionUID = -4870646572106575667L; @@ -45,15 +45,15 @@ public class ScoreCorrectionProviderImpl implements ScoreCorrectionProvider { * @return A pair whose first component is the time point of the last modification to the act results, the second * element is a map whose keys are the sail IDs (in the Extreme Sailing Series we'll use the team names as * the sail IDs, such as "SAP Extreme Sailing Team" or just "SAP" for short); values with be a list - * representing the act's races, from first to last, where each {@link Pair} holds in its first component a + * representing the act's races, from first to last, where each {@link Util.Pair} holds in its first component a * string describing the rank, which could be an integer number formatted as a string, or a three-letter * disqualification reason such as "DNC", "DNF" or "DNS"; the second component is the points the competitor * scored in that race. Usually, if the first component is a number, the score can be expected to be * #competitors+1 - rank. A disqualification gets 0 points. */ - private Pair>>> getActResults(URL actUrl) throws IOException { + private Util.Pair>>> getActResults(URL actUrl) throws IOException { Pattern quotedCompetitorNameAndAllTheRest = Pattern.compile("^\"([^\"]*)\",(.*)$"); - Map>> result = new HashMap>>(); + Map>> result = new HashMap>>(); HttpURLConnection conn = (HttpURLConnection) actUrl.openConnection(); authenticate(conn); TimePoint lastModified = new MillisecondsTimePoint(conn.getLastModified()); @@ -75,16 +75,16 @@ public class ScoreCorrectionProviderImpl implements ScoreCorrectionProvider { split = new String[preSplit.length-1]; System.arraycopy(preSplit, 1, split, 0, split.length); } - List> competitorEntry = new ArrayList>(); + List> competitorEntry = new ArrayList>(); result.put(sailID, competitorEntry); for (int i=0; i(rankOrMaxPointsReason, points)); + competitorEntry.add(new Util.Pair(rankOrMaxPointsReason, points)); } line = br.readLine(); } - return new Pair>>>(lastModified, result); + return new Util.Pair>>>(lastModified, result); } private void authenticate(HttpURLConnection conn) { @@ -93,12 +93,12 @@ public class ScoreCorrectionProviderImpl implements ScoreCorrectionProvider { } @Override - public Map>> getHasResultsForBoatClassFromDateByEventName() throws Exception { - Map>> result = new HashMap>>(); + public Map>> getHasResultsForBoatClassFromDateByEventName() throws Exception { + Map>> result = new HashMap>>(); for (String actName : getAvailableActNames()) { URL actUrl = getCsvUrls(actName).iterator().next(); - Pair>>> actResults = getActResults(actUrl); - result.put(actName, Collections.singleton(new Pair(EXTREME_40_CLASS_NAME, actResults.getA()))); + Util.Pair>>> actResults = getActResults(actUrl); + result.put(actName, Collections.singleton(new Util.Pair(EXTREME_40_CLASS_NAME, actResults.getA()))); } return result; } @@ -124,7 +124,7 @@ public class ScoreCorrectionProviderImpl implements ScoreCorrectionProvider { public RegattaScoreCorrections getScoreCorrections(String actName, String boatClassName, TimePoint timePoint) throws Exception { URL actUrl = getCsvUrls(actName).iterator().next(); - Pair>>> actResults = getActResults(actUrl); + Util.Pair>>> actResults = getActResults(actUrl); return new RegattaScoreCorrectionsImpl(this, actResults.getB()); } diff --git a/java/com.sap.sailing.expeditionconnector.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.expeditionconnector.test/META-INF/MANIFEST.MF index ebd61fb4e94..76109425bd0 100755 --- a/java/com.sap.sailing.expeditionconnector.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.expeditionconnector.test/META-INF/MANIFEST.MF @@ -9,4 +9,5 @@ Fragment-Host: com.sap.sailing.expeditionconnector Require-Bundle: com.sap.sailing.server, com.sap.sailing.domain.test, com.sap.sailing.udpconnector, - org.junit4;bundle-version="4.8.2" + org.junit4;bundle-version="4.8.2", + com.sap.sse.common diff --git a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java index 2a3c53211fd..0341aecbe52 100755 --- a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java +++ b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java @@ -35,7 +35,6 @@ import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.test.mock.MockedTrackedRace; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.Wind; @@ -44,6 +43,7 @@ import com.sap.sailing.expeditionconnector.ExpeditionMessage; import com.sap.sailing.expeditionconnector.ExpeditionWindTracker; import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory; import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver; +import com.sap.sse.common.Util; public class UDPExpeditionReceiverTest { @Rule public Timeout TestTimeout = new Timeout(60 * 1000); diff --git a/java/com.sap.sailing.expeditionconnector/META-INF/MANIFEST.MF b/java/com.sap.sailing.expeditionconnector/META-INF/MANIFEST.MF index c09e3b27727..c7b71d71f64 100755 --- a/java/com.sap.sailing.expeditionconnector/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.expeditionconnector/META-INF/MANIFEST.MF @@ -10,7 +10,8 @@ Export-Package: com.sap.sailing.expeditionconnector, Require-Bundle: com.sap.sailing.domain, com.sap.sailing.declination, com.sap.sailing.udpconnector, - com.sap.sailing.domain.common + com.sap.sailing.domain.common, + com.sap.sse.common Bundle-ActivationPolicy: lazy Import-Package: org.osgi.framework Bundle-Activator: com.sap.sailing.expeditionconnector.impl.Activator diff --git a/java/com.sap.sailing.feature.p2build/raceanalysis.product b/java/com.sap.sailing.feature.p2build/raceanalysis.product index c2f37c5c3a5..22a418cdfc9 100755 --- a/java/com.sap.sailing.feature.p2build/raceanalysis.product +++ b/java/com.sap.sailing.feature.p2build/raceanalysis.product @@ -1,7 +1,7 @@ - + @@ -32,7 +32,9 @@ + + diff --git a/java/com.sap.sailing.feature/feature.xml b/java/com.sap.sailing.feature/feature.xml index d596429a6f6..c30427742a3 100755 --- a/java/com.sap.sailing.feature/feature.xml +++ b/java/com.sap.sailing.feature/feature.xml @@ -290,4 +290,18 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al version="0.0.0" unpack="false"/> + + + + diff --git a/java/com.sap.sailing.freg.resultimport.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.freg.resultimport.test/META-INF/MANIFEST.MF index 2eaeaaf8d60..d1d4c28a786 100755 --- a/java/com.sap.sailing.freg.resultimport.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.freg.resultimport.test/META-INF/MANIFEST.MF @@ -8,4 +8,5 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Fragment-Host: com.sap.sailing.freg.resultimport Import-Package: org.osgi.framework;version="1.3.0" Bundle-ActivationPolicy: lazy -Require-Bundle: org.junit4;bundle-version="4.8.2" +Require-Bundle: org.junit4;bundle-version="4.8.2", + com.sap.sse.common diff --git a/java/com.sap.sailing.freg.resultimport.test/src/com/sap/sailing/freg/resultimport/test/SimpleHtmlParsingTest.java b/java/com.sap.sailing.freg.resultimport.test/src/com/sap/sailing/freg/resultimport/test/SimpleHtmlParsingTest.java index d83aa3d177a..0fb01362420 100755 --- a/java/com.sap.sailing.freg.resultimport.test/src/com/sap/sailing/freg/resultimport/test/SimpleHtmlParsingTest.java +++ b/java/com.sap.sailing.freg.resultimport.test/src/com/sap/sailing/freg/resultimport/test/SimpleHtmlParsingTest.java @@ -16,10 +16,10 @@ import java.util.regex.Pattern; import org.junit.Test; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.resultimport.CompetitorRow; import com.sap.sailing.resultimport.RegattaResults; import com.sap.sailing.freg.resultimport.impl.FregHtmlParser; +import com.sap.sse.common.Util; public class SimpleHtmlParsingTest { @Test diff --git a/java/com.sap.sailing.freg.resultimport/META-INF/MANIFEST.MF b/java/com.sap.sailing.freg.resultimport/META-INF/MANIFEST.MF index 5031590c631..e5a16da45d5 100755 --- a/java/com.sap.sailing.freg.resultimport/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.freg.resultimport/META-INF/MANIFEST.MF @@ -11,4 +11,5 @@ Import-Package: com.sap.sailing.resultimport, org.osgi.framework;version="1.3.0" Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sailing.domain.common, - com.sap.sailing.domain + com.sap.sailing.domain, + com.sap.sse.common diff --git a/java/com.sap.sailing.freg.resultimport/src/com/sap/sailing/freg/resultimport/impl/ScoreCorrectionProviderImpl.java b/java/com.sap.sailing.freg.resultimport/src/com/sap/sailing/freg/resultimport/impl/ScoreCorrectionProviderImpl.java index 36a04c31f29..f62f98967fb 100755 --- a/java/com.sap.sailing.freg.resultimport/src/com/sap/sailing/freg/resultimport/impl/ScoreCorrectionProviderImpl.java +++ b/java/com.sap.sailing.freg.resultimport/src/com/sap/sailing/freg/resultimport/impl/ScoreCorrectionProviderImpl.java @@ -13,11 +13,11 @@ import com.sap.sailing.domain.common.RegattaScoreCorrections; import com.sap.sailing.domain.common.ScoreCorrectionProvider; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.resultimport.RegattaResults; import com.sap.sailing.resultimport.ResultUrlProvider; import com.sap.sailing.resultimport.ResultUrlRegistry; import com.sap.sailing.resultimport.impl.RegattaScoreCorrectionsImpl; +import com.sap.sse.common.Util; public class ScoreCorrectionProviderImpl implements ScoreCorrectionProvider, ResultUrlProvider { private static final long serialVersionUID = 5853404150107387702L; @@ -35,8 +35,8 @@ public class ScoreCorrectionProviderImpl implements ScoreCorrectionProvider, Res } @Override - public Map>> getHasResultsForBoatClassFromDateByEventName() { - Map>> result = new HashMap>>(); + public Map>> getHasResultsForBoatClassFromDateByEventName() { + Map>> result = new HashMap>>(); FregHtmlParser parser = new FregHtmlParser(); for (URL url : getUrls()) { URLConnection conn; @@ -45,7 +45,7 @@ public class ScoreCorrectionProviderImpl implements ScoreCorrectionProvider, Res TimePoint lastModified = new MillisecondsTimePoint(conn.getLastModified()); RegattaResults regattaResult = parser.getRegattaResults((InputStream) conn.getContent()); final String boatClassName = getBoatClassName(regattaResult); - result.put(boatClassName, Collections.singleton(new Pair(boatClassName, lastModified))); + result.put(boatClassName, Collections.singleton(new Util.Pair(boatClassName, lastModified))); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/java/com.sap.sailing.geocoding/META-INF/MANIFEST.MF b/java/com.sap.sailing.geocoding/META-INF/MANIFEST.MF index 6bbb017622d..99298bdc409 100644 --- a/java/com.sap.sailing.geocoding/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.geocoding/META-INF/MANIFEST.MF @@ -8,4 +8,5 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ActivationPolicy: lazy Export-Package: com.sap.sailing.geocoding Require-Bundle: org.json.simple;bundle-version="1.1.0", - com.sap.sailing.domain.common + com.sap.sailing.domain.common, + com.sap.sse.common diff --git a/java/com.sap.sailing.geocoding/src/com/sap/sailing/geocoding/impl/ReverseGeocoderImpl.java b/java/com.sap.sailing.geocoding/src/com/sap/sailing/geocoding/impl/ReverseGeocoderImpl.java index a3a8ecec0c0..577e4d1fe9a 100644 --- a/java/com.sap.sailing.geocoding/src/com/sap/sailing/geocoding/impl/ReverseGeocoderImpl.java +++ b/java/com.sap.sailing.geocoding/src/com/sap/sailing/geocoding/impl/ReverseGeocoderImpl.java @@ -24,9 +24,9 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.SerializablePosition; import com.sap.sailing.domain.common.impl.PlacemarkImpl; import com.sap.sailing.domain.common.impl.SerializablePositionImpl; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.quadtree.QuadTree; import com.sap.sailing.geocoding.ReverseGeocoder; +import com.sap.sse.common.Util; public class ReverseGeocoderImpl implements ReverseGeocoder { @@ -43,12 +43,12 @@ public class ReverseGeocoderImpl implements ReverseGeocoder { private final int MAX_ROW_NUMBER = 1000; private final int MAX_RADIUS = 300; - private QuadTree>> cache = new QuadTree>>();; + private QuadTree>> cache = new QuadTree>>();; @Override public Placemark getPlacemarkNearest(Position position) throws IOException, ParseException { Placemark p = null; - Triple> cachedPlacemarks = checkCache(position); + Util.Triple> cachedPlacemarks = checkCache(position); if (cachedPlacemarks != null && cachedPlacemarks.getC() != null && !cachedPlacemarks.getC().isEmpty()) { p = cachedPlacemarks.getC().get(0); @@ -71,7 +71,7 @@ public class ReverseGeocoderImpl implements ReverseGeocoder { @Override public List getPlacemarksNear(Position position, double radius) throws IOException, ParseException { List placemarks = null; - Triple> cachedPlacemarks = checkCache(position); + Util.Triple> cachedPlacemarks = checkCache(position); //Calculating the search radius and the maximum number of returning Placemarks double limitedRadius = Math.min(radius, MAX_RADIUS); @@ -198,7 +198,7 @@ public class ReverseGeocoderImpl implements ReverseGeocoder { Collections.sort(placemarks, new Placemark.ByDistance(position)); if (position != null) { synchronized (cache) { - cache.put(position, new Triple>(position, radius, placemarks)); + cache.put(position, new Util.Triple>(position, radius, placemarks)); } } } @@ -217,7 +217,7 @@ public class ReverseGeocoderImpl implements ReverseGeocoder { private void updateCachedPlacemarks(Position cachedPoint, Double newRadius, List newPlacemarks) { if (cachedPoint != null) { synchronized (cache) { - cache.replace(cachedPoint, new Triple>(cachedPoint, newRadius, newPlacemarks)); + cache.replace(cachedPoint, new Util.Triple>(cachedPoint, newRadius, newPlacemarks)); } } } @@ -229,7 +229,7 @@ public class ReverseGeocoderImpl implements ReverseGeocoder { * null, if there's nothing cached around position within * {@link ReverseGeocoderImpl#POSITION_CACHE_DISTANCE_LIMIT the distance limit} */ - private Triple> checkCache(Position position) { + private Util.Triple> checkCache(Position position) { synchronized (cache) { return cache.get(position, POSITION_CACHE_DISTANCE_LIMIT); } diff --git a/java/com.sap.sailing.gwt.ui.test/.classpath b/java/com.sap.sailing.gwt.ui.test/.classpath index 2d7fd14bc00..5cc60e2c543 100755 --- a/java/com.sap.sailing.gwt.ui.test/.classpath +++ b/java/com.sap.sailing.gwt.ui.test/.classpath @@ -1,10 +1,10 @@ - + diff --git a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF index cc2929cfa54..bad1c3b6fe0 100755 --- a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF @@ -4,15 +4,18 @@ Bundle-Name: Test Bundle-SymbolicName: com.sap.sailing.gwt.ui.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP -Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Fragment-Host: com.sap.sailing.gwt.ui Require-Bundle: com.sap.sailing.server, - org.hamcrest.core;bundle-version="1.1.0", com.sap.sailing.expeditionconnector, org.mockito.mockito-core;bundle-version="1.9.5", - org.objenesis;bundle-version="1.3.0" + org.objenesis;bundle-version="1.3.0", + org.hamcrest;bundle-version="1.1.0", + com.sap.sailing.domain.igtimiadapter.persistence;bundle-version="1.0.0", + com.sap.sailing.domain.racelogtrackingadapter.testsupport;bundle-version="1.0.0" Bundle-ClassPath: . -Import-Package: javax.jms;version="1.1.0", +Import-Package: com.sap.sailing.domain.racelogtracking.impl, + javax.jms;version="1.1.0", javax.servlet;version="2.6.0", javax.servlet.http;version="2.6.0", junit.framework;version="4.8.1", diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/AbstractListFilterTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/AbstractListFilterTest.java index eef0659e9e7..10f0f64f74e 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/AbstractListFilterTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/AbstractListFilterTest.java @@ -1,15 +1,17 @@ package com.sap.sailing.gwt.ui.test; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import org.junit.Before; import org.junit.Test; import com.sap.sailing.domain.base.impl.BoatImpl; -import com.sap.sailing.gwt.ui.client.shared.filter.AbstractListFilter; +import com.sap.sse.common.Util; +import com.sap.sse.common.filter.AbstractListFilter; public class AbstractListFilterTest { @@ -33,12 +35,14 @@ public class AbstractListFilterTest { @Test public void test() { - assertTrue(als.applyFilter("Race 2", all).size() == 2); - assertTrue(als.applyFilter("3", all).size() == 1 && als.applyFilter("3", all).get(0).getName() == "Race 3"); - assertTrue(als.applyFilter("1 Wannsee", all).size() == 1 && als.applyFilter("1 Wannsee", all).get(0).getName() == "Race 1"); - assertTrue(als.applyFilter("Wannsee 3", all).size() == 0); - assertTrue(als.applyFilter("Race Race", all).size() == 5); - assertTrue(als.applyFilter("R", all).size() == 5); - assertTrue(als.applyFilter("i", all).size() == 4); + assertEquals(2, Util.size(als.applyFilter(Arrays.asList(new String[] { "Race", "2" }), all))); + assertEquals(1, Util.size(als.applyFilter(Collections.singleton("3"), all))); + assertEquals("Race 3", als.applyFilter(Collections.singleton("3"), all).iterator().next().getName()); + assertEquals(1, Util.size(als.applyFilter(Arrays.asList(new String[] { "1", "Wannsee" }), all))); + assertEquals("Race 1", als.applyFilter(Arrays.asList(new String[] { "1", "Wannsee" }), all).iterator().next().getName()); + assertEquals(0, Util.size(als.applyFilter(Arrays.asList(new String[] { "Wannsee", "3" }), all))); + assertEquals(5, Util.size(als.applyFilter(Arrays.asList(new String[] { "Race", "Race" }), all))); + assertEquals(5, Util.size(als.applyFilter(Collections.singleton("R"), all))); + assertEquals(4, Util.size(als.applyFilter(Collections.singleton("i"), all))); } } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java index 697c347bc3e..42afd4c8605 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java @@ -18,12 +18,12 @@ import com.sap.sailing.domain.common.dto.IncrementalOrFullLeaderboardDTO; import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.ExpandableSortableColumn; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO; +import com.sap.sse.common.Util; public class GwtTestCaseColumnToggling extends GWTTestCase { @@ -65,7 +65,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { } private void listRacesInEvent(){ - service.listTracTracRacesInEvent(JSON_URL, /* hidden */ true, new AsyncCallback>>() { + service.listTracTracRacesInEvent(JSON_URL, /* hidden */ true, new AsyncCallback>>() { @Override public void onFailure(Throwable caught) { @@ -73,7 +73,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { } @Override - public void onSuccess(Pair> result) { + public void onSuccess(Util.Pair> result) { System.out.println("Listed races."); for (TracTracRaceRecordDTO rr : result.getB()){ if (rr.name.equals(TRACKED_RACE)){ @@ -178,7 +178,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { private void getLeaderboardByNameAndDeleteColumn(){ ArrayList al = new ArrayList(); al.add(COLUMN1_NAME); - service.getLeaderboardByName(LEADERBOARD_NAME, new Date(), al, /* previousLeaderboardId */ null, new AsyncCallback() { + service.getLeaderboardByName(LEADERBOARD_NAME, new Date(), al, /* addOverallDetails */ false, /* previousLeaderboardId */ null, new AsyncCallback() { @Override public void onFailure(Throwable caught) { fail("Failed to get leaderboard."); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LineSegmentTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LineSegmentTest.java index 11b90dd4b07..8ae2a248e10 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LineSegmentTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LineSegmentTest.java @@ -1,6 +1,6 @@ package com.sap.sailing.gwt.ui.test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index 4e55a75dbd4..76342bbc535 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -31,9 +31,9 @@ import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.tracking.CourseDesignChangedListener; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; @@ -56,6 +56,7 @@ import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.domain.tracking.impl.WindTrackImpl; +import com.sap.sse.common.Util; public class MockedTrackedRace implements DynamicTrackedRace { private static final long serialVersionUID = 5827912985564121181L; @@ -220,7 +221,8 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void addListener(RaceChangeListener listener, boolean notifyAboutWindFixesAlreadyLoaded) { + public void addListener(RaceChangeListener listener, boolean notifyAboutWindFixesAlreadyLoaded, + boolean notifyAboutGPSFixesAlreadyLoaded) { // TODO Auto-generated method stub } @@ -518,7 +520,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, - long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, + GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { // TODO Auto-generated method stub return null; @@ -617,7 +619,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, Iterable windSourcesToExclude) { // TODO Auto-generated method stub return null; @@ -630,7 +632,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { // TODO Auto-generated method stub return null; } @@ -666,13 +668,13 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Iterable>> getMarkPassingsTimes() { + public Iterable>> getMarkPassingsTimes() { // TODO Auto-generated method stub return null; } @Override - public Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { // TODO Auto-generated method stub return null; } @@ -737,14 +739,14 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Distance getAverageCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatestAnalyses) + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, boolean waitForLatestAnalyses) throws NoWindException { // TODO Auto-generated method stub return null; } @Override - public void waitUntilWindLoadingComplete() throws InterruptedException { + public void waitUntilLoadingFromWindStoreComplete() throws InterruptedException { // TODO Auto-generated method stub } @@ -931,6 +933,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } + @Override + public GPSFixStore getGPSFixStore() { + // TODO Auto-generated method stub + return null; + } + @Override public Bearing getCrossingBearing(Waypoint w, TimePoint t) { // TODO Auto-generated method stub @@ -938,9 +946,29 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { + public Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { // TODO Auto-generated method stub return null; } + @Override + public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalysis) throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, + boolean upwindOnly, boolean waitForLatestAnalysis) throws NoWindException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void waitUntilLoadingFromGPSFixStoreComplete() throws InterruptedException { + // TODO Auto-generated method stub + + } + } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRaceLogTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRaceLogTest.java new file mode 100644 index 00000000000..0cd398f06cf --- /dev/null +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRaceLogTest.java @@ -0,0 +1,46 @@ +package com.sap.sailing.gwt.ui.test; + +import java.util.Collections; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.dto.PositionDTO; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.HighPoint; +import com.sap.sailing.gwt.ui.shared.MarkDTO; +import com.sap.sailing.server.RacingEventService; + +public class PingMarkViaRaceLogTest { + private SailingServiceImplMock sailingService; + private RacingEventService service; + private final String columnName = "column"; + private final Fleet fleet = new FleetImpl("fleet"); + + @Before + public void prepare() { + sailingService = new SailingServiceImplMock(); + service = sailingService.getRacingEventService(); + } + + @Test + public void testPinging() { + service.getMongoObjectFactory().getDatabase().dropDatabase(); + Series series = new SeriesImpl("series", false, Collections.singletonList(fleet), + Collections.singletonList(columnName), service); + Regatta regatta = service.createRegatta("regatta", "Laser", UUID.randomUUID(), Collections.singletonList(series), + false, new HighPoint(), UUID.randomUUID()); + RegattaLeaderboard leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); + + MarkDTO mark = new MarkDTO("mark", "mark"); + PositionDTO position = new PositionDTO(30, 40); + + sailingService.pingMarkViaRaceLogTracking(leaderboard.getName(), columnName, fleet.getName(), mark, position); + } +} diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java index d3150d3d5b2..1d81c766219 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java @@ -37,8 +37,6 @@ import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; @@ -139,14 +137,14 @@ public class PolarSheetGenerationServiceTest { } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { - return new WindWithConfidenceImpl>(new WindImpl(p, at, + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at) { + return new WindWithConfidenceImpl>(new WindImpl(p, at, new KnotSpeedWithBearingImpl(2.0, new DegreeBearingImpl(180.0))), 0.9, - new Pair(p, at), true); + new com.sap.sse.common.Util.Pair(p, at), true); } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, Iterable windSourcesToExclude) { return getWindWithConfidence(p, at); } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SafeHtmlHeaderWithTooltipTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SafeHtmlHeaderWithTooltipTest.java index 40609bb9e49..6f9bfb42580 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SafeHtmlHeaderWithTooltipTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SafeHtmlHeaderWithTooltipTest.java @@ -1,6 +1,6 @@ package com.sap.sailing.gwt.ui.test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java index 5f66b11461a..b96966f39d1 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java @@ -1,158 +1,192 @@ -package com.sap.sailing.gwt.ui.test; - -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.net.UnknownHostException; -import java.text.ParseException; -import java.util.List; - -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.common.RegattaIdentifier; -import com.sap.sailing.domain.common.ScoreCorrectionProvider; -import com.sap.sailing.domain.racelog.RaceLogStore; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; -import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayService; -import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayServiceFactory; -import com.sap.sailing.domain.tracking.RacesHandle; -import com.sap.sailing.domain.tracking.TrackerManager; -import com.sap.sailing.domain.tractracadapter.TracTracAdapterFactory; -import com.sap.sailing.gwt.ui.server.SailingServiceImpl; -import com.sap.sailing.server.RacingEventService; -import com.sap.sailing.server.impl.RacingEventServiceImpl; -import com.sap.sailing.server.replication.ReplicationService; -import com.sap.sailing.server.replication.impl.ReplicationInstancesManager; -import com.sap.sailing.server.replication.impl.ReplicationServiceImpl; - -public class SailingServiceImplMock extends SailingServiceImpl { - private static final long serialVersionUID = 8564037671550730455L; - private RacingEventService service; - - public SailingServiceImplMock() { - super(); - service = new RacingEventServiceImpl(); - } - - @Override - protected ServiceTracker createAndOpenRacingEventServiceTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - when(result.getService()).thenReturn(new RacingEventServiceImpl()); - return result; - } - - @Override - protected ServiceTracker createAndOpenReplicationServiceTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - try { - final ReplicationServiceImpl replicationService = new ReplicationServiceImpl("test exchange", "localhost", new ReplicationInstancesManager()) { - @Override - protected ServiceTracker getRacingEventServiceTracker() { - @SuppressWarnings("unchecked") - ServiceTracker result = (ServiceTracker) mock(ServiceTracker.class); - doReturn("Humba Humba").when(result).toString(); - when(result.getService()).thenReturn(new RacingEventServiceImpl()); - return result; - } - }; - when(result.getService()).thenReturn(replicationService); - } catch (IOException e) { - throw new RuntimeException(); - } - return result; - } - - @Override - protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( - BundleContext bundleContext) { - return null; - } - - @Override - protected ServiceTracker createAndOpenTracTracAdapterTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - when(result.getService()).thenReturn(TracTracAdapterFactory.INSTANCE); - return result; - } - - @Override - protected ServiceTracker createAndOpenSwissTimingAdapterTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - when(result.getService()).thenReturn(new SwissTimingAdapterFactory() { - @Override - public SwissTimingAdapter getOrCreateSwissTimingAdapter(DomainFactory baseDomainFactory) { - return new SwissTimingAdapter() { - @Override - public List getSwissTimingRaceRecords( - String hostname, int port) throws InterruptedException, - UnknownHostException, IOException, ParseException { - // TODO Auto-generated method stub - return null; - } - - @Override - public RacesHandle addSwissTimingRace(TrackerManager trackerManager, - RegattaIdentifier regattaToAddTo, String raceID, String raceDescription, BoatClass boatClass, String hostname, - int port, RaceLogStore logStore, long timeoutInMilliseconds) - throws InterruptedException, UnknownHostException, IOException, ParseException, - Exception { - // TODO Auto-generated method stub - return null; - } - - @Override - public SwissTimingFactory getSwissTimingFactory() { - // TODO Auto-generated method stub - return null; - } - - @Override - public com.sap.sailing.domain.swisstimingadapter.DomainFactory getSwissTimingDomainFactory() { - return com.sap.sailing.domain.swisstimingadapter.DomainFactory.INSTANCE; - } - }; - } - }); - return result; - } - - @Override - protected ServiceTracker createAndOpenSwissTimingReplayServiceTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - final SwissTimingReplayServiceFactory swissTimingReplayService = new SwissTimingReplayServiceFactory() { - @Override - public SwissTimingReplayService createSwissTimingReplayService( - com.sap.sailing.domain.swisstimingadapter.DomainFactory domainFactory) { - // TODO Auto-generated method stub - return null; - } - }; - when(result.getService()).thenReturn(swissTimingReplayService); - return result; - } - - @Override - protected RacingEventService getService() { - if (service == null) { - service = new RacingEventServiceImpl(); - } - return service; - } -} +package com.sap.sailing.gwt.ui.test; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.text.ParseException; +import java.util.List; + +import javax.xml.bind.JAXBException; + +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.common.RegattaIdentifier; +import com.sap.sailing.domain.common.ScoreCorrectionProvider; +import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; +import com.sap.sailing.domain.racelogtracking.impl.RaceLogTrackingAdapterFactoryImpl; +import com.sap.sailing.domain.swisstimingadapter.StartList; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; +import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayService; +import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayServiceFactory; +import com.sap.sailing.domain.tracking.RacesHandle; +import com.sap.sailing.domain.tracking.TrackerManager; +import com.sap.sailing.domain.tractracadapter.TracTracAdapterFactory; +import com.sap.sailing.gwt.ui.server.SailingServiceImpl; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sailing.server.replication.ReplicationService; +import com.sap.sailing.server.replication.impl.ReplicationInstancesManager; +import com.sap.sailing.server.replication.impl.ReplicationServiceImpl; +import com.sap.sailing.xrr.resultimport.schema.RegattaResults; + +public class SailingServiceImplMock extends SailingServiceImpl { + private static final long serialVersionUID = 8564037671550730455L; + private RacingEventService service; + + public SailingServiceImplMock() { + super(); + service = new RacingEventServiceImpl(true, new MockSmartphoneImeiServiceFinderFactory()); + } + + @Override + protected ServiceTracker createAndOpenRacingEventServiceTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + when(result.getService()).thenReturn(new RacingEventServiceImpl()); + return result; + } + + @Override + protected ServiceTracker createAndOpenReplicationServiceTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + try { + final ReplicationServiceImpl replicationService = new ReplicationServiceImpl("test exchange", "localhost", new ReplicationInstancesManager()) { + @Override + protected ServiceTracker getRacingEventServiceTracker() { + @SuppressWarnings("unchecked") + ServiceTracker result = (ServiceTracker) mock(ServiceTracker.class); + doReturn("Humba Humba").when(result).toString(); + when(result.getService()).thenReturn(new RacingEventServiceImpl()); + return result; + } + }; + when(result.getService()).thenReturn(replicationService); + } catch (IOException e) { + throw new RuntimeException(); + } + return result; + } + + @Override + protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( + BundleContext bundleContext) { + return null; + } + + @Override + protected ServiceTracker createAndOpenTracTracAdapterTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + when(result.getService()).thenReturn(TracTracAdapterFactory.INSTANCE); + return result; + } + + @Override + protected ServiceTracker createAndOpenSwissTimingAdapterTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + when(result.getService()).thenReturn(new SwissTimingAdapterFactory() { + @Override + public SwissTimingAdapter getOrCreateSwissTimingAdapter(DomainFactory baseDomainFactory) { + return new SwissTimingAdapter() { + @Override + public List getSwissTimingRaceRecords( + String hostname, int port) throws InterruptedException, + UnknownHostException, IOException, ParseException { + // TODO Auto-generated method stub + return null; + } + + @Override + public RacesHandle addSwissTimingRace(TrackerManager trackerManager, + RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, + int port, StartList startList, RaceLogStore logStore, long timeoutInMilliseconds) + throws InterruptedException, UnknownHostException, IOException, ParseException, + Exception { + // TODO Auto-generated method stub + return null; + } + + @Override + public SwissTimingFactory getSwissTimingFactory() { + // TODO Auto-generated method stub + return null; + } + + @Override + public com.sap.sailing.domain.swisstimingadapter.DomainFactory getSwissTimingDomainFactory() { + return com.sap.sailing.domain.swisstimingadapter.DomainFactory.INSTANCE; + } + + @Override + public StartList readStartListForRace(String raceId, RegattaResults regattaResults) { + // TODO Auto-generated method stub + return null; + } + + @Override + public RegattaResults readRegattaEntryListFromXrrUrl(String xrrEntryListUrl) throws IOException, + JAXBException { + // TODO Auto-generated method stub + return null; + } + }; + } + }); + return result; + } + + @Override + protected ServiceTracker createAndOpenSwissTimingReplayServiceTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + final SwissTimingReplayServiceFactory swissTimingReplayService = new SwissTimingReplayServiceFactory() { + @Override + public SwissTimingReplayService createSwissTimingReplayService( + com.sap.sailing.domain.swisstimingadapter.DomainFactory domainFactory) { + // TODO Auto-generated method stub + return null; + } + }; + when(result.getService()).thenReturn(swissTimingReplayService); + return result; + } + + @Override + protected ServiceTracker createAndOpenRaceLogTrackingAdapterTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + RaceLogTrackingAdapterFactory factory = RaceLogTrackingAdapterFactoryImpl.INSTANCE; + when(result.getService()).thenReturn(factory); + return result; + } + + @Override + protected RacingEventService getService() { + if (service == null) { + service = new RacingEventServiceImpl(); + } + return service; + } + + public RacingEventService getRacingEventService() { + return getService(); + } +} 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 0294a67fd0b..f1ca83db134 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 @@ -60,7 +60,7 @@ public class TestColumnSwapping { public String generateRandomUUID() { return UUID.randomUUID().toString(); } - }); + }, /* hasOverallDetails */ false); leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., DEFAULT_FLEET, true, null, null); leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., DEFAULT_FLEET, true, null, null); leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., DEFAULT_FLEET, true, null, null); @@ -98,7 +98,7 @@ public class TestColumnSwapping { 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, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null); + lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null); } catch (Exception e) { // e.printStackTrace(); fail(e.getLocalizedMessage()); @@ -123,7 +123,7 @@ public class TestColumnSwapping { public String generateRandomUUID() { return UUID.randomUUID().toString(); } - }); + }, /* hasOverallDetails */ false); assertNotNull("Leaderboard != NULL", lb); lb.addRace("1", /* explicitFactor */ null, 1., DEFAULT_FLEET, false, null, null); lb.addRace("2", /* explicitFactor */ null, 1., DEFAULT_FLEET, false, null, null); @@ -139,7 +139,7 @@ public class TestColumnSwapping { public void testColumnSwappingFabian() { service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3"); try { - leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null); + leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */ true, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestWindChartPointRecalculation.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestWindChartPointRecalculation.java index c39d660cbbd..10de1427378 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestWindChartPointRecalculation.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestWindChartPointRecalculation.java @@ -1,66 +1,119 @@ package com.sap.sailing.gwt.ui.test; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static org.hamcrest.core.IsEqual.*; - import org.junit.Test; import org.moxieapps.gwt.highcharts.client.Point; -import com.sap.sailing.gwt.ui.client.shared.charts.WindChartPointRecalculator; +import com.sap.sailing.gwt.ui.client.shared.charts.ChartPointRecalculator; public class TestWindChartPointRecalculation { + + private static final double POINT_Y_EQUALITY_EPSILON = 0.005; + + private static final List MULTIPLE_WRAP_AROUNDS = Arrays.asList(356.74, 10.54, 30.192, 20.625, 5.647, 350.526); + private static final List EXPECTED_RECALCULATED_VALUES_FOR_MULTIPLE_WRAP_AROUNDS = Arrays.asList(356.74, 10.54 + 360, 30.192 + 360, 20.625 + 360, 5.647 + 360, 350.526); + + private static final List CONTINOUS_WRAP_AROUNDS = Arrays.asList(5.34, 358.97, 10.42, 50.0, 110.65, 140.54, + 210.85, 280.38, 330.5, 358.97, 7.24, 48.0, 112.65, 135.54, 215.85, 278.38, 333.5, 356.97, 8.24, 48.245, + 112.57, 133.54, 215.637, 278.38, 333.5, 356.97); + private static final List EXPECTED_RECALCULATED_VALUES_FOR_CONTINOUS_WRAP_AROUNDS_OVERALL_DELTA_MINIMUM = Arrays.asList( + 5.34, 358.97 - 360, 10.42, 50.0, 110.65, 140.54, 210.85 - 360, 280.38 - 360, 330.5 - 360, + 358.97 - 360, 7.24, 48.0, 112.65, 135.54, 215.85 - 360, 278.38 - 360, 333.5 - 360, 356.97 - 360, + 8.24, 48.245, 112.57, 133.54, 215.637 - 360, 278.38 - 360, 333.5 - 360, 356.97 - 360); + private static final List EXPECTED_RECALCULATED_VALUES_FOR_CONTINOUS_WRAP_AROUNDS_CLOSEST_TO_PREVIOUS_POINT = Arrays.asList( + 5.34, 358.97 - 360, 10.42, 50.0, 110.65, 140.54, 210.85, 280.38, 330.5, 358.97, 367.24, 408.0, 472.65, + 135.54 + 360, 575.85, 638.38, 693.5, 716.97, 728.24, 768.245, 112.57 + 720, 853.54, 935.637, 998.38, 1053.5, + 1076.97); + + private static void pointYEquals(Point actual, double expectedY) { + assertThat(actual.getY().doubleValue(), closeTo(expectedY, POINT_Y_EQUALITY_EPSILON)); + } @Test - public void testRecalculaion() { + public void testClosestToPreviousPointRecalculation() { + Point previousPoint = new Point(9, 20); + Point notToBeRecalculated = new Point(10, 100); - assertThat(WindChartPointRecalculator.recalculateDirectionPoint(90.0, 110.0, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); - assertThat(WindChartPointRecalculator.recalculateDirectionPoint(null, 110.0, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); - assertThat(WindChartPointRecalculator.recalculateDirectionPoint(90.0, null, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); - assertThat(WindChartPointRecalculator.recalculateDirectionPoint(100.27458, 110.0, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); - assertThat(WindChartPointRecalculator.recalculateDirectionPoint(90.0, 100.75639, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); + pointYEquals(ChartPointRecalculator.stayClosestToPreviousPoint(previousPoint, notToBeRecalculated), notToBeRecalculated.getY().doubleValue()); + + notToBeRecalculated = new Point(10, 5); + pointYEquals(ChartPointRecalculator.stayClosestToPreviousPoint(previousPoint, notToBeRecalculated), notToBeRecalculated.getY().doubleValue()); + + Point toBeMovedDown = new Point(10, 358); + pointYEquals(ChartPointRecalculator.stayClosestToPreviousPoint(previousPoint, toBeMovedDown), -2); + + previousPoint = new Point(9, 350); + Point toBeMovedUp = new Point(10, 2); + pointYEquals(ChartPointRecalculator.stayClosestToPreviousPoint(previousPoint, toBeMovedUp), 362); + } + + @Test + public void testOverallDeltaMinimalRecalculaion() { + Point notToBeRecalculated = new Point(10, 100); + assertThat(ChartPointRecalculator.keepOverallDeltaMinimal(90.0, 110.0, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); + assertThat(ChartPointRecalculator.keepOverallDeltaMinimal(null, 110.0, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); + assertThat(ChartPointRecalculator.keepOverallDeltaMinimal(90.0, null, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); + assertThat(ChartPointRecalculator.keepOverallDeltaMinimal(100.27458, 110.0, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); + assertThat(ChartPointRecalculator.keepOverallDeltaMinimal(90.0, 100.75639, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); //Test for special case, which caused a wrong recalculation, because of the wrong use of Math.abs() notToBeRecalculated = new Point(10, 179.8537); - assertThat(WindChartPointRecalculator.recalculateDirectionPoint(180.12356, 190.0, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); - assertThat(WindChartPointRecalculator.recalculateDirectionPoint(170.0, 179.5372, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); + assertThat(ChartPointRecalculator.keepOverallDeltaMinimal(180.12356, 190.0, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); + assertThat(ChartPointRecalculator.keepOverallDeltaMinimal(170.0, 179.5372, notToBeRecalculated).getY(), equalTo(notToBeRecalculated.getY())); Point toBeMovedDown = new Point(10, 356); - assertThat(WindChartPointRecalculator.recalculateDirectionPoint(4.0, 23.0, toBeMovedDown).getY(), equalTo(new Point(10, 356.0 - 360.0).getY())); + assertThat(ChartPointRecalculator.keepOverallDeltaMinimal(4.0, 23.0, toBeMovedDown).getY(), equalTo(new Point(10, 356.0 - 360.0).getY())); Point toBeMovedUp = new Point(10, 5); - assertThat(WindChartPointRecalculator.recalculateDirectionPoint(356.0, 359.83364, toBeMovedUp).getY(), equalTo(new Point(10, 5.0 + 360.0).getY())); + assertThat(ChartPointRecalculator.keepOverallDeltaMinimal(356.0, 359.83364, toBeMovedUp).getY(), equalTo(new Point(10, 5.0 + 360.0).getY())); } @Test - public void testContinuousWrapAround() { - //Test multiple wrap around between 350� and 30� - List pointYValues = Arrays.asList(356.74, 10.54, 30.192, 20.625, 5.647, 350.526); - List expectedPointYValues = Arrays.asList(356.74, 10.54 + 360, 30.192 + 360, 20.625 + 360, 5.647 + 360, 350.526); - assertThat(recalculatePoints(pointYValues), equalTo(expectedPointYValues)); + public void testClosestToPreviousPointWithMultipleAndContiniusWrapArounds() { + //Test multiple wrap around between 350 and 30 + assertThat(recalculatePointsClosestToPreviousPoint(MULTIPLE_WRAP_AROUNDS), equalTo(EXPECTED_RECALCULATED_VALUES_FOR_MULTIPLE_WRAP_AROUNDS)); - //Test multiple round trips from 0� to 360� - pointYValues = Arrays.asList(5.34, 358.97, 10.42, 50.0, 110.65, 140.54, 210.85, 280.38, 330.5, 358.97, - 7.24, 48.0, 112.65, 135.54, 215.85, 278.38, 333.5, 356.97, - 8.24, 48.245, 112.57, 133.54, 215.637, 278.38, 333.5, 356.97); - expectedPointYValues = Arrays.asList(5.34, 358.97 - 360, 10.42, 50.0, 110.65, 140.54, 210.85 - 360, 280.38 - 360, 330.5 - 360, 358.97 - 360, - 7.24, 48.0, 112.65, 135.54, 215.85 - 360, 278.38 - 360, 333.5 - 360, 356.97 - 360, - 8.24, 48.245, 112.57, 133.54, 215.637 - 360, 278.38 - 360, 333.5 - 360, 356.97 - 360); - - assertThat(recalculatePoints(pointYValues), equalTo(expectedPointYValues)); + //Test multiple round trips from 0 to 360 + assertThat(recalculatePointsClosestToPreviousPoint(CONTINOUS_WRAP_AROUNDS), equalTo(EXPECTED_RECALCULATED_VALUES_FOR_CONTINOUS_WRAP_AROUNDS_CLOSEST_TO_PREVIOUS_POINT)); + } + + private List recalculatePointsClosestToPreviousPoint(List points) { + List racalculatedPoints = new ArrayList(); + Point previousPoint = null; + for (Double yValue : points) { + Point newPoint = new Point(10, yValue); + if (previousPoint != null) { + newPoint = ChartPointRecalculator.stayClosestToPreviousPoint(previousPoint, newPoint); + } + racalculatedPoints.add(newPoint.getY().doubleValue()); + previousPoint = newPoint; + } + return racalculatedPoints; } - private List recalculatePoints(List pointYValues) { + @Test + public void testOverallDeltaMinimalWithMultipleAndContinuousWrapArounds() { + //Test multiple wrap around between 350 and 30 + assertThat(recalculatePointsWithOverallDeltaMinimal(MULTIPLE_WRAP_AROUNDS), equalTo(EXPECTED_RECALCULATED_VALUES_FOR_MULTIPLE_WRAP_AROUNDS)); + + //Test multiple round trips from 0 to 360 + assertThat(recalculatePointsWithOverallDeltaMinimal(CONTINOUS_WRAP_AROUNDS), equalTo(EXPECTED_RECALCULATED_VALUES_FOR_CONTINOUS_WRAP_AROUNDS_OVERALL_DELTA_MINIMUM)); + } + + private List recalculatePointsWithOverallDeltaMinimal(List pointYValues) { List recalculatedPointYValues = new ArrayList(); Double min = null; Double max = null; for (Double yValue : pointYValues) { Point p = new Point(10, yValue); - p = WindChartPointRecalculator.recalculateDirectionPoint(min, max, p); + p = ChartPointRecalculator.keepOverallDeltaMinimal(min, max, p); Double newYValue = p.getY().doubleValue(); recalculatedPointYValues.add(newYValue); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/WindGridColorPaletteTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/WindGridColorPaletteTest.java index 09860775f37..47afd6a2753 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/WindGridColorPaletteTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/WindGridColorPaletteTest.java @@ -1,9 +1,10 @@ package com.sap.sailing.gwt.ui.test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; -import com.sap.sailing.gwt.ui.simulator.util.*; + +import com.sap.sailing.gwt.ui.simulator.util.WindGridColorPalette; public class WindGridColorPaletteTest { diff --git a/java/com.sap.sailing.gwt.ui/.classpath b/java/com.sap.sailing.gwt.ui/.classpath index 486745d1300..e09700fd3f7 100755 --- a/java/com.sap.sailing.gwt.ui/.classpath +++ b/java/com.sap.sailing.gwt.ui/.classpath @@ -1,14 +1,13 @@ - - - - + + + diff --git a/java/com.sap.sailing.gwt.ui/.gitignore b/java/com.sap.sailing.gwt.ui/.gitignore index cd554ca5860..fc79b77cf85 100755 --- a/java/com.sap.sailing.gwt.ui/.gitignore +++ b/java/com.sap.sailing.gwt.ui/.gitignore @@ -3,4 +3,4 @@ /.generated /gwt-unitCache /extras -/com.sap.sailing.gwt.ui.* +/com.sap.sailing.gwt.* diff --git a/java/com.sap.sailing.gwt.ui/.project b/java/com.sap.sailing.gwt.ui/.project index 0e39cc17829..3093455dba8 100755 --- a/java/com.sap.sailing.gwt.ui/.project +++ b/java/com.sap.sailing.gwt.ui/.project @@ -35,5 +35,6 @@ org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature com.google.gwt.eclipse.core.gwtNature + org.eclipse.wst.jsdt.core.jsNature diff --git a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs index e61c9136447..13039738f66 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs @@ -1,4 +1,4 @@ eclipse.preferences.version=1 entryPointModules= filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5EYXRhTWluaW5nPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC5Ib21lPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= 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 3d1e6b08933..548f1163d5a 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 @@ -1,3 +1,3 @@ -#Fri Jan 13 22:25:34 CET 2012 -eclipse.preferences.version=1 -encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties=UTF-8 +eclipse.preferences.version=1 +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 diff --git a/java/com.sap.sailing.gwt.ui/AdminConsole.css b/java/com.sap.sailing.gwt.ui/AdminConsole.css index 2fcaf5f94d3..c5597a11b54 100644 --- a/java/com.sap.sailing.gwt.ui/AdminConsole.css +++ b/java/com.sap.sailing.gwt.ui/AdminConsole.css @@ -206,3 +206,7 @@ select { .gwt-SuggestBoxPopup .item-selected { background-color: #ffc; } + +.wrap-cols td div { + white-space: normal; +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/AdminConsole.html b/java/com.sap.sailing.gwt.ui/AdminConsole.html index 4169d2fb226..e8d613d1750 100644 --- a/java/com.sap.sailing.gwt.ui/AdminConsole.html +++ b/java/com.sap.sailing.gwt.ui/AdminConsole.html @@ -1,59 +1,60 @@ - - - - - - - - - - - - - - - - - - - - - SAP Sailing Analytics Administration Console - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- Your web browser must have JavaScript enabled - in order for this application to display correctly. -
- - - - + + + + + + + + + + + + + + + + + + + + + SAP Sailing Analytics Administration Console + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/Home.css b/java/com.sap.sailing.gwt.ui/Home.css new file mode 100644 index 00000000000..713dc1aef54 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/Home.css @@ -0,0 +1,207 @@ +@import url(fontface.css); + +/** Add css rules here for your application. */ + +* { + font-family: Arial; + font-size: 12px; +} + +/** Example rules used by the template application (remove for your app) */ +h1 { + font-size: 2em; + font-weight: bold; + color: #777777; + margin: 40px 0px 70px; + text-align: center; +} + +fieldset { + padding: 6px 6px; + margin: 8px 3px 10px 2px; + border: 1px solid black; +} + +fieldset legend { + padding: 0px 4px; + font-weight: bold; +} + +.chartLegend { + float: left; + margin-right: 4px; +} + +.chartTitle { + font: 20px bold; +} + +.sendButton { + display: block; + font-size: 16pt; +} + +/** Most GWT widgets already have a style name defined */ + +.dialogVPanel { + margin: 5px; +} + +.serverResponseLabelError { + color: red; +} + +/** Set ids using widget.getElement().setId("idOfElement") */ +#closeButton { + margin: 15px 6px 6px; +} + +.gwt-TabPanelBottom { + height: 100%; +} + +.global-alert-message { + font-weight: bold; + color: red; + font-size: 120%; + text-align: right; + padding: 0px 50px 0px 0px; + clear: both; +} + +.errorLabel { + color: #FF0000; +} + +.linkNoBorder { + border: none; +} + +.refreshLink { + border: none; + position: relative; + vertical-align: middle; +} + +.valignMiddle { + position: relative; + vertical-align: middle; +} + +.boldLabel { + font-size: 14px; + font-weight: bold; +} + +.boldAnchor { + font-weight: bold; + vertical-align: middle; +} + +.gwt-ListBox { + font-size: 12px; +} + +.legColumnHeader { + background-color: #FFFFFF; + padding: 0 0 0 0; +} + +.dialogMessageLabel { + font-weight: bold; +} + +.gwt-DialogBox { + z-index: 20; +} + +.gwt-DialogBox .Caption { + background: none repeat scroll 0 0 #e5e5e5; +} + +.gwt-DialogBox .dialogTopLeft { + background: none repeat scroll 0 0 #e5e5e5; +} + +.gwt-DialogBox .dialogTopRight { + background: none repeat scroll 0 0 #e5e5e5; +} + +.gwt-DialogBox .dialogMiddleLeft { + background: none repeat scroll 0 0 #e5e5e5; +} + +.gwt-DialogBox .dialogMiddleRight { + background: none repeat scroll 0 0 #e5e5e5; +} + +.gwt-DialogBox .dialogBottomCenter { + background: none repeat scroll 0 0 #e5e5e5; +} + +.gwt-DialogBox .dialogBottomLeft { + background: none repeat scroll 0 0 #e5e5e5; +} + +.gwt-DialogBox .dialogBottomRight { + background: none repeat scroll 0 0 #e5e5e5; +} + +.gwt-Button { + margin: 3px 2px 3px 5px; +} + +.gwt-Button:hover { + color: #fff; +} + +.gwt-Button[disabled] { + color: #888888; + cursor: default; + background: #f1f1f1 !important; + border: 1px solid #A3A3A3 !important; +} + +.gwt-Button[disabled]:hover { + color: #888888; + cursor: default; + background: #f1f1f1 !important; + border: 1px solid #A3A3A3 !important; +} + +.gwt-TextBox { + font-family: Arial !important; + font-size: 12px !important; +} + +select { + font-family: Arial !important; + font-size: 12px !important; +} + +.leftPaddedPanel { + padding-left: 50px; + padding-right: 50px; +} + +.paddedPanel { + padding: 10px; +} + +.gwt-SuggestBoxPopup { + text-align: left; + cursor: pointer; + cursor: hand; + border: 1px solid #666; + border-top: 0; + background-color: #fff; + z-index: 999; +} + +.gwt-SuggestBoxPopup .item { + border: 1px dotted #aaa; +} + +.gwt-SuggestBoxPopup .item-selected { + background-color: #ffc; +} diff --git a/java/com.sap.sailing.gwt.ui/Home.html b/java/com.sap.sailing.gwt.ui/Home.html new file mode 100644 index 00000000000..2b6cb645632 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/Home.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + SAP Sailing Analytics Homepage + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/Leaderboard.css b/java/com.sap.sailing.gwt.ui/Leaderboard.css index 28080806749..270d817cdc7 100755 --- a/java/com.sap.sailing.gwt.ui/Leaderboard.css +++ b/java/com.sap.sailing.gwt.ui/Leaderboard.css @@ -158,3 +158,10 @@ body, table td, select, button { .circleRed { background: #f01010; } + +.busyIndicator-Simple { + margin-top: 7px; +} + +.busyIndicator-Circle { +} 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 84f65b2ae0a..c5ddc4a6236 100755 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -24,12 +24,13 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.freg.resultimport, com.sap.sailing.domain.swisstimingreplayadapter, com.sap.sailing.simulator;bundle-version="1.0.0", - de.csenk.gwt.ws, com.google.gwt.osgi;bundle-version="2.6.0", com.sap.sailing.domain.igtimiadapter, + com.sap.sailing.xrr.resultimport, com.sap.sailing.datamining.shared;bundle-version="1.0.0", com.sap.sailing.datamining;bundle-version="1.0.0", com.sap.sse.gwt;bundle-version="1.0.0", + com.sap.sailing.domain.racelogtrackingadapter;bundle-version="1.0.0", com.sap.sailing.manage2sail;bundle-version="1.0.0", com.sap.sse.common;bundle-version="1.0.0", com.sap.sse.datamining.shared;bundle-version="1.0.0", @@ -42,13 +43,13 @@ Import-Package: com.sap.sailing.polars, javax.servlet;version="2.6.0", javax.servlet.descriptor;version="2.6.0", javax.servlet.http;version="2.6.0", + org.apache.http.client;version="4.2.5", org.json.simple, org.json.simple.parser, org.osgi.framework, org.osgi.util.tracker Web-ContextPath: /gwt Bundle-ClassPath: WEB-INF/classes/, - WEB-INF/lib/gwt-servlet.jar, WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar, WEB-INF/lib/gwt-ajaxloader.jar, WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar diff --git a/java/com.sap.sailing.gwt.ui/RaceBoard.css b/java/com.sap.sailing.gwt.ui/RaceBoard.css index 889f9dba932..f3daea67dce 100644 --- a/java/com.sap.sailing.gwt.ui/RaceBoard.css +++ b/java/com.sap.sailing.gwt.ui/RaceBoard.css @@ -371,3 +371,9 @@ img.openColumn { background: #f01010; } +.busyIndicator-Simple { + margin-top: 7px; +} + +.busyIndicator-Circle { +} diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch index d851188e1cd..e19f996d629 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch @@ -1,54 +1,52 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch index 972faa69fac..5d943eeb607 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT.launch @@ -5,6 +5,7 @@ + @@ -41,6 +42,9 @@ + + + @@ -49,7 +53,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/Search.html b/java/com.sap.sailing.gwt.ui/Search.html new file mode 100755 index 00000000000..02da77912e7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/Search.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + SAP Sailing Analytics Search Demonstration + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml index 6b72b611654..1da7d1d7306 100755 --- a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml +++ b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml @@ -6,7 +6,7 @@ GWTCacheControlServletFilter - com.sap.sailing.gwt.ui.server.GWTCacheControlServletFilter + com.sap.sse.gwt.server.GWTCacheControlServletFilter GWTCacheControlServletFilter @@ -82,35 +82,10 @@ administrator + eventmanager - - Leaderboard editing pages @@ -118,7 +93,7 @@ administrator - moderator + eventmanager diff --git a/java/com.sap.sailing.gwt.ui/base.css b/java/com.sap.sailing.gwt.ui/base.css new file mode 100644 index 00000000000..9ea6b783a7e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/base.css @@ -0,0 +1,213 @@ +/** + * Resets + */ +/* normalize.css v3.0.0 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} +/** + * Fonts + */ +@font-face { + font-family: 'Proxima Nova'; + src: url("data:application/vnd.msfontobject;base64,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"); + src: url("data:application/vnd.msfontobject;base64,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#iefix") format('embedded-opentype'), url("data:application/font-woff;base64,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") format('woff'), url("fonts/proximanova-bold-webfont.ttf") format('truetype'), url("fonts/proximanova-bold-webfont.svg#proxima_nova_rgbold") format('svg'); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: 'Proxima Nova'; + src: url("data:application/vnd.msfontobject;base64,zVcAANdWAAACAAIABAAAAAIABQYDAAACAAQBAJABAAAEAExQrwAAgPvgAFAAAAAAAAAAAJsBACAAAABNIC62xgAAAAAAAAAAAAAAAAAAAAAAAB4AUAByAG8AeABpAG0AYQAgAE4AbwB2AGEAIABSAGcAAAAOAFIAZQBnAHUAbABhAHIAAAAkAFYAZQByAHMAaQBvAG4AIAAxAC4AMAAwADEAIAAyADAAMAA1AAAALgBQAHIAbwB4AGkAbQBhACAATgBvAHYAYQAgAFIAZwAgAFIAZQBnAHUAbABhAHIAAAAAAEJTR1AAAAAAAAAAAAAAAAAAAAAAAwCUfAA3bAA3cgA8PhLN6YrIeFpXyWhbcUp/eCJjunIsZyxF9yb3BUOC3BrEZUV7en5KBFugRZ02KjAGVkYpVBt5Dg1BqbE5YbUazTcXHTEZNwD1YaBBVloZ8hvlyHfx64YWgxMz4XzhimgHVnMziKgTuOv5xfcjpVS/kr8CoN4nnmxKqwRyah2kiyCLjVO7FbDXZCxAlRyvyiu1aKp4kqQBBTXNra09Nc1IZuMxJlzjzhoBrl/i70bxT1QZH5l8iUqhC3O0LQDyu+oQ1onjsyYrlbSzG2aR7t4yhjSKMhXetEp0/LJ1vlK9zZltaYr/OnF8ULF8VAcbFqGu9pJpyMRAAB7+qd+IWGAhQyAJew4DZCGWOM9OoEBvYxsvflxmEZe1yduqLBvSqLpaYtmWklyyc6E6w64bgL0jikOTSERjIKoBVAozXDqiZHpMQpnLUGMXtIS0eKaQg92OZJDjw5lDzG6PrHLNECM8lSVCDBkEXIoHKfOhZiNKZSMNiGcKQIRgMQdWwpJh+HzQagE0DSgYrDJAR7QpcYu0anyJvbPFCFfIv6nMGKxfMVS5TttAuLKq2uLks7F4cAiQ0ZIqUvQsB8kZHgpPMi4e45JukBQkGwluDw6cFw8gNnZCyJMACKKvKZzwKeCsi6JpXQslC8fwFGpZpIQDcIzCXXcLJpB+Ww+2ReO7uUQ9K/Lxt9yr95jZuHVe2dUee3QdaQqhx4jM5qhnrDoyGYJi0lIhjKEC6861XDiAHyRsR4VknIYnognST2iBisThqYW0yR4gBmjtF5/PnO7E7DC4Tozr0eQ/WSxiGpOMeqmdCMkCkSllhAELs0UeINCBw3Bt6033umMUYTv7Uz/ou9HKbiyb8NE6TZwSYa7PZyUBNJmscCH00AD7oFc4l35HStX0SvDKKa+IG8hYpoUDsTYhuhQWXh6q/DXZo7A/vlY3LFNoB71CN9wpfrIZDRKKGLTzu/3CAMvg46K4TzcF8a3SyaJhwtQz7WBjJ1c/sNvvfNf78imvJjTZ6tcep/iab76HMi4ErD3mkQcwgC4bhc8p+52HO08nWpiXYZAP4wSkNYkUN19JOvHI4Jong+gw1mCjU4zyCVmrKTTxZnCGDwMDhTCeE/L++4zHGKf7iGC4asY7bQAdFTIohEVLHlxFESZkKrD+LKFsgzQgcvRFkA0K+M9KCRzCvbaNpQFV7hkjI7rcbgqqdKRRAXoFNzgKUW0dQDJBufGeUBpNRYvQT6oGcORmCo4PDWRQQzk2KBEhiO1hH2RIvvH8rcqRhrXkNcwEhUE++rA7Lt0fKO/dnqNDQ1UAc0CzOtmtcNCoIDhFcaz2YbYxsvKITdUMR4WwV75+nELACNEMPUFlFqM9qVpf18YAPjA05j8MkI38k60iAasqkDf85ZBByAkWh7eFlNuKqVAiR0CikqQhMapSTyardMTdcTIGpflCJywgh1QyrRH6XtwUJmD95qwVq2YZqFoGHldyetDhjklwmJoM9H+0frt4ofxxkIjBEtQ3sr8SASBtOoLR+x0qlFSdthmyQC4hFk+p9V6uikQGbBcTEn/3RcwaXcWoPCrLmWSyjYKVlGr2TAcB4OZEOp47r611ApkMwIrbuMHKEjWpUUxuAVMoAki2oGbVq2BNwNL8BMJdKibHU0Tespu+yue2atz1H8opUdpoTubNKkuwtnoTibBUPN8DENx16k5YTdLNshsdseXZVZS4diYlYvqlrITAYU7/M0DrWHSklhCSNosJYGwCFKmqkPDXerVJ5qlNyDtpoJZ4AJ8BoMuvgQ30z6pFy+CBLX6IRpFashRcyRaRAOEsvylUUJJA4niCQihYZF8pnMgBFwcc7/1yMCtPkEtSOJVc8OKrsw0lTEXgWFZsJf+AJ+Jg3wxK0Z+QgYlEi1kNmApuRlnBYeaefML5T7b/pUCsoy+iiTFTssA+7FcQyDFzEa/CXCKHFzFzoTxTRNT8IEqH7BlwnKphdhF/Woq17C4WalptVMBc6gXDAXoAbATjOERktFIsgfYpfptauJRR4G1GI1u7lo+XlWQrae0kWi6QNbWxEQ1TzQSnM59YWKM1TCGX4ikYgUh5DkZXMK4MBGLQswrGgJlybBGoDcrFSWrSpz0Jvc781REwu6mAHW3WCi8Mi+U6yxMHUL6Ror1gUiDUQX3ERnD3VmbOkdpSCkO7g7ylfCKhVyhQjnKV/xVwKI1KIQSN+UQi0Zp7RVLV5RAgQRUsNpRIiTc152eS9livqLyiUSIUohd8SAj9IzHeDleYoVp2BqaYpc3hyk9uJG2LLiMJ5o6LigTux31i5+84VUQQSmRHyQHh6h1AxRgSkaZ2F+UyXmce62VrBiQ4MrBfTjS8ukd6zqqmthEw0bPCdDsNpMOznjG5KEeiwa+jAJi3YmAZBbjsK04DoxxFrZJ0Xx0Z0n7f+tQooKIHbkWcwAqDZcYBbSDKBIavDyxdmZWtxl+p3KgPR8Mp79/chKQKwJJAbthYMrrkJq87Zr0crAEXUCbUrMyP1vHTRjMPNBbLEM7OnCeZVgGmsMBafyMtM0QCJoaxQBWU2DPTmAdgYFGiWAgzESEvhBioCimDMpxgMk57Wj5aUdqRxSlIYCEeOFL5FGB0XkDMDsCDW7MDopOpLsmFlJk6v8pO1+UZpT9Breyn50/ZMnOor/R72HCoZ157lRs1lvPf0Z3Nee5Iy9a09y5C0c1496h5Ulw96mwg3cPexj6nVm1nnhQBEHOKEqlinMRLcEUQBGyeqsBwXRMtd5FKwQ0irFmVpEgABhmXZ+JePh44fyaw2yd6bMH0O/x29C9lPGKuDhtlqKcmi30TPEytERyi0kaYGfA1iZ8pWoTe0WIW8wooe4OHCSqWRXBHjCEofITyGyLlM3BPGOizp25hgynoo3NkfHgGXDK5YwacFT3l6YJEkeyb0wFagKrUrUOLnw+wktwFzsX7P5MNhC4Fi1LxZkylaxL+uBv4dFBLNNSZ/v721eiRCYkmjEJhwcVWmJol1ZheKavIPw05Ogd7vBMrzZJC5Lc9y6wylgetaavO0AnWSbebKsgTymBn6I8xtqWkrlLZBTzyvTPd6vcY2Iz0VRDIkL2C7wO0bml3pVNhvzL6uiqvznuKpe25KzIKvJSRhPJE2pamJjiowQlWzwZpWopBAg1ALCBTgx8ktC/0TuDJxnJ77onJxb4n0AlNvLyjtrloiRQIgPfbvC9w+M7UgcNJAgDawIXUTOX7e6RSpLWwQxp5eeh249eY+xEKEJ4DSZ24EoYkuBNq5Cym+7IeceWFC691Jc6KiKJsEFLOgsKYGfFloIRz2gan161xhYUhpENlC4AAWu881slI8ur04BBjh/uVNJmCCq0NVFJpeYk8QmKjyb2paMuQyHUoyhEIs6BmXqRMtXAezP8gSUDictZodBjQAP0GvcuGDIz9XwdiTlApYoRhEFA+ikTBQuVL4Gculji9hzYNTyVb8xCWlVCW86tSipM6QYsval31v+lakmSzcgoxIYwN02kw3imVPoPRCYMXDqYCXBwa5s5qVjByH27mnLGt7kuPAV7zbhlAgnlCtBbJS2m+UVFaSaJqJoUwTNCdpMyZVohXes3lHKTIgF/2Qhr1ILtg/AXnx1wzpHjAmNwuyfJOM3iJpwTokNbiLwm06myPe0s3/B0jI4EIOihwMxBFARL05O6ADl8gQ6FCMB+PBa6Ol6Hg8nBAmIwRK3RGfCGoCIEkrpaf6DnyRoI8jTBAezHJARpmGiFmlDmKqPFE0ZSiJKZPoenyjogI1Rg5AaVVP4qSskhPcnBDRmeFOp/Isnnaj01qVVRAS1TW6HP+o1aq+iSK1K0wFhPaVGiWHIicqIN1qr40SqWRYPTdZAGLmj0knihhGsFgD0mokugrWkB9FFXH+t0CuVKIJWu0P9RaeXA+rtKE/HW2l01cnjY2ADMx6n6VICoqaFYopeoYAhSlKMUG1o2cBa9WF5AluYFlabfosyNo0hErBZN09iDFJQw2WltDs9GM0Rme2ZbTWkn9sezW3Z0t+7A24idrgNs96NsZkMU3fRNX2L18ZfePsY3REsSEgDWeBSdldlc27nYMYYUZ4wqdoN4GiUA3J/8tDWb+mdimJTJapGdxybemrBiBhLs6CnDYWqcjNpOa12QqlQw1ZdO7SYFSh8CLjo0Xo67f+IXXCXCA20zEg+4bDdmOrDBhNgLEw76A+xq+8NXwlsPEd7cJAvI4MvMCBD1aXLmemSCYH2bGtAkBoA7eDCDztE4bXbOOaHC3fzd41ZJrxKQgFcvl9uDRGJnYlHoIBacuSXqT3Egv3qR3sfrN3ftg0vTzIjpn7NWmDchACLXIiA3gu/AMnL5Mlf0egjjJUsdHuOFAkwyIq3xMoQr/xoY7oO5kRBftHCdADgEJ7YBYaR0nUfs5cYCdqgEKIrj2c/noqhK2j6ojvcR1A+2glEsAIlje3mG9NscFMPhXsjhFo0VgbG0iQZRkAD4F028CJ7oanMcowAwDpQJ3U4TMDoWeOYUAM1infpdTy2KehzJFCmbCUhUheR4UzS2qQeV1l5OblUFvdrDSqJEqBcVUuKy6GB5XFQFDuYbLJcpEVAFO/4iGaA6pKVJjNc/wILmbZgW8diQbsRUarSnRFLk69KShiAj4oH6WgUX1q3+RtCEObSSRNKugBeqVP/N/D7MczMJxPMTAd9uoLWqmhD+IAiUBKV77N/FsPMYSdBscTOY2iSIg/KwMo9SUMQr467NSK885LTmVRyIiTVBWVwcGQGJiF4ZvmEo3wCHJIrM0zjKOLLhdAwAi8Csh/Ke1hAEXibKCzCQzi2qqO1hfr4cFQDiFtbTFvG40w4Wg9FwPDsGTsN/dw/HwAeQACRwbWaSTyybEMlYtEE3EpiJIKAFWa9FhUvsnrLzAPQcw7zbUd1XqZ2yRmP3Z3Ystt8JE0pjIJxRHasr6A4n39K3yHtauARYpXRjKM5H+1srg0gAyXcgs2yo+NYsL8ntg0GW2ZftA0h424qgptG+ch7ALWB9jBq9m4zWks9mKe/Ov6S4ucZc0gxEiwOQeqKXh9XoNlrB+MvICTBjERuSd/5HJm6G4/KsaORRRo0gWn8JlAnA4SminSWv7GRQXQwBZOyAn0jNpw6DbhMkXIa5Kc/Lkn4j+HgCx7C0+hMLbpSxRkL4LpR9PEZYA3GIUrWz+YHTKYwRQ91UGv+X2k5XKhHmShlBjvXTyEm0g8qUgri/3Fm01JjEhiNGFMoSMDEd56i1YYEYocMDF+bfp7zgC9IZUAV1pJOyXmjbH74ZPT4DFFKsRZrge1gOtIXwiTAGX90Z1pkHp5Mizc7pEjzlYLp4gQPSiXeQs955eBYALPnAE56H43HCLIwFsxBVHOVxUXE0N5xWZzC5KFnC8EqRw5C1EXdXkdgywiNYCI+dw3teXzKqMug0iw3Bw8z+U4MlMoxQmMmBCiSkMOjxq7svQHnR88jEjaDXNDBLw8IzWKl90Dxfqs/xksKp+6DPAh06ZdqFYh64Vn/WKEIzeN/Bm0z7nF5QdouO+YhtP8wG2cyBF0mvvI/UzqKUAdDgN0UiP9EEQVnZmoqHp0GCNmTU9UAABggDU5qBQJD5cAJ4Be4rgFHgxpxyj4igIjb+OIJydYSYQgbrYuHDzgkGnIjLtDYQDReZn0XQYNj73qU3d25TJZcDSLvFFJ8t6pfI2lU3JvBoXCxCrOTKm6RWJ+9yAL6hnH70CU4EmWPsuTk8FlBr2utSS4notunyJKYWpASl/xwpPQa9OhDmqdnbvbz0q1YoEwuBsisJE5WhOKAlKDojXYVAINhBviIvl4b27W/5x1qhxJlt/HoixfhQbMOZxcDNl69VZ4dY0kWkT3cCxqnvMzMjYJxOnNwIwLS8upZBOHV+fjenmYnl8jEkRYKxnJ1wuPHw/KZ29JXFKAwogWXrJstfr3Wq9qpkpD5DG8loKKYoHHemWZ37cB1k7nm0ihDCCkBXBLyOrx6al1CUW4pKWrUFNg++WSlqZl6TR5+b81PQKp52vRhtNN2YghG5tfBYzHWwgsjPTreDGDdjVSkup0e7GG4bXONbLxfgcgGA9SGQlVGrKQcDOFrCGEaNIZaOteAQUHXL5krHxkfimPZutaKHdZWkmREVyOAn0O6wZs9ng4zLpi/8BihGQSTI+37YE7IG5jALM0Xu18iJJEjgXWE7PoE2OaWGsH8GzLLM/DpqCT6zCru0RSL4oFPLDlNXGGWiFwekV4llhTLTT89bHCaB4pl9ndRXxPyGXzJDZf+BmN2asFEY1lEC7ABDOtCwfhysetrxsvhacDizUbGiG1rJ7uS52NYoyQsW4RpXJvSPYkA3cJ/wYnPT3cyiIbtGe8InR9gY5bjZq3C4Ne3+0IEMLymAGZYzgLlWqt3NXb4iYILaDHlWmNS+Dho+B8hyN3zHIO8kvLxJO6sxthzhuuahjmZ7JB80EeVMDFZhqIeOEiOyyKxwehGMPNx9DIRh1T8z2LtEJxEtnO/c1h5KeZWdGLxVosrDgEhM4UkZoucekc1pOXNOOTibbX0Se1yFaWEG0M22Xt7FTMOk/dHJUUFIlrcaSkQSjjCxIweSZiBiCHGA8IvwD6Iq4zLaU9mBkdYwIFt2ngy6D1GGA+cbCfJTAStEeStx4dkOGg6I5Cc3GcfSHVlRueo4QxqTvRdKWo9d2+40I9Q3VMY5gONKgUcaEhF1OgNOc10k6+W+n0AR+Naa0R8QQvQMNjzPz07c5DiIUb/0wh9TvcuwdaDh3TsibcBo33Cm1D4IU/FolC6RC8UUlugspFF9qbZJxAsDItSuMoU5huDYOqs+Fw8urfi5+uRII27PIIgW/JsIMLdg+AUpb4D//RuuwZcJ5/szSuE+kKAQoJj+UX+wW/jmu4lh/+WZlXB8NC9/m+SytSk6SiMtPpMCjRgAwafqEIi2njfwl1JQBDYITNsuux/At0l+4hfB5GTMO3wMiBijPLet+CXDN0M8DkluI5lzNIwiqfwpTIdFV6cxHp1htOXAtH2I4i1Ua91jVejDKMuTkWIEW9wNU6mdFykqZkqFpOZEK9TtEFcRB0iwNdFXWaPVmQBXcQEfirSKrQPwDaMUK7P744XNdXgS8k0lX+de1wq3IFwAY5BFwY4xpNFyIPDKKyjQmQ/DIgJ5MNQxQAzDqruoMvQqwenlFAC6zYcI5UGIXJ50+K+cESUEy8p8ggFkeK+F4S+jipeUZIWPbVMIYnWKRCjIeFYD5ZKmLHNNJkVLPoyFRGrm4wbYRXGUIPnA32jKdeBpIOk2EGyTfTYO2+qqtHEuEOHWEw11U5WSTVHT9eRAk7nfkIzhacKtaoCQuqBMTPrkdgFkNyrS3+57zeKNsnbAFiDK9MpZos7XyKsMXyX3gNsvJHtvVJ7Z3I989VNczra1PthjAj3yiusMKU6GvsvcnSqDeMYhcW/vM5NZeMFNhx4k6WFm4074Hajzg/nFX6h2pjRaicQkgdgRjBMg76r0QIoFDwIKWMd0QKo0Dclv40IEomdDIxmytJGhwoImH9wE6tb0sq7cr7Cb3u8I7OkQjADyl9dhgC+AoQMoB0jXEqDNJ8cLGgqzXrfo8E2KVQFHE01YpUs+MVoVg+MgfTzTipw75AJ/GW9gVf9oawnwhGhicdlnw+JUoQfB3H+1wHZHHiNoYWsZ0+qNlqs4Wf9Pn2PqUa+HVxF8JMDJGQlcJMLJZy7s7w8j1fs7aYEJTwi0d7XaZnSphrBsi3UGC56c8MNmun6qh+9gOCxM0CdPFKzGBX7FYuRnFKXNoZmLGCOc2klsS05RjcYCDjwbAAqKih13Tygnit6lY4fyuk6nJUBlTm7j/A2QwTt7HmBAVWsvJBC27aabxRVcvQ4hqOVugjxCMA+XE1+oqAxnmC913+vuknMud4Umu6cbKBHwj2ESpA2ktjAMR8MmLK0l6HBiCsZt6sXApDEl/PTddyiHXYpzZvXVny4U7iFwzfD/EZuwri9gJf5GeVAxwuFcNJ4ERUTbAWW3+phVamNX5Lv2M1zSARUPuzAAN4DrgPTOPvTldTfcn4zgVZu4aYA1VpDd5zzYMwJ/6V5IlVG9/PMY72OfPi56luiIL8GrBsyesyqq0toH8a6Jp2qNO1ncE/5P0e9fypz+PjafZ6aFw5n+Qj5g2MOlOf7uaqpagG7qKygLAq1vsaZNDbQE6Mq1dM0B7KSQBQ1pwKV7UhA27ESo/jxovR8fDCOVXOK+XD2Q6QdkfBMDZraiXG6s8UAUUirUFEiDx4N1dVrkXtJqMy8Eu+iMzS8bDbBBNow7MYOZPjanqCdCi47bhU7NUPbIKH+S8nnBLUhZqBLAopwCEMxP+PCAjR5sDf5TpxkvHHReQCYSjWAV8FOkncB9Ho3l+c2/KdhrH5j1NYeAJCnDSIfi1EgIwC+wOepVhau4r+n9usZk49a9E9Cr2CIsRybu5GVXM/nWoMBIF/FHDp9lrTUZYY9ldBlm9if+MwSJ1yKyChcqodOBPziJCLkQQmIKANhTvQAwR8HYkJRscj70B8BC5ud4tbCPi/SF6GeTFNVH0q5rgHCkiBI/64/8NRmMln+vpoqjh1fHz7JMGuI9lXijGASgCAhgQPA7jdblHX+hcBrAFIhKXJ/GbdQX6htFBwXTWqvxGaqPQn9YjaZ21NVIRGYvccwRE2GC7UceTj53dG0IBMaqel1RNVRCNqrD/XTddDia7kl6I4wDVwds8JYfRR5Y5S/HF5WcOilWUMCQ9wyeg3hHF2IVz6O8S4Z1ElV3HjudgoKUZOBNPN97mSqVBvUBtPBkns7Xi7C0uBuRPkqAqKaMwQzA8LRCAbh5sE/aLA4aPmav0pSQK7B4fxCcGpBVPmQkHPq10jRLFRsRUB9/PpnAodY2yayAEwZZxhRpObUPgl+Qri5ZREOdxBgd4IrLGHtx9oH1P59C/nxOzaSNRH6uiJIKdDRrHrzrlXVf961N33cL6bXUDgXS4+SRgwp/CrV2GlZamnuMDX9ugWxJEj+Q4iggr5zTUQKhGqKN282EU3fIacCJie6BkotTH13/g0HYXRAA/1a+EI1m2Pa6Zj3pMBWZVjy6MF/7xicspLCy37mcYJUw4zWSA4MyCr3pCNaGQTarlYwwxArNr+gGtYtwULm9vwDYqb/Q4EUVIj5ZjIvuF7hdTIVOeOySeGsnkkQbPjRCj7cADpz4pDXp1KXVv9YlzdVUoAzBr0+N4zgyz3rA8F0AZ+jHTgOj2A8uwqBzEGgQAbWKEKkycSiQCE8UBUm+wfpuYxuZRIQTQNDHZ6EiBdkaM3CFLekCu8JIVb3NHepOuNSg6D308fnwNMYQ7oadG7izJyzgxP55PbOEoGPd3y9NHBzc85nkDMs/gPjYXRAQN/0ABKLh1NPj7g9jFKyt2iitPNw92WbORQgZGa61eFhlw0gWxzqbBLtYhm0xbXRJYerKibhfqgzV4nIMqUqaqTghNTGxJJIOmkX1HDn0imRGYPT7sNzM23sg1VLJAgQmMK1Ssxtsv5ybNsejOzJzeEiCMBlXF/NTniOHagR5oBIH0ArzfI9dpqwkv+8AkuGBSx4UEmf/1Q0a+ZK4ctZiE3E2Ae7Ib0FLTKo5w8nS+W8ApsQKgHSFm5DZR2G7/hauCkMBi56tk3cwUx+qzjpF6hmUhtJKn+5ECq04aWomoHaFUnFMoj6W+go5CkoROA3Z5yoO+sybM9uimSnodoGydkhLT/D5BjaxOZyT6P/9Wm9g0UKP638jc3k8UI9SQlNOAqeLODISQoC1TK0DbP+ZVkpf2zEb6ZAADw03I2mLPglN6aVgCGQO1gWx6Frkfnb03wdH0wrKYevdKPh19WHC1r2Cb+2nkH+wmgshq1V/y19uGCO/Y7WF6w8I/XfYAKR3seZ1EPIUapGowUYz4TzQbsaScfNTncs4tqHozB1VDWVGWb6HDHxa2UDCwmHCjglBm91+RW6Nwv/SDf76sQ2/2IMXeoswKEnWqs0T3O9SEWGA/mITtIoj+Hd4nJ8LuHiWEe/WuefLFIZnTNoifIdb4QCuuGSdMhgAQbCUwmQieRzonIYiQdLDV1rTg/D/isXg26R8EJgLPSMys4QjB1R8vKJn4j6GJtKk0ieNLlHUUN0eZwZCDw0EaEXTVl5R1keQuB0k6X0n5xIGwGIpCjXnfIFe9uKqUR23SDPwODEQ7WG3LOMjG4i1bfHDjQyDvpKzgxQgXiKMgqzdScr6XxvF65xpgbSUwQNH0RmAPMZvsPTEHfcHkqxUQ5X3TkdXCJiAgsQhOEAlfM0oWOo06Vru33zcH3lNqmRoS2YdKBAruhh0qFcY/CHe5YqoDFb4NH5F4cErDhBbABawnZ0x7t2rsQq+EFBHHAlGNtnxwFZ06IATTJaqDwo9iHFiQLIhgX0P/IXNjkrxoFyKr2s55g4eI+L9TCErBwEYMPt1/2sUiXoaigfklwnkfFzKAmWJEQ2GkFzvFrB01YCuFdjhgjDUe2qwp3lQaAlXfvwTScEp9CS8TlZAmCLyZ2WweTcOdLhzIGT7QbgHcOBBkNEOB15MYoDzR/fcYiQ481whTlzmgxbyAxV2jLOByMj8zw7uH38dmGBS1riEX/wnBbTEWY1Jq2JI15GrbTJjZsJLrfJXI5YSkCK9finAvUAzlySgBdcdwf9cEvII5l3nRFMRisrtAAZxBXv3z2xNHvCpcBZsY6CLQJM2gSV/4CvxtOWdiBuNXZA85NaWGHlzWklzP4RAhIbFEU5y1dUsb517xICHy/8tIS8NMN5sasKwGEsYYJ9rTnXCgIKQcH1kfmtCgNz4nfdqSMiUEB2kBCM9VBI4Jq0QrDp8BB4HjIz2K3CYbzwc+0TeXfMoyibRj2Brp9zJIBtwdzY8OeJId0h//wkHimkbwhiwq21ClP8Rip6BagIQdWeNWwCsYuPASamyUt4LUqYKbnXMJBtA2HvmM5FJomy8QjSRZomztSJ4OaNIGMeEIWY74MbOLCCZquRDyU+EWmhGgqSYlycGYfajGcVQ2rMm1NPr8OI55Mn+lF+LNOhbmr7U9yytm2lUEneiukFWTRJ4oZqp+TpW14ZV9FjpSSdZEWN/CBbJRet5Rg7u91uD4QTYobFh1IAAASfoMTwahK1NwVJaHiqDIFy3lZxkfmkgcnyFuu/gQWs5Le73a73GrNcIJgWc1jyQzXGajlyYOMCHgfKDpDdHX4lET5hPFOvBQGA2lPBoKlXizicGc1kuqOC0CqO+7nceZ/NQCdpNKFQmOBPrKcaf70WixLywPOZob1bYpUHMGMpADVn5tmEoFQfj/CAE43cDuc89b1mnOr0JlyW0EcQNqU3DuxPqmaAmFzXPgQkSY4BPqM4dWJxBMggEiTFMvYqcUNEXQxHJ2oIh8b1Iy2cIRYRwIdpFo4iW2aPtau3BfBZ0NH5ZLi3nNSYYBooXXkKhbqPccYrRI3DBDSceh9Hq72BIeM4qr4whp4dccxgk08IRoAHaBepcI2Da8QFk4DJroz+HJmAfbbhj1XHZXErgvi8FiFbHH4KC4ehCwHf8Ql3BoqfP4VVb/WmSYx66nX7UZwHLa3B2CTsYfme3NWH8cgwJAJcbxpyi+phfjwYgKMbMzioBIz9Uknco7EBETgxrjxVLjAJLHNgvSPJ9EVKNWaK18uw18lgGKoNMortqYj+mAQ/2RvQ2K3X0khaHlbPdBlxEvHwDqCk/iBj+t97sBpD48VZZ2vPgyRCoDwXFulES8mBBz0rXEkzmIUWwgEFxTeShS4b6JGYyiBGhEzmD0hEEUSCQB/5p8wD8T6i48V6oLAqYiOJQ3p+iIW+671ywbBrEHyEr13L0YOAgEW9L0k7ERhSfCuKDRULdQJ6d/bkGL+Au6j3H/Qz5jkScfkuXBJ0sKrL/d4rfmTdaAYAmvXTfcPNxnzZjdWtg4/8388rOCH9BqLNUDp9jsxBeov2X11aK1Tnim0DHUaxfxJIYW+HEI+VWgpyo9IRVZUQgDOVYVgVnJMN23JFllZTPVtwXdu5mRl8FLITpL4WQwpgvCIM8Yf5xJZ0SSK0h3163RP2nx6HTAGAul+I3yJ6/zm/5J+csgvpnkAQDfonqgUIl6TyYpVkMymOp4JWYGdTOZdeElun2aCufluBqK8q41yJhWNtwPm2iMgpaYuLooAJIjoCfqdaScg7acn14u0jVmBAlC8ZQpqAHDxgaJO+cvFBQUk8JeE8LpQyTRJcT/TnOk5Qyi9lMDKNdmLQC+38TWWirQ7bVLioqs5XWTgQckd15XrzunMKpg86iPWdOoYvrrLAo8/UXkZrAt8vyb5lI7iFGDaQvUpDfvclC6ROopuM5pXMs9FHqDAzRBfgQYmshel9QhVdWBxsutZ/U17Dgtt+++OctCMM2pXs3JlgE0R0lbAVYi5IPBGVDy1GDfUsqPyfwsMz/+BMRP//pNCyPEdF67W3goPUH8esT8KedeC8iIK4EjyjsDOrt8stWIASjg+fAzyjvR+EogkKSlGlZC2jIlh/UZXmCkwEnyGUotorlFwQE3KJ9qCja+PVeLdmTY6/SKjjupUDg5kMMwNSqwhCDpWcShsG94dJ08mJRM64Q5bTmPO2M7guNcAX8HLPWjNqKcOMjoALBpSngF6Xs0PI085pUIxfiU+l6De5dWdR80TQBgpAawiuc5U2ItcZTJGMOGGNl7buscs2Wa35lDU1pSkhicKXhy4pM8lZVkham4fYwpU5fWpBnkoil7f0lN2oV0T81gKMrOYyq1YWFPhSgv2LPnU4ZYqpwys4hv830ZuGsZmjnBNGhRNFNUfP6BjwuPDY7DNB6gUGErcQzN6OeD6yDSl3BBhW4VinsZqskYFM71P8MO/M1owDIh9x3SNDQhN9Z7xwsCTKHoMFh0D/is4HBfmXk5+pCM+brUcSoBGxQ459I7aA6UExCk+FwoULYlQCmyja8dXCRaSGISXRZe4FnTmjBazMtRaOYDQVBxNGlLAwhYDajB6N9ibz0WA8elNL9NsGQaycRTaLSvGAAR9dzgMOnWuLHKwKAO5zG4+FH7gGIF7SM8IfifcftlT7bqfSLCpBD9ogCgNPBZTmddLdD56dsdMwbdKdg2cYh/Xe5MjFvt+P5D0A5k/0zXge2puuCegtU6I5F7s0eGVueSHElZr3i6l8zTj7GnE6DLBROM7Y7OgwaNIWXMslUZI3QKSzZg6ejLZqu18dJEzsZ8OsUD1yHHy4yVttHAHKb92k0wbeDEVyfuZWn/stfQBJ2GaWD2eFMg12L/CF5SKRmbnB5P4LyFLAm6zjtg3XmYsrRHCVhMdXSs2fhqrHZiUMQFLhlCBdxkqQhxmVGTzwgrUzc6jCRh6pscUjBmNzOOQUBIFGhOmvLUftlTck4lLNYLFXXJLaFUttLnJmksm7C8AjUFMAYyhdDfPlgfILBJsMI+EAtzqMNsopMiEDwt+fZk8wBRPxIEQkBYWdqUxAGri1Femf9ZDU1Ap995Ix9KX7+7DFDHjoigLj2WUyU6MkKWfFEwSNlPszqmrNsL1y0qnWg6mTJMSlPeUL481J2EjMun87zIc3xGmFAiO9uXPDzXLvFEMYAiONS37hGnDU/MLyhlishwB85Kz8D+UlqT3zS8wN66gneHvfHJI8tqNMJpjH5q82HMvAjXic5e980NYUnFDYxqRdwwbW3DFXBPiHEeXGAZjT9yhwWshlfqII3K19Eqfe5TA0Dl97oWg7fBpsNiRt0HuinTDK8ttvcJUest0ZjPouJyI2fIcoRIMs2e/REhyqJAQ9USX4MVs8CcZjSxOHSGszzUxCiQTYz9+cLiOoQuvI5pAlWHSGO1GqNH0r+IsDccwgmHZ1byE6gN14TlBeUIKTafwXijcz3pa5wzo0sn7ttBd6MaDPiscNr9GoZ12IeaKPeVfgl32UDjABY7JJF3yBDCEjSg0UBQUT9f+rjzPznCUqcDPJIqBSFqiWR6BOYCR3UOXUPZpWBhXs8iihEVH/1ijWMSwTR4SNywgBUJHgAKb1egS0JeVx/TqXSeP4Wq3hfhlsqDG+mD+/TJzPZYSoUY/8PLWXqYla4wOpHdtxWSMhQf1VbT1FeMEybbK6vHVI2iqGiWG3RzR6FHpRaLvQ6UOmF6l01TZjklR5sxyl0LOyELi0fMmqPmdTipjj5CfHB8rBWEXy0NDnsUAzE0DmZBEShjYQLm1bIByzVwABCZsbuzBKyvx8DDSNDqA+BMyPEbA/eKEjToyO3qpOXA2aZRaiKIgdc0VEDOByibwNqsqpcWhu0oIckVnFwVivbibV9zuWAtjYULsC6uxyLgYWH/4AXrVWy1qp5wlY6XsTKfGcLzIkB4JDYa/nF03lYDu2MHzNDJ6knZ4gPjilMC0xH9Tx2RGSCigNqs0SGrJPazBTGsNXEiDTwBbkgR8gYAgIgDxZwDQhWoj3A4hj/R4hEafESapPJz936oEXwXJ7GNyh8hjwCOX+IcK4rdR/fBROqdZbBV99WYhiqxI+n52OrqTI8+6RM0ukrabqAmgmpwgthpqQKIxJ30EMPgUAezImP8s+oPtfzzHyuP+QfY9ro6Bcay7cNLZcJHfGy6Xb/JZJYxQEcZRViOmVRAnS0Tw+HnbbpK++Ui4I3qg1LOuVY2CJqGAJy9BsvEq0KOtoFmfjz9rzquFtQFpBNHh5l4I74A01SXtvA09A0hmcwEPoSEtAhiuEsNHmuVVN8345MDFoxjjSz8BMpqNWXFwCFZ7fR1t1cUT7BAPVOPWxuTr9AEwZwSI+jbZY5Q4pxeiAfETaQnUjqfPqilJhonWso7JiTOwlEEUaAAT6KXQi2okLaVlyHA91QKgBIFolQVAMmzOmkVR5g7Ijc7SJEw6sHNRFIkLDyfJRQwMH12T7deFoNwlacEZMb9XLi0oXlocAUOoFumwREiXP/RVtSPrEYsUDhWo9i/gsgK312sI3IrLiulEgPu4KNL6522350kZmexGbURxGa1xFu4DqyJbp+DUYYh/WLePnysh311sDtA5m4AizD/q0vbRZNrRHiJSZ+XA4flElfLvHASLYfmKHf7mPxsFwxaAgsUOGPMABb3lhzSVqZKYfYgFmMoZtmiePTIWWd0NVNfp6hxZJ8H+urBII2wLZdjFpNrheQBrig689Xs971Q2WD4lEyffhCWRPIamErGvZkKdnX34nMynhJ9mstAo/gxcDL36xZdHjpy3qP+RE6hvCpmPwEPNIR0ae0EXjDUMRq6Nh/2UYAjar+HUew2kLVedOGVOTKYCd+F7eY4gFjm9F4J8xJ+IUxxHvW9lDL/bxbPsnCZCmWJg4WWzUVorcdh+8/UY34DQDHENVYcZLDbjxIJ69sdYCGyOHQTpQTajs/SACQff2WV6EwIbAmOI9wMR7GsSThs0Tbv11dSJsgpbsuUeeGYOyNq2FG6M/AzGexBVL+Isru/cBu2RYjNwEwFdjUbvVUXrmhJ4eLorj5+9vaGcgrQbXFcatfr6Qv4tdCuK86YLAXMxKkkTFcwQ0blAkVjU4YTAvpaXGBn/Ydj1Cu/4sqxYJU+UYUGUNJwFpaKA2hMPvmYaIFARl3cFJ+bPahcfr5ki+ExzRqjp6TGAQyw6T/Et0GjeNgFkhxMf/aQ8ry8IkQiP0hyjBGy7F2u7J7qJDET0JrTxNjZdRUQLGbSttETwPiopZD+UaObwcQfqXcY+eyujiJhMvT/lr2opE0P/kJRoctJ8vjkX70ao1Bnb19cCYJlKDzu9sPU6KR942Qyd4MdXwLI65fh5vPj9H1/Z1E4Sc0x7eZVAe1F3Lr3kEdAkvyL8ocnaEzbc1C/2C4WB3AIolIhP/HD6owLOSr9h0xwxqV2JlZFJEcGtKGw5ggpM1Wqu/pRYHzTGO/qbyFZOkyiNE1LGTWeYIY4VpeQDsCArZf5WeocTZvl3O1Xh4sUV1ydENvUZl3GhFSGk1t8SBjZTi1SBWwZlJfjGTtmw1JxePkL6O1RWSEFcxZsymAGYirFQkur/cKcKBaM3xJkvdSfhInzO1tcXFr9DcVrbOwkvJjb2pl+G2VQ/HiNths3VE9+se0FEfhrcO3hcW+fie8JQVyOerfhOA6GYnhz5I8TYbdluOkb+DLI40E2I6bbz+B8WoZuKo8J7wVOiK+9MEYoVdKiQRswdjASbDZvUlYB5Vr5zyhUZ65S4mwkQQO9IhWXoGdojE+OK1HX2cN3Jo33dk0ISWs4UAyN4TZmwr4AAjMkxQNKFK0yE5NfZ55zZxP5k4kbxwKcJGtFA8P5p9imfainJ+dCb3wjaO4MdaCKnhzGRr+/H3eZ5xOCQ5LuUlaP8Z4MHcM4iLUCbDzCYgUqfSNI3EkujA32F0mGvV4bfx6iHFeOMOtyFnUMRYzp6o82iDEvJ8BGmlsoa9Keoj3/Bup32FQlHyS54EHqoGWjl8CKuedrD7geQEqv7/OShyBx0hwy74BzXlMFA2hnAnUgdXv+7AHjgRql01KipJNZ0NQl4fE/B/hiIFV8OQzKBhdEuROA4AKBk44yo1x9AJlv7vwcb8GhGGjzDPyClkqKxkbhjP8j8KrKx9In/HBC97uk/iP+5pPaE4n0QlpxZl9Ls7eWhhdk+incmMHrlRAuQyoTVJkfBZkLn6pHugIZkj6bhJLSX8NRohVxRoOAhvyXVAPz1N/koyuD9wS1YQi78GhGwd8bEDMXiCJoMXVP4jp5QSDxhrmytCW4OSU+whuOev1m3qJ8K75QtgECUcqx1vJMrHwuMHFOBNZagqoJuMIvmYsmCUiFBkWlKREjLA4EAuDgTaTYFpMLmrq0KnwHDoTv8pXLsA7Xd0szWSqrNQlBjDoG053mbzI5AEzATM3CF0sJaV17p1Jwj6vlM5kA5kXh4K9g3U1dDOUAhC+0VlAIYGgBAfyn0p4BT70qzj1mdFLrEbzhqn5Xmtzq3ChRHqEGR7t/Ci+N7YXUcVmXZwGZJeK4JqCgYg7BDGp8W1w1AV6Oo9gGB9z9AHpnF0AoOQqJjpKRN6nq1GFHgQDYrlJZLrIwVLBGakqbcKvkFHaqXVqeplD1VfK+YuyEgIavHJhUIcrgKctK5SmC86huoCE4fkp1MHBbSbik1NBP7Aux0n9ScmGgkwZQ0H5yHVJExpyHNmOeEql8RrnTsHrT+x2NL5oOfE5GiegYxIJzWpgiDVBQoXEQWMup1ahahK6Qz1AN3yh0VCAU7cOIJ3CiNGs4659QqNRp4S7mWMOfqkRM1TAG/jzNJFcOWHzNOJqU4Z4jBUi1hyMNwiNn4jdGBgPPhBLikzHTFuW78lOdZhMmHpJTBOU0n64WWuPSx0FEoQXUVnTE+UXzYDzW7cYialtTEiglKx8CVLgEf/dYr98Jd+AQteeRkubo8zAZfiFKXAMjzSU0S6i63WggS/p0I8wxTffb4EBPiDDdalBc1vDUApIdoavgVHLtSD3WU4gRSNhe8A13HeosDngt3ZFypMGhLLrk3b4cXeq2r3ykyj8O+CCKwOUUYwCJXAZTPsALi+0XBRbEKxgvdF4pJMYpS3lX/GPps8fZF/4q/IhjsjM+/TYlA5fugEXxNJ/tIEB6wl0UrekUi35629JINyE7g4eGyWyij7puK/i3MmC9EZOg8Mi0TBv1y4waYCvnw6W/9cXzMbfXE+Y51tk6tOR1NgHz0LJyN5TABZL4Wj028sC7rTg9w1fuNjA2FA9aagTChMf2GpfaXRFRuRNJOUum0tactUmpOYIavlBu7miWXk19o2zXtbkMtSIzZWEOoVxj1cuMEFmxxQbPR+NuN8EPJBP39zMd0VEq8E+DxARgx8u4O1+lzzPLalxr17PpF9qRtb3OHPHylwveF/FFcreYqbsvbquWg2Mi+ciW1W0biEbORvHGlhem0Oxw2hplxrZsvqMB5GKWljctz6gmzo0jdKXB0w3QX5Z3wpZRaY/NxVvHsYHOIUN4ds7/RMK79xTaUxcEQ7zPqGIRPgZno6l+mKOH5Q8qDSmAjcH9pE3fhixZZjsdSy8bmqJsoMHTR01cOyBIGynJAFMkXlKJykJ2Lm2Xt8ZSIW7XH+FGpYKhSTSf57YYB7VGm/RM1Evn7HZ75ERDbzhnvbSaKI/x4PA4r1IpfW4Ep9WIZi5VqRpyc61GK1fP1VJsbvGiI9qfV06cy4Q9VSiH7UbWCVAa9D74sq5/VfraGVa+PgpUtkVO/zeTiC3KdIZ6E4EiPU1IkFsc4esezwX5wMXg76aveCf3uGzd0A80aiKUvhNZUjh7jEnqLx/Bcz5OiI/T1qUU2kaDdBU0RIE6CEupEbKBy1Jag8ZkdlD2j8F2pJqB0yRQJAWUPSPchpRF6EO21MBngbLMvjBeapaCvQybgE2ETw8Dy6fcFv1Fe3nhM9vv8KwSm1Q+1pLdMu4ltpZLjaQu+bIxVtFcuuru4quWICgOzrqeuKY2qLJq/OiiyN1xD0Wi9y+cmT/RXvPoote6PO2ko8bcHn464ONpwHj5xpUgrVU8jXYhCkDrFBuk0GN/WoRvkwVrU20PN6bYad+BsYa7igwEzj4wdRTnkWOLEoWHxBOZ5MOTHYxaCpuOA5MOhT9/4REa67a80nlZNU2qqhJMXWjL4qfBDGBEU8wF8kBdV4pzdqH3KgFwoAaqImi2xR4FxJktUpJGgcihehf+IXQb5SfjBYycXEhicibHZ2Auo9pklPlFG0kTnNYsXKWKZWqdGA86WBJ2aj2plFtqbgYMCUfX6KsfcTQ3g3yX3fswdBp4G+zWGXmhHIg7EZodTiDGCgyerueVkT3FMh6qkBXBJ0eVSHfBIiiU6ELhutacxnmSZVl0CI9CY4TNhtqmot5oN0cdSLDBYlz1W16JapcHet6fxpMCzW92e0eAxreUO1FhgUvD4YRYdtL7JQoZhCP2Y07bOAx4htSSgAAAAQMBYAAifS0z7psWkoMLu5us4HGZBUKY0BFZ+iI6MogjVRDiQEaJ2AQsGyJy1MlD8lMjAo0R6KVqm7YHN1xUAWroqLQt29usIE9W3zfFScHcR4VW7OzC5WUAyFt8a24M3fQMyIbpmMpjxesENAkjXzp1hNZaMRiMjnFFLaoIqnBV44Ej8Tnz3ZDHwhTs9c+Nm88BpUDTs6fEh/MNt7mNZZYdZFd8V57avMUPRT6xYK2Lo1z4pXt9+39n7lng365Gcl7Hy4SW+TjaDWH2wSmHiNYW7MhiRlBVwafrhkKtIcK9WAkLgpAzCMk0254YLEPfy+Hj8+2Iyx+iPFkma95EAE1hBxTcYmTcG/CGkFlDhKTDCgijpEoODzLuhCEI0YkW+ahmQuqXoq8QYf1tMlHo27uUWkNGFrCwvR2KUZOXp6bsTRHz32575oAWYsGwrISZ6FoZ8kYjpGhclhpd5ewbMNKIHiU5R7xtHdVVIWeKMwk3qoKTqkBxquOI1oKJWHGoLNFKBULQWpGNRIyirWqGkpcmLwHTBBBhekECE05QOMM341hA4SeUEQj/3+DsoUIyQFluW1bzrTaAJJxejYMRW0NLrdQ07ZfTWWalGbdfDkQlRv4ZwGJkoi0RR1TPlUYptmiUZeizNhSah6nAVxFghHRnvGuBXe5A+IUwWECMrNkw5fISNsreHqNyM3CguSKSH3GwHKsep9xLByDbp3f4u7AEayNzyMXTtTq1RO1wHAPrROI23KhtCU6SndnFsPowKmFDnKHmYV9XmKDALS9W/UMXtmjfn9BWt+861impNQK4h/1hDXpWnTclmW4AUNAEYv/+e7jtkLtzGRwqMlMjhbL/tYC4FjCiNZ4cxnhgAP8404hO6MnmMv4BvyyQ7SpCaSgwOlUjKGyAzjdaGNt5vdiDhJxZk8A/ZY4+0Xv3rE932oygv/QU3wnC4HLJDtkLjK6d/D5IO7dvSXzfoWb6xywwzpNSmsQ2dMM5mjrNCkodHFrqLq5BzsyCWHfhNJgiYhRCeUSeS88jlAsRoNM+b9G7/6IqdwtW6gOAVjXA4Eq950GEK61iuOPEgGLbMgGUK26KIJX4xR37g20KotAHUpDIyl3TB1wMSbiV4X/PyAixAyRDUqNbIKOWVTHlAU8o5+BpcdO2KQP0cvCkgb0lYGNK74ZAiQLARkMPsC45u9NDH9Yi//7QldfczwbMhnDAiX4W4POhBykwcYcUdyccSWKcjZMInJBUALTdRBvy35SQiB4U47VtuI759rzsooFBREDJo+wVmFlyIUpNyGnzuS7TyATUaDty08Qso0t29LqaBLp3iTU0BwE2URvjCRU8lQoAeaEx0MAloQA+4WA5pLtxo9ebAqBINEIHAYDyhHoCWOMjBAxR0jYjCIdYpY2HWdADLD5ar0RIzAq2gpiNwIwgvCx1Z27V5Fx0zUOwZgEpOGQgGoVRK2IhGKC4p+8SqTuAkKuTRSTcbQzb1Ru810yag4/S5tFHFj9Qul8ek8pIUhDFHFBKPQXCPLtk6rmIqOWR5odmBMPqLQwuNA1OxTp2IiKaxdD5rFGNONjgiyZGOsBSC3LBAYCbDwZOtlCUc0DUqzJB6uC3uKRB9CS7b16fLrD8bxVvY3a2Ds2lHF9D8sBlZN2qcA2LzlEYul5nK5dlwHS0BnqGztsTM6OkukHSNxz/SY3ST1y176w2vz7agieHUVXDRD1/NIUzZE7Ok5a9ZqI2+4a0r6BmzfSdgbRSVhYdn1KtLEanFRiJ6dOfOSFfGuPIOALBzUjHq0CB+i4FnmwadfdeDhmcEAUHD8kthuv1p2h2uTBpjXiKS/vO/FQg2UYlVYKv+UpIpbOcbuDcIkrJKqQsLvAiGKRoOhiow/+DsFPxd0QhgUYMnvLCmJjpOqAoAs40+slaXVQGoW7QiYqq6PpPoD2VV2ECDgzpUaoRypGv4mtWgPE8jY30jYyocRb1Y9DfFTUoyAAtKcOKsqAK4nzwwENw3hHrjPdKPYU/5j4XotrN31w+SZRghSdHv5UI1d1tXhzJAzm941kRyrCTzTzX5TICpMUwTT3ggmuaTbSvosQQ1IcdqAPUuf1Kh98CIBP/XHBOWqsSmxzeqluddzDEw18ZDIA11EDljCHty46DLbVt1+OOBgwovB2SohtVbDEICiLxyQNFf/IQCiL8nCdK1uwOMsXEX6CfPNk9mGx1Gs2SbFJkmDC8gKa6CS8/QIRhn4IzIQ5Hrx9q3uYkRFMspUGARbCQ78XyliilrQb4yihXZwkAhUWkqv0lS29mPHIVQ0ofwv4IlP0tpfxYc2SDDWax/XjzDqaTSupCLlnknqZYAPgnXSAe1pugHEGu/GoxMFpjmAE8EQaCJium461GsyviHtjY0u/y4ySLefK4WIZDY9jbckc0zeBvWPGM8RLQ4hLB39pPQTCTIRjDKuGbhHRn9HqtGCEDrClH+jkyxue8KTttGX7eDQas/vZY6lzUCNs6tOUHtOoaepjIloH/A7Ba3+ET2Tj7DBMV9XJkOMcYb4kTo2/XO0BdyKSGT7SPJeGAKBQIUIucwaLD07XUJipRrxlfGxpQ0m+OEA3rqh//QbZUjIkiib5M+ZkLOLiX+xl1tYeMkWWPE6A6KRloKCUptod2u3wngWK3rgZUIIrZU/j0dM6ykZ9iBMu9qgzR8W68twm3JBGthHNZdEhkUiWHtRLb6jGrG2Bmy3lcuBGDRVwmYic7ZThv4uXOl1NVC3+oEX0BTOCuBv9Uw1pk43aswgdsjTjpFcWDABpixeCxJPRpAI/4FFTnS5KqX5k/R4a8gnIOFUjU10+SDC4OTHyRpL5H/A4TGFCkx51EztiS1RIflPjEszhzbNjWrv2ibLDgoEkfLZazjX7y95+wJ2Omqsctsq2b84FUa6BVhJMwX/QBMR51Hv04A8kuW07Depc5zpj2GOnS43yhhkUpkWCWywt4zLjd5N88PaBbEmay8Llk+UqJXXS94mkdLuuJw3Iauh02H+oTgdWRpr6QYVg9JEcPQrF9UENQBN8yC2vBWMZ9vCiF3xXUOCdXzxqJeFFQAVSLFVOtTMsASroAvAhYBHxZFBAQLes4TYdo7KwvNL2fEsS1yBnDfXQZwMi5jIKWUW5RpCVmqxyhrnT7wBxSj4Asrr0wkOFAbJyLl9cXP6K7pCLwu4BlfbzFUwt3nKwitFWavgI8jHvJaAyvBAVWY4J4fEFiZrNHenBqz2rPO/aGE11YOgCsfni08tRi6NLJfSgrHEznD/RlbK3CQTkajIqNL/+NcGEWsUDXGoq6giyLtv7UsCf762/PzNGML8TonEFSZwEhVQdogf5klc8rArzr5fq3nQgJfv/N6xQ+HwUkpgxHV8tLRCpQ858XQIJGOECimmtGMms01sDh1aCwbLrI8HLcQh96YshDS44lHW3ZA0UbTMdOAtO0EbPmDcxUEHWIGIH7VleolONkWHZFth6f+AxSWFoeUTwSwON9EpbvUtB5a6C6VhBIINGSG2FmLGf2zagohSyNTUoFMgWKPk7ZtaDhpVG5tx1U9BWIuB25Wspwoz5B/eiLI0+IRayQDvEn7njhTCJgyMOMGR63vfgJxbiX4TyM8f+K/jvECkS/VJf1YPezzsqoaokQuGbb8UkHjBf6msaUwbwh0pdoDFHI+6RU+R8EgvzBEcKKvwMSHh0b4SA5uWaDFw4avZ4Z+odIfu+GwSbHQe0jDZGBHN6knbZPEC//EEI/1YeCQcPf7MJKTRzHSnbEljrmAFMasRZJswi8HX6HL0cnCMuZAe/KO1WAL2ko2NEGRMGuk5flYBHQosfBNSUjlKlF8hSYkiihhNmNZyVuYBDU2d59o0k5GzlABuB5QqrUxsnAR77VPfW1IeV1fYw8+cMDnhUDviUYwzIFd60Scy1V8v1h8SVnzYbfO83SiQq6EFWvvubvv0LxmBxAo9Qkw9KK83VbqbDIvGCr5QkrybyRu8YiZ1xnRx7eEEIKAo/CwvH4mLMeA6iItRx7pFcNS6iIY/riP1lAZiKtQ7WI1Xle4iayEXohy+YJEkS7Nms0hXuE2tjB4JM6GEpw+Dkj+h6dBlxQlz9Uhjm239bH1pAkn8G/ZYNCeOMbAhCaqAdA+O3HXimFbf2bMdbv2FM+pjL61sBYWdZkAQyxaUMXOHHJX7IFsqmL4EpAcTkPFwFiDiyYY7ClIuqEXSBJiUhO43dLE2GGdycmldCaAsMMMs1V/A3YRIYEROPQkjJucNhxTrtt4u2ln3kYYkopriMilSx6xsrpbEsOEzdR3FMr4FYAHC25rrPlsRfmkz2ISbHGjAXTevnSAb+RaX3Y3CxdcPSRNvtGGhp4PFFFJ/Q1YWkaxvI2iB5G0RlXDseuN5AwHA8QiKsmKIXfq6+62YM0XMO/W4s91zaUYipVgkfXJmQ4t7FrdBOErnyN5DIxa3y4wWyAGYAMLnoZP0NGaPrsOxEi3aaNiDZifjk4fJu8QjxfXetqMd5uZQS9umWAzaMU1yHwhmYNSzh75HaCuS9ISP6obWQq2iDlYqTVJxLq0JDPtCV7DzpCnGcUBuSbBJCstLJpcE9qaqj9C7xu1nj0Tm5JPbDYt03HRbhO0vE0E5TRAl69C83Zqj7q5RWYTVTplDpa43aIXlTL7xYoF4wrBnc7ppdRuYQoSMWOBcNdXauCU460yzA7nr/tNrggImoltDgiNAsprd/L0tFJFqcuWJShdZI8mCIFR4KXsoBD2wtOpJVZoRcF2hhuTUNT9MPJtoi0c3q1TnidU0BkWC/+iCGa4zHxhvdgi6jhBJdJ/DxfPjNdyArBg9z5Qj1T5sjbILafFJ3lcF07xFxd4MTL3CI/Q6NoNPeUQ5WESFfAmtadBYoLeQ96xJtIb0WFEd2qNtm8/OAj3MUOLpWpaO/jdlJNncGSo2l9XjB107gYaIlAhWrl63y64ZE7Ot+YkgwS7vYCEKD5kofFkwCa/a7Bty8ELhUu7+vq0amOrmmli/pICHLRgcNJxAH4H+TBGBr7pNJQ4QjgVL+nRAJ6+o0WtTz+Az3KQWaWsPaAV4ZP3pT0UTnvPkTw+vrx4uLygFC4/CX16aeoenJtuehWdmvB1gIf1stHzDVBon6uA5sSATUYaFWwE3X78VEnRuLS8g6XAnP+IuUVbDRYiKtsX/r/kxqkXVYGFt8E/ki3l92ArAu62BQkbkYLJUCWFqEEj23C5QWRTWCf4EJPGxhiyTLjkhL9sArs5HsL7RcUadTDqC2TE2o5B6tFuM8J8fl4TMqevciGHW5C7+gZNsOW1JYst81U22N4YXZif/n9KmK0E5+BltuLThyMlfwJq24C0fYhkTAj26kO2f0dCD3+Xh1emEPGVaSUOszLaOZyplNc8AlPPKFlt3NgADyAc3nQGnPVDAVB4+nri5IBTsHpzbEORBoO64eT+BxvzCuxQrfECJx8wIe8Ur6KJ3YZ9b7lOvJK+tmEUCTcLXU7wvN0NiDMy6k6ITKg5zBL1cqMCUjSYX4Ra52mQLZD0ddrsBUKBQPFn4mJXsWbnH6nTC4Dtr9jrt6OX/I7udnd4CG2Y2saMEFJM0ikXez3uA3d0x8CwMnEzRy44SsbID8Gkg5I7gn+ZAua/YZ50H8LPhr7DOuA4o4ucC8bw2qjGGVNyF3h9ySG75GYZkWSwDHZ9QnSYHgOZH6/XL6WN7RhDZLFeTR0VkKhQzFdhjLhveI5h0y4/SG1LHXHCU0fvSPkhc86xIJfs4WUByrdDvsJIgwBsUfDmcRK2eexDEhYkAf0OAwEkUWEfcx2Wsbmez1KJsKBdOiglbXA9wNYmMQxeGRYDIYyxhoCIUYVrWi+cqYnTKrxyKfggp+oQMk42OUBPugG5Zg6wFDHjkcgwHEPK6uB6S6XAyIoC4E3fgqM3BtA/GDUuxoEdyhktWYM35DWInGt4tL6Ig9EbG61pOJxvZ04JZr51/YzIkhzxzmzKkhYsmuM3KXqXddBWPJaSk0X5dTUlOhLu11AYbu6Ou4WMUQK5Yd9UTXrUnE6JKO3QLlUEDNFDFWVfaeGLoJLxODTMSoJS06VizDPvKVk/k6CcjcnZp+cnbnoH+1lECDNXNgD8hP5zFz936UlMWs4HIKEd1t0BqPNw+gVD8cYYOtXDjI5zqjYb/rvwt1qC3Dx0DmaNrjRluevVKG3w/dyRl0hpv7AVldSxE6Jo6At9xZIuGrYIkjVWNFf++KtC7GOF2PcXLdLECzUa4An3sy22AyHjFcUCHR7zb8m5Doxi2PSGqwhW5XxFw12zZABkxG6leoPEOKjQqwC2DD8cDxxyHtJytdyzbFHwDt44pgPiKk71ZjoKHkJXQgAJUksuq8tGTfLS2V5IwdSD0W7AzHqeoIrzOMkUuAnXALLj2rC07tEOKFSHwsRTeBccOozjoRed07g1Qk8vXGzUYXW1za+MmEZ8zMJPKZhT4aSZgqIV3P24EwNS98Qvdq7HRFAPSG94fBiTMy5p8gifG7eGdLeRexR0WvHLLVBQ/fkXQXMXoOhKjbST+4NthTa+IeKZtoQVyLR4kWH/ooQRy92kDcARDmEt5bHlkLdNiC9eiXpXL2zuijkhgoeVwgkLgphAm0PkSve1FsFCalVP+Mbuq/JICOJCOUGoEQAM6ELAcg+rQR4uAtptfG9FnGEOzXbn1cQgSwHh924BzD+5AIYqUXsviOG5hj+l+CbX4irJBHK9o+YKA95SZVb6melPnloY5lcFA7wa0PXeFm5B74swHbNZdNxcRP4TPVGmfiBqNew6GJ1l6FluODgIRBAYAv4QldnC6YgVKwK5QNKG5cnjadZ9qFtsQasWru6/FUAW8FoGU4lGPf+8uPCSaoMiJ/mfgZqSDM+CNiMMjFdIoV8bzCVAnIQXMR6Y47i7OHllVW2CTpT3za+1E0HBkBphggd7KpofglpHOCl6FE4w5KRSYySBGioB72XZv16HOa4Dpo+AUkHdnAdLCklO//qp1sw2Y7HXU4sA/wc8k6/zSA/bxdw8VTMJkh9qpreznzhYEObyDNucMKwEWlUES3oFdCzDX42jqVDbpAuFhGDJYIJhodyH9ZfqfPcQC71MOJuON6/B9Rz7EHTmGfGTEFIIzpdo8aCTYytrmxHoJUziabD/zA0w5XHlIeqeTCEZ6ZmGkUzIKE9JUemiOGfSmYiBPmbaFR45gTOYMZaEUZL3PHRWEC0gu9vs34Z9uisELu2yTLy8/6jvNe9QNUsgiKNhuPhsHxBdgzRuWxEI9m2iiCSXLByxdiXzYwg564KJqNgyAHSKS9CYJxaaMBKogRaerOxJfe2TlRnS0DEkky1wSlMl3SOdW6Ergbm0LIh2LB+uMzCncQT4Fza4oXxxBdW3voRLzoe+bb82n5rOBRhZqJe395K29eAkQlj/a2//0vBAG9Jl4SBfbQBjCt+i4VJ+/e4CpyhdiTg2zCC/Z9ds4MpqndihTSpBlPj6Cb+On8T/TyqVBT9E2nAVxhKwK0ODHVEnltLtsOQNuvinbvgzDsJip8si314a40rxB3PO8hVluAbayNzYaRuRCgHj9YpMeyr7oHHQLrqCtdqM8TjeXAm65EtjPtmIkQkmjctRC6LOVoHdjgqAKVmVVMFgGb1fIoKpqc2FNtxL2mMti/ExhD0QkUqMpVR76YBMltA1dIAeWaqTcUBq25xjXVLKXCXjuJHzi35UEEnDFCX1QKaFU+MLscYsuzrJK27KjDC70XAY583b0WpM2SXj7/LzlHX4EcBKNGlgOeGC9UQBPFAEy6zfj98pS1NZPdEz4ZCSvJ55aZSjUk9+go+4BPyabVOxLuYQS3bR5zom3ZEDick0tzLu4dRF4Iw13k5ipZAncAEQwFtCvvBvOBhshNmuXt2oYItNCl+EhRpn39JNjb5iCkMSADR4qEOE3iETie1u2k/QRqICDTTM0NKx46LF6KhloE6yfcizSwDICTskqHAdiLpHGbqqxLUUgwC8ioKlplpHEsfoleWAw4kooAXAihYVgRHIAOGuE5XdQSHRvw7xTlBT8wQvE7+75gWTgAYtFu9QABs0yB36z+An4FHYlUd4uv1fK7r7vCS7ERtIeuMMw+dLMTAuFmJGLjx4K1qUYv2p3XJHXwtZw8hiw8Qlyy1B22KweA0diVzwSWPMl0HeYZ86Hh07NXJQxRz7U5L2SYTCK7cBRbZJlgSy46VPv3rp2YpaVY+rg4qa/+0lXsbggx8TwJWZzxpQv9y3DKYicAkIXR0wfQaYDWuIJ6DyniRFeZiMijxCjMgZ0SSaweFHaBIgoibaZL6BqKadShZMHpxtEAWo7KPFmIneIScxTLFPemDCGnhJC9zpBNXY2VpIOHJBxIkBMbHJOz4AEiXAuhDmMeXe2XRtJhWg2lAgwTaIvg3NCMIK4CmK4AcZH9YuICAReaSaHnjhpDa440hjED0kHn9PKLEugLUSyfaEhhBC4uHZPZvUKfplevxIOPvwXCiNl//FBEfHCNuiSNCFLMXU9LGAfWhy5VxDr+/haL2iAZ4vQI5lqNpLIGB+EQU4IVYPyKNETokFZdx33GOd1RDoH/TkUEnKkcnYupimjZKZtDS6tCU7eI2RmNNCl8jRBpGF2v/3OUfkF4wxEWgDzdc4NkCf/KewjGTREvW5OcpXryOp2vbyrDrYy1kkhsPawHHfKPglUUdpaW4cVU6Guf0ZEAglQVzYRg70FTnd7HcNEs9BOJ7SmbDCm940jBfowAeKcY560ZNGPQpDV0tOXMpwbdOITcl0QdDjPTNdx2xCa1cv+QgjxZ8b9iQnCt1+1SnNXPoh+wAhATiQbeA3cR0KnXWDA8IAUg6ejrHOmy4MQHFI7AYSnsUDwpQoCAK43QAtU9gQO4uw0CN4O2oqgTHxKIgXCLwNHG8ddpM6tjADkhWXMpKkIDzQlH+yZCgc7w4y1WLSkJdwLsOgpcp41gOJDOBZNC8FG1kREIHXSrJF6YZGLtn3dDgyqLj0hXV1OG3ZPR0I7ei8XwIApcOJVq2B0gN2WuY+2EUCm8m/NISZ+NRoIWsVumAg+Jc1inFEv1kOKgp42jCFAYDyuuLKUOMU3Sc41LeDVeG+mM6raOloEAsTUgCj1QcMSKJGWozWssBhaGQfEPMrb/RryuqcvhqS1ptew74BsGi9s0szR/UlrCa5AUY+sKTJ1PvsArm4+/7CarBvkv7ZwtNUmjzENk/fUeZtLchDZnuIjS0fyIiLIXC8RxI4yd5liStVy9aikQg60zDXqaDxvVYpRxI+78CN0iOIKW2tMYGrYDNQRo5XtBW31mqbB50yysMdtcb4UKsjhrcDLDuoMXL3c/U7Bn9o5vsOHuTkDxlhgeRBoWcwYzBIfawGPcjM+iILXbCrQiEqYumwCxagQ+DI1Z5RMxqaB1kE4AEoB4ikR1AHULJs9YSydisq0yuAZz0erADCdoh/z1Ydizds2o16c1DaxKcZcl2uOJjBAXhnYexdiwNRkYuqbrwRRxJTlBNHMCVzznK55pooOjBExodXZZRI5PyLlCSH4ZsLvOCCQeH8PMdPDqovXVllTl5/ZJK6qaMYA84eiMli6kfTMRiLKxexlbswGHPlRKIksMQB+Ktk3nGVoeyCKnvoj7MieVUjIl3483qWZllZ3z4or/YtBRTYJ45slzYdVHdrCW7s4vGacjK3W8WsukxxpwOlS0Q/ycrCDdUNUlCUOxzWj7eZ0jdnl1kMAqsKwLuYDoBqI+OXh43CKQ9XbTmbQjOKxPTEbqaEKGNCkkKV9N6k6MoIIkz/VH3ikAsK2+tGf8LXY3rTjVn3K2xc0oOsYNU1A++xa2gJtcchCHRH6v+PwFIANE6PbqIvI2EQL/QNDsjQN8vIbszw1LwowMRXMLYDmMd0SKVsTZJAzFeXHFcsD4AUIbYl4YHdfWdeL8QA5Dibw4NBxWnjMgvdbQV2PQffyz+uE+kwA81e3DA8Diajt5BEJ0Bzg2viKyDKibtZACrr7TTwRxzYhMdd95r5cuA0bnM9HvmNifbEQ9G98vXYsYJFn4Wzqi8aiNUZidvowP7R8auTWTc8SzM39xFstgURkuwTmz2uZ5pw0eShsxgvmBRhUhz928qzBnmQH8LwjicgaGc41hb0h832L6jRAh5+YE4zww7sf13audDBbeegXCfiVw0DQc9wJQ7wjzcUnwl86qCM6CmR0IYA1IC1msypAHYg0NNPVnMyaf9UZ7vgn5t6vEOgAheNndpwESQZObGVC9MT9PaI9QL8YQJ8o2nHNJQL5MbqNce7nwlgmvkR+ZjM+X5xgYcOtiCX1WxrEQXDyIjU9pVCEq+3v6sCFhWpoFtdVjNxdYojpa8dXRu0BlgRQpmElBcxkPyoiX5QP0doQRU57BiOWzu3h8HL7WwsEnn5vSJWZ3oBSMoZTazwYUuAz8Rigel6ywepGwyVahACGNB5FE4w30/Z64Z1ORZbcVGnfHpdvlFAIFgwLylJsJIyB5ZAMgRaWPtJg6ttv0YoFZmcVGhFWMf7uv8btxJgysTg8rM5tYI1aQYuaF+agCiGTGVqkDk09JBpT9pt1aRuIjX4I9skU66m6l+KHPbIcSdIV+G2uTehrYpf292y52WXOU/gsKdFz6ePyWHi8ycgIybBxrbKd2ZmdLRbvCRxEVJWmUopwnrI1wYlYkrp+QkgQNgxLBgxoLsySqIev3KUSMne4QxpJ15IBPzMy/xAHfEnEZCYA=="); + src: url("data:application/vnd.msfontobject;base64,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#iefix") format('embedded-opentype'), url("data:application/font-woff;base64,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") format('woff'), url("fonts/proximanova-regular-webfont.ttf") format('truetype'), url("fonts/proximanova-regular-webfont.svg#proxima_nova_rgregular") format('svg'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'Proxima Nova'; + src: url("data:application/vnd.msfontobject;base64,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"); + src: url("data:application/vnd.msfontobject;base64,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#iefix") format('embedded-opentype'), url("data:application/font-woff;base64,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") format('woff'), url("fonts/proximanova-semibold-webfont.ttf") format('truetype'), url("fonts/proximanova-semibold-webfont.svg#proxima_novasemibold") format('svg'); + font-weight: 600; + font-style: normal; +} diff --git a/java/com.sap.sailing.gwt.ui/build.properties b/java/com.sap.sailing.gwt.ui/build.properties index 9a1111b8996..3e0c785617a 100755 --- a/java/com.sap.sailing.gwt.ui/build.properties +++ b/java/com.sap.sailing.gwt.ui/build.properties @@ -2,7 +2,6 @@ source.WEB-INF/classes/ = src/main/java,\ src/main/resources output.WEB-INF/classes/ = bin bin.includes = META-INF/,\ - WEB-INF/lib/gwt-servlet.jar,\ WEB-INF/classes/,\ WEB-INF/web.xml,\ AdminConsole.css,\ @@ -15,6 +14,7 @@ bin.includes = META-INF/,\ com.sap.sailing.gwt.ui.Leaderboard/,\ com.sap.sailing.gwt.ui.LeaderboardEditing/,\ com.sap.sailing.gwt.ui.UserManagement/,\ + com.sap.sailing.gwt.Home/,\ crossdomain.xml,\ images/,\ reset.css,\ @@ -28,7 +28,7 @@ bin.includes = META-INF/,\ highcharts/,\ Spectator.css,\ fontface.css,\ - webfonts/,\ + fonts/,\ com.sap.sailing.gwt.ui.Simulator/,\ Simulator.html,\ Simulator.css,\ @@ -53,5 +53,8 @@ bin.includes = META-INF/,\ AdminConsoleDateTimePicker.css,\ qrcode/,\ js/,\ - AdminConsoleProgressBar.css + AdminConsoleProgressBar.css,\ + Home.css,\ + Home.html,\ + base.css output.. = WEB-INF/classes/ diff --git a/java/com.sap.sailing.gwt.ui/fontface.css b/java/com.sap.sailing.gwt.ui/fontface.css index 53efbf90d8a..6fa84064820 100644 --- a/java/com.sap.sailing.gwt.ui/fontface.css +++ b/java/com.sap.sailing.gwt.ui/fontface.css @@ -3,8 +3,8 @@ @font-face { font-family: 'OpenSansLight'; - src: url('webfonts/OpenSans-Light-webfont.eot'); - src: url('webfonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-Light-webfont.woff') format('woff'), url('webfonts/OpenSans-Light-webfont.ttf') format('truetype'), url('webfonts/OpenSans-Light-webfont.svg#OpenSansLight') format('svg'); + src: url('fonts/OpenSans-Light-webfont.eot'); + src: url('fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-Light-webfont.woff') format('woff'), url('fonts/OpenSans-Light-webfont.ttf') format('truetype'), url('fonts/OpenSans-Light-webfont.svg#OpenSansLight') format('svg'); font-weight: normal; font-style: normal; @@ -12,8 +12,8 @@ @font-face { font-family: 'OpenSansLightItalic'; - src: url('webfonts/OpenSans-LightItalic-webfont.eot'); - src: url('webfonts/OpenSans-LightItalic-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-LightItalic-webfont.woff') format('woff'), url('webfonts/OpenSans-LightItalic-webfont.ttf') format('truetype'), url('webfonts/OpenSans-LightItalic-webfont.svg#OpenSansLightItalic') format('svg'); + src: url('fonts/OpenSans-LightItalic-webfont.eot'); + src: url('fonts/OpenSans-LightItalic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-LightItalic-webfont.woff') format('woff'), url('fonts/OpenSans-LightItalic-webfont.ttf') format('truetype'), url('fonts/OpenSans-LightItalic-webfont.svg#OpenSansLightItalic') format('svg'); font-weight: normal; font-style: normal; @@ -21,8 +21,8 @@ @font-face { font-family: 'OpenSansRegular'; - src: url('webfonts/OpenSans-Regular-webfont.eot'); - src: url('webfonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-Regular-webfont.woff') format('woff'), url('webfonts/OpenSans-Regular-webfont.ttf') format('truetype'), url('webfonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); + src: url('fonts/OpenSans-Regular-webfont.eot'); + src: url('fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-Regular-webfont.woff') format('woff'), url('fonts/OpenSans-Regular-webfont.ttf') format('truetype'), url('fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); font-weight: normal; font-style: normal; @@ -30,8 +30,8 @@ @font-face { font-family: 'OpenSansItalic'; - src: url('webfonts/OpenSans-Italic-webfont.eot'); - src: url('webfonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-Italic-webfont.woff') format('woff'), url('webfonts/OpenSans-Italic-webfont.ttf') format('truetype'), url('webfonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); + src: url('fonts/OpenSans-Italic-webfont.eot'); + src: url('fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-Italic-webfont.woff') format('woff'), url('fonts/OpenSans-Italic-webfont.ttf') format('truetype'), url('fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); font-weight: normal; font-style: normal; @@ -39,8 +39,8 @@ @font-face { font-family: 'OpenSansSemibold'; - src: url('webfonts/OpenSans-Semibold-webfont.eot'); - src: url('webfonts/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-Semibold-webfont.woff') format('woff'), url('webfonts/OpenSans-Semibold-webfont.ttf') format('truetype'), url('webfonts/OpenSans-Semibold-webfont.svg#OpenSansSemibold') format('svg'); + src: url('fonts/OpenSans-Semibold-webfont.eot'); + src: url('fonts/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-Semibold-webfont.woff') format('woff'), url('fonts/OpenSans-Semibold-webfont.ttf') format('truetype'), url('fonts/OpenSans-Semibold-webfont.svg#OpenSansSemibold') format('svg'); font-weight: normal; font-style: normal; @@ -48,8 +48,8 @@ @font-face { font-family: 'OpenSansSemiboldItalic'; - src: url('webfonts/OpenSans-SemiboldItalic-webfont.eot'); - src: url('webfonts/OpenSans-SemiboldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-SemiboldItalic-webfont.woff') format('woff'), url('webfonts/OpenSans-SemiboldItalic-webfont.ttf') format('truetype'), url('webfonts/OpenSans-SemiboldItalic-webfont.svg#OpenSansSemiboldItalic') format('svg'); + src: url('fonts/OpenSans-SemiboldItalic-webfont.eot'); + src: url('fonts/OpenSans-SemiboldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-SemiboldItalic-webfont.woff') format('woff'), url('fonts/OpenSans-SemiboldItalic-webfont.ttf') format('truetype'), url('fonts/OpenSans-SemiboldItalic-webfont.svg#OpenSansSemiboldItalic') format('svg'); font-weight: normal; font-style: normal; @@ -57,8 +57,8 @@ @font-face { font-family: 'OpenSansBold'; - src: url('webfonts/OpenSans-Bold-webfont.eot'); - src: url('webfonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-Bold-webfont.woff') format('woff'), url('webfonts/OpenSans-Bold-webfont.ttf') format('truetype'), url('webfonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg'); + src: url('fonts/OpenSans-Bold-webfont.eot'); + src: url('fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-Bold-webfont.woff') format('woff'), url('fonts/OpenSans-Bold-webfont.ttf') format('truetype'), url('fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg'); font-weight: normal; font-style: normal; @@ -66,8 +66,8 @@ @font-face { font-family: 'OpenSansBoldItalic'; - src: url('webfonts/OpenSans-BoldItalic-webfont.eot'); - src: url('webfonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-BoldItalic-webfont.woff') format('woff'), url('webfonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), url('webfonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); + src: url('fonts/OpenSans-BoldItalic-webfont.eot'); + src: url('fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-BoldItalic-webfont.woff') format('woff'), url('fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), url('fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); font-weight: normal; font-style: normal; @@ -75,8 +75,8 @@ @font-face { font-family: 'OpenSansExtrabold'; - src: url('webfonts/OpenSans-ExtraBold-webfont.eot'); - src: url('webfonts/OpenSans-ExtraBold-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-ExtraBold-webfont.woff') format('woff'), url('webfonts/OpenSans-ExtraBold-webfont.ttf') format('truetype'), url('webfonts/OpenSans-ExtraBold-webfont.svg#OpenSansExtrabold') format('svg'); + src: url('fonts/OpenSans-ExtraBold-webfont.eot'); + src: url('fonts/OpenSans-ExtraBold-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-ExtraBold-webfont.woff') format('woff'), url('fonts/OpenSans-ExtraBold-webfont.ttf') format('truetype'), url('fonts/OpenSans-ExtraBold-webfont.svg#OpenSansExtrabold') format('svg'); font-weight: normal; font-style: normal; @@ -84,8 +84,8 @@ @font-face { font-family: 'OpenSansExtraboldItalic'; - src: url('webfonts/OpenSans-ExtraBoldItalic-webfont.eot'); - src: url('webfonts/OpenSans-ExtraBoldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('webfonts/OpenSans-ExtraBoldItalic-webfont.woff') format('woff'), url('webfonts/OpenSans-ExtraBoldItalic-webfont.ttf') format('truetype'), url('webfonts/OpenSans-ExtraBoldItalic-webfont.svg#OpenSansExtraboldItalic') format('svg'); + src: url('fonts/OpenSans-ExtraBoldItalic-webfont.eot'); + src: url('fonts/OpenSans-ExtraBoldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('fonts/OpenSans-ExtraBoldItalic-webfont.woff') format('woff'), url('fonts/OpenSans-ExtraBoldItalic-webfont.ttf') format('truetype'), url('fonts/OpenSans-ExtraBoldItalic-webfont.svg#OpenSansExtraboldItalic') format('svg'); font-weight: normal; font-style: normal; @@ -93,10 +93,10 @@ @font-face { font-family: 'OpenSansCondensedLight'; - src: url('webfonts/OpenSans-CondLight-webfont.eot'); - src: url('webfonts/OpenSans-CondLight-webfont.eot?#iefix') format('embedded-opentype'), - url('webfonts/OpenSans-CondLight-webfont.woff') format('woff'), - url('webfonts/OpenSans-CondLight-webfont.ttf') format('truetype'); + src: url('fonts/OpenSans-CondLight-webfont.eot'); + src: url('fonts/OpenSans-CondLight-webfont.eot?#iefix') format('embedded-opentype'), + url('fonts/OpenSans-CondLight-webfont.woff') format('woff'), + url('fonts/OpenSans-CondLight-webfont.ttf') format('truetype'); font-weight: normal; font-style: normal; @@ -104,10 +104,10 @@ @font-face { font-family: 'OpenSansCondensedLightItalic'; - src: url('webfonts/OpenSans-CondLightItalic-webfont.eot'); - src: url('webfonts/OpenSans-CondLightItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('webfonts/OpenSans-CondLightItalic-webfont.woff') format('woff'), - url('webfonts/OpenSans-CondLightItalic-webfont.ttf') format('truetype'); + src: url('fonts/OpenSans-CondLightItalic-webfont.eot'); + src: url('fonts/OpenSans-CondLightItalic-webfont.eot?#iefix') format('embedded-opentype'), + url('fonts/OpenSans-CondLightItalic-webfont.woff') format('woff'), + url('fonts/OpenSans-CondLightItalic-webfont.ttf') format('truetype'); font-weight: normal; font-style: normal; @@ -115,10 +115,10 @@ @font-face { font-family: 'OpenSansCondensedBold'; - src: url('webfonts/OpenSans-CondBold-webfont.eot'); - src: url('webfonts/OpenSans-CondBold-webfont.eot?#iefix') format('embedded-opentype'), - url('webfonts/OpenSans-CondBold-webfont.woff') format('woff'), - url('webfonts/OpenSans-CondBold-webfont.ttf') format('truetype'); + src: url('fonts/OpenSans-CondBold-webfont.eot'); + src: url('fonts/OpenSans-CondBold-webfont.eot?#iefix') format('embedded-opentype'), + url('fonts/OpenSans-CondBold-webfont.woff') format('woff'), + url('fonts/OpenSans-CondBold-webfont.ttf') format('truetype'); font-weight: normal; font-style: normal; diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Bold-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Bold-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Bold-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Bold-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Bold-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Bold-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Bold-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Bold-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Bold-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Bold-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Bold-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Bold-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Bold-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Bold-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Bold-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Bold-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-BoldItalic-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-BoldItalic-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-BoldItalic-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-BoldItalic-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-BoldItalic-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-BoldItalic-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-BoldItalic-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-BoldItalic-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-BoldItalic-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-BoldItalic-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-BoldItalic-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-BoldItalic-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-BoldItalic-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-BoldItalic-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-BoldItalic-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-BoldItalic-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondBold-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondBold-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondBold-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondBold-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondBold-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondBold-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondBold-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondBold-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondBold-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondBold-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondBold-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondBold-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLight-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLight-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLight-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLight-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLight-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLight-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLight-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLight-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLight-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLight-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLight-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLight-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLightItalic-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLightItalic-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLightItalic-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLightItalic-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLightItalic-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLightItalic-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLightItalic-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLightItalic-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLightItalic-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLightItalic-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-CondLightItalic-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-CondLightItalic-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBold-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBold-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBold-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBold-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBold-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBold-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBold-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBold-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBold-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBold-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBold-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBold-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBold-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBold-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBold-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBold-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBoldItalic-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBoldItalic-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBoldItalic-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBoldItalic-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBoldItalic-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBoldItalic-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBoldItalic-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBoldItalic-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBoldItalic-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBoldItalic-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBoldItalic-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBoldItalic-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBoldItalic-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBoldItalic-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-ExtraBoldItalic-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-ExtraBoldItalic-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Italic-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Italic-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Italic-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Italic-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Italic-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Italic-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Italic-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Italic-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Italic-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Italic-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Italic-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Italic-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Italic-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Italic-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Italic-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Italic-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Light-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Light-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Light-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Light-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Light-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Light-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Light-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Light-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Light-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Light-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Light-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Light-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Light-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Light-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Light-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Light-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-LightItalic-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-LightItalic-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-LightItalic-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-LightItalic-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-LightItalic-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-LightItalic-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-LightItalic-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-LightItalic-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-LightItalic-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-LightItalic-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-LightItalic-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-LightItalic-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-LightItalic-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-LightItalic-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-LightItalic-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-LightItalic-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Regular-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Regular-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Regular-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Regular-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Regular-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Regular-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Regular-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Regular-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Regular-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Regular-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Regular-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Regular-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Regular-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Regular-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Regular-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Regular-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Semibold-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Semibold-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Semibold-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Semibold-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Semibold-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Semibold-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Semibold-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Semibold-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Semibold-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Semibold-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Semibold-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Semibold-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Semibold-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-Semibold-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-Semibold-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-Semibold-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-SemiboldItalic-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-SemiboldItalic-webfont.eot similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-SemiboldItalic-webfont.eot rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-SemiboldItalic-webfont.eot diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-SemiboldItalic-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-SemiboldItalic-webfont.svg similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-SemiboldItalic-webfont.svg rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-SemiboldItalic-webfont.svg diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-SemiboldItalic-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-SemiboldItalic-webfont.ttf similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-SemiboldItalic-webfont.ttf rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-SemiboldItalic-webfont.ttf diff --git a/java/com.sap.sailing.gwt.ui/webfonts/OpenSans-SemiboldItalic-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/OpenSans-SemiboldItalic-webfont.woff similarity index 100% rename from java/com.sap.sailing.gwt.ui/webfonts/OpenSans-SemiboldItalic-webfont.woff rename to java/com.sap.sailing.gwt.ui/fonts/OpenSans-SemiboldItalic-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.eot new file mode 100644 index 00000000000..59dc21df311 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.eot differ diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.svg new file mode 100644 index 00000000000..a4d21ca1b82 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.svg @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.ttf new file mode 100644 index 00000000000..ce1cadabe66 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.ttf differ diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.woff new file mode 100644 index 00000000000..63826fd4662 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.woff differ diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.eot new file mode 100644 index 00000000000..49e4b872322 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.eot differ diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.svg new file mode 100644 index 00000000000..96e7c2348ae --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.svg @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.ttf new file mode 100644 index 00000000000..d29cc757487 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.ttf differ diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.woff new file mode 100644 index 00000000000..0e2f85a8d55 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.woff differ diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.eot new file mode 100644 index 00000000000..ec807c8c36d Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.eot differ diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.svg new file mode 100644 index 00000000000..f55eb7b85fb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.svg @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.ttf new file mode 100644 index 00000000000..f212bc1a5e4 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.ttf differ diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.woff new file mode 100644 index 00000000000..e70b64148c6 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.woff differ diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/mootools-adapter.js b/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/mootools-adapter.js deleted file mode 100644 index f4a9a3ccda8..00000000000 --- a/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/mootools-adapter.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - Highcharts JS v2.3.3 (2012-10-04) - MooTools adapter - - (c) 2010-2011 Torstein Hønsi - - License: www.highcharts.com/license -*/ -(function(){var e=window,i=document,f=e.MooTools.version.substring(0,3),g=f==="1.2"||f==="1.1",j=g||f==="1.3",h=e.$extend||function(){return Object.append.apply(Object,arguments)};e.HighchartsAdapter={init:function(a){var b=Fx.prototype,c=b.start,d=Fx.Morph.prototype,e=d.compute;b.start=function(b,d){var e=this.element;if(b.d)this.paths=a.init(e,e.d,this.toD);c.apply(this,arguments);return this};d.compute=function(b,c,d){var f=this.paths;if(f)this.element.attr("d",a.step(f[0],f[1],d,this.toD));else return e.apply(this, -arguments)}},adapterRun:function(a,b){if(b==="width"||b==="height")return parseInt($(a).getStyle(b),10)},getScript:function(a,b){var c=i.getElementsByTagName("head")[0],d=i.createElement("script");d.type="text/javascript";d.src=a;d.onload=b;c.appendChild(d)},animate:function(a,b,c){var d=a.attr,f=c&&c.complete;if(d&&!a.setStyle)a.getStyle=a.attr,a.setStyle=function(){var b=arguments;a.attr.call(a,b[0],b[1][0])},a.$family=function(){return!0};e.HighchartsAdapter.stop(a);c=new Fx.Morph(d?a:$(a),h({transition:Fx.Transitions.Quad.easeInOut}, -c));if(d)c.element=a;if(b.d)c.toD=b.d;f&&c.addEvent("complete",f);c.start(b);a.fx=c},each:function(a,b){return g?$each(a,b):Array.each(a,b)},map:function(a,b){return a.map(b)},grep:function(a,b){return a.filter(b)},inArray:function(a,b,c){return b.indexOf(a,c)},merge:function(){var a=arguments,b=[{}],c=a.length;if(g)a=$merge.apply(null,a);else{for(;c--;)typeof a[c]!=="boolean"&&(b[c+1]=a[c]);a=Object.merge.apply(Object,b)}return a},offset:function(a){a=$(a).getOffsets();return{left:a.x,top:a.y}}, -extendWithEvents:function(a){a.addEvent||(a.nodeName?$(a):h(a,new Events))},addEvent:function(a,b,c){typeof b==="string"&&(b==="unload"&&(b="beforeunload"),e.HighchartsAdapter.extendWithEvents(a),a.addEvent(b,c))},removeEvent:function(a,b,c){typeof a!=="string"&&(e.HighchartsAdapter.extendWithEvents(a),b?(b==="unload"&&(b="beforeunload"),c?a.removeEvent(b,c):a.removeEvents&&a.removeEvents(b)):a.removeEvents())},fireEvent:function(a,b,c,d){b={type:b,target:a};b=j?new Event(b):new DOMEvent(b);b=h(b, -c);b.preventDefault=function(){d=null};a.fireEvent&&a.fireEvent(b.type,b);d&&d(b)},washMouseEvent:function(a){return a.event||a},stop:function(a){a.fx&&a.fx.cancel()}}})(); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/mootools-adapter.src.js b/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/mootools-adapter.src.js deleted file mode 100644 index e92a7227c07..00000000000 --- a/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/mootools-adapter.src.js +++ /dev/null @@ -1,327 +0,0 @@ -/** - * @license Highcharts JS v2.3.3 (2012-10-04) - * MooTools adapter - * - * (c) 2010-2011 Torstein Hønsi - * - * License: www.highcharts.com/license - */ - -// JSLint options: -/*global Fx, $, $extend, $each, $merge, Events, Event, DOMEvent */ - -(function () { - -var win = window, - doc = document, - mooVersion = win.MooTools.version.substring(0, 3), // Get the first three characters of the version number - legacy = mooVersion === '1.2' || mooVersion === '1.1', // 1.1 && 1.2 considered legacy, 1.3 is not. - legacyEvent = legacy || mooVersion === '1.3', // In versions 1.1 - 1.3 the event class is named Event, in newer versions it is named DOMEvent. - $extend = win.$extend || function () { - return Object.append.apply(Object, arguments); - }; - -win.HighchartsAdapter = { - /** - * Initialize the adapter. This is run once as Highcharts is first run. - * @param {Object} pathAnim The helper object to do animations across adapters. - */ - init: function (pathAnim) { - var fxProto = Fx.prototype, - fxStart = fxProto.start, - morphProto = Fx.Morph.prototype, - morphCompute = morphProto.compute; - - // override Fx.start to allow animation of SVG element wrappers - /*jslint unparam: true*//* allow unused parameters in fx functions */ - fxProto.start = function (from, to) { - var fx = this, - elem = fx.element; - - // special for animating paths - if (from.d) { - //this.fromD = this.element.d.split(' '); - fx.paths = pathAnim.init( - elem, - elem.d, - fx.toD - ); - } - fxStart.apply(fx, arguments); - - return this; // chainable - }; - - // override Fx.step to allow animation of SVG element wrappers - morphProto.compute = function (from, to, delta) { - var fx = this, - paths = fx.paths; - - if (paths) { - fx.element.attr( - 'd', - pathAnim.step(paths[0], paths[1], delta, fx.toD) - ); - } else { - return morphCompute.apply(fx, arguments); - } - }; - /*jslint unparam: false*/ - }, - - /** - * Run a general method on the framework, following jQuery syntax - * @param {Object} el The HTML element - * @param {String} method Which method to run on the wrapped element - */ - adapterRun: function (el, method) { - - // This currently works for getting inner width and height. If adding - // more methods later, we need a conditional implementation for each. - if (method === 'width' || method === 'height') { - return parseInt($(el).getStyle(method), 10); - } - }, - - /** - * Downloads a script and executes a callback when done. - * @param {String} scriptLocation - * @param {Function} callback - */ - getScript: function (scriptLocation, callback) { - // We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script. - var head = doc.getElementsByTagName('head')[0]; - var script = doc.createElement('script'); - - script.type = 'text/javascript'; - script.src = scriptLocation; - script.onload = callback; - - head.appendChild(script); - }, - - /** - * Animate a HTML element or SVG element wrapper - * @param {Object} el - * @param {Object} params - * @param {Object} options jQuery-like animation options: duration, easing, callback - */ - animate: function (el, params, options) { - var isSVGElement = el.attr, - effect, - complete = options && options.complete; - - if (isSVGElement && !el.setStyle) { - // add setStyle and getStyle methods for internal use in Moo - el.getStyle = el.attr; - el.setStyle = function () { // property value is given as array in Moo - break it down - var args = arguments; - el.attr.call(el, args[0], args[1][0]); - }; - // dirty hack to trick Moo into handling el as an element wrapper - el.$family = function () { return true; }; - } - - // stop running animations - win.HighchartsAdapter.stop(el); - - // define and run the effect - effect = new Fx.Morph( - isSVGElement ? el : $(el), - $extend({ - transition: Fx.Transitions.Quad.easeInOut - }, options) - ); - - // Make sure that the element reference is set when animating svg elements - if (isSVGElement) { - effect.element = el; - } - - // special treatment for paths - if (params.d) { - effect.toD = params.d; - } - - // jQuery-like events - if (complete) { - effect.addEvent('complete', complete); - } - - // run - effect.start(params); - - // record for use in stop method - el.fx = effect; - }, - - /** - * MooTool's each function - * - */ - each: function (arr, fn) { - return legacy ? - $each(arr, fn) : - Array.each(arr, fn); - }, - - /** - * Map an array - * @param {Array} arr - * @param {Function} fn - */ - map: function (arr, fn) { - return arr.map(fn); - }, - - /** - * Grep or filter an array - * @param {Array} arr - * @param {Function} fn - */ - grep: function (arr, fn) { - return arr.filter(fn); - }, - - /** - * Return the index of an item in an array, or -1 if not matched - */ - inArray: function (item, arr, from) { - return arr.indexOf(item, from); - }, - - /** - * Deep merge two objects and return a third - */ - merge: function () { - var args = arguments, - args13 = [{}], // MooTools 1.3+ - i = args.length, - ret; - - if (legacy) { - ret = $merge.apply(null, args); - } else { - while (i--) { - // Boolean argumens should not be merged. - // JQuery explicitly skips this, so we do it here as well. - if (typeof args[i] !== 'boolean') { - args13[i + 1] = args[i]; - } - } - ret = Object.merge.apply(Object, args13); - } - - return ret; - }, - - /** - * Get the offset of an element relative to the top left corner of the web page - */ - offset: function (el) { - var offsets = $(el).getOffsets(); - return { - left: offsets.x, - top: offsets.y - }; - }, - - /** - * Extends an object with Events, if its not done - */ - extendWithEvents: function (el) { - // if the addEvent method is not defined, el is a custom Highcharts object - // like series or point - if (!el.addEvent) { - if (el.nodeName) { - el = $(el); // a dynamically generated node - } else { - $extend(el, new Events()); // a custom object - } - } - }, - - /** - * Add an event listener - * @param {Object} el HTML element or custom object - * @param {String} type Event type - * @param {Function} fn Event handler - */ - addEvent: function (el, type, fn) { - if (typeof type === 'string') { // chart broke due to el being string, type function - - if (type === 'unload') { // Moo self destructs before custom unload events - type = 'beforeunload'; - } - - win.HighchartsAdapter.extendWithEvents(el); - - el.addEvent(type, fn); - } - }, - - removeEvent: function (el, type, fn) { - if (typeof el === 'string') { - // el.removeEvents below apperantly calls this method again. Do not quite understand why, so for now just bail out. - return; - } - - win.HighchartsAdapter.extendWithEvents(el); - if (type) { - if (type === 'unload') { // Moo self destructs before custom unload events - type = 'beforeunload'; - } - - if (fn) { - el.removeEvent(type, fn); - } else if (el.removeEvents) { // #958 - el.removeEvents(type); - } - } else { - el.removeEvents(); - } - }, - - fireEvent: function (el, event, eventArguments, defaultFunction) { - var eventArgs = { - type: event, - target: el - }; - // create an event object that keeps all functions - event = legacyEvent ? new Event(eventArgs) : new DOMEvent(eventArgs); - event = $extend(event, eventArguments); - // override the preventDefault function to be able to use - // this for custom events - event.preventDefault = function () { - defaultFunction = null; - }; - // if fireEvent is not available on the object, there hasn't been added - // any events to it above - if (el.fireEvent) { - el.fireEvent(event.type, event); - } - - // fire the default if it is passed and it is not prevented above - if (defaultFunction) { - defaultFunction(event); - } - }, - - /** - * Set back e.pageX and e.pageY that MooTools has abstracted away - */ - washMouseEvent: function (e) { - return e.event || e; - }, - - /** - * Stop running animations on the object - */ - stop: function (el) { - if (el.fx) { - el.fx.cancel(); - } - } -}; - -}()); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/prototype-adapter.js b/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/prototype-adapter.js deleted file mode 100644 index b86bd67c98c..00000000000 --- a/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/prototype-adapter.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - Highcharts JS v2.3.3 (2012-10-04) - Prototype adapter - - @author Michael Nelson, Torstein Hønsi. - - Feel free to use and modify this script. - Highcharts license: www.highcharts.com/license. -*/ -var HighchartsAdapter=function(){var g=typeof Effect!=="undefined";return{init:function(c){if(g)Effect.HighchartsTransition=Class.create(Effect.Base,{initialize:function(a,b,d,e){var f;this.element=a;this.key=b;f=a.attr?a.attr(b):$(a).getStyle(b);if(b==="d")this.paths=c.init(a,a.d,d),this.toD=d,f=0,d=1;this.start(Object.extend(e||{},{from:f,to:d,attribute:b}))},setup:function(){HighchartsAdapter._extend(this.element);if(!this.element._highchart_animation)this.element._highchart_animation={};this.element._highchart_animation[this.key]= -this},update:function(a){var b=this.paths,d=this.element;b&&(a=c.step(b[0],b[1],a,this.toD));d.attr?d.attr(this.options.attribute,a):(b={},b[this.options.attribute]=a,$(d).setStyle(b))},finish:function(){delete this.element._highchart_animation[this.key]}})},adapterRun:function(c,a){return parseInt($(c).getStyle(a),10)},getScript:function(c,a){var b=$$("head")[0];b&&b.appendChild((new Element("script",{type:"text/javascript",src:c})).observe("load",a))},addNS:function(c){var a=/^(?:click|mouse(?:down|up|over|move|out))$/; -return/^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/.test(c)||a.test(c)?c:"h:"+c},addEvent:function(c,a,b){c.addEventListener||c.attachEvent?Event.observe($(c),HighchartsAdapter.addNS(a),b):(HighchartsAdapter._extend(c),c._highcharts_observe(a,b))},animate:function(c,a,b){var d,b=b||{};b.delay=0;b.duration=(b.duration||500)/1E3;b.afterFinish=b.complete;if(g)for(d in a)new Effect.HighchartsTransition($(c),d,a[d],b);else{if(c.attr)for(d in a)c.attr(d,a[d]);b.complete&& -b.complete()}c.attr||$(c).setStyle(a)},stop:function(c){var a;if(c._highcharts_extended&&c._highchart_animation)for(a in c._highchart_animation)c._highchart_animation[a].cancel()},each:function(c,a){$A(c).each(a)},inArray:function(c,a){return a.indexOf(c)},offset:function(c){return $(c).cumulativeOffset()},fireEvent:function(c,a,b,d){c.fire?c.fire(HighchartsAdapter.addNS(a),b):c._highcharts_extended&&(b=b||{},c._highcharts_fire(a,b));b&&b.defaultPrevented&&(d=null);d&&d(b)},removeEvent:function(c, -a,b){$(c).stopObserving&&(a&&(a=HighchartsAdapter.addNS(a)),$(c).stopObserving(a,b));window===c?Event.stopObserving(c,a,b):(HighchartsAdapter._extend(c),c._highcharts_stop_observing(a,b))},washMouseEvent:function(c){return c},grep:function(c,a){return c.findAll(a)},map:function(c,a){return c.map(a)},merge:function(){function c(a,b){var d,e;for(e in b)d=b[e],a[e]=d&&typeof d==="object"&&d.constructor!==Array&&typeof d.nodeType!=="number"?c(a[e]||{},d):b[e];return a}return function(){var a=arguments, -b,d={};for(b=0;b {a : 'a', b : {b1 : 'b1', b2 : 'b2_prime'}, c : 'c'} - /*merge: function(){ - function doCopy(copy, original) { - var value, - key, - undef, - nil, - same, - obj, - arr, - node; - - for (key in original) { - value = original[key]; - undef = typeof(value) === 'undefined'; - nil = value === null; - same = original === copy[key]; - - if (undef || nil || same) { - continue; - } - - obj = typeof(value) === 'object'; - arr = value && obj && value.constructor == Array; - node = !!value.nodeType; - - if (obj && !arr && !node) { - copy[key] = doCopy(typeof copy[key] == 'object' ? copy[key] : {}, value); - } - else { - copy[key] = original[key]; - } - } - return copy; - } - - var args = arguments, retVal = {}; - - for (var i = 0; i < args.length; i++) { - retVal = doCopy(retVal, args[i]); - } - - return retVal; - },*/ - merge: function () { // the built-in prototype merge function doesn't do deep copy - function doCopy(copy, original) { - var value, key; - - for (key in original) { - value = original[key]; - if (value && typeof value === 'object' && value.constructor !== Array && - typeof value.nodeType !== 'number') { - copy[key] = doCopy(copy[key] || {}, value); // copy - - } else { - copy[key] = original[key]; - } - } - return copy; - } - - function merge() { - var args = arguments, - i, - retVal = {}; - - for (i = 0; i < args.length; i++) { - retVal = doCopy(retVal, args[i]); - - } - return retVal; - } - - return merge.apply(this, arguments); - }, - - // extend an object to handle highchart events (highchart objects, not svg elements). - // this is a very simple way of handling events but whatever, it works (i think) - _extend: function (object) { - if (!object._highcharts_extended) { - Object.extend(object, { - _highchart_events: {}, - _highchart_animation: null, - _highcharts_extended: true, - _highcharts_observe: function (name, fn) { - this._highchart_events[name] = [this._highchart_events[name], fn].compact().flatten(); - }, - _highcharts_stop_observing: function (name, fn) { - if (name) { - if (fn) { - this._highchart_events[name] = [this._highchart_events[name]].compact().flatten().without(fn); - } else { - delete this._highchart_events[name]; - } - } else { - this._highchart_events = {}; - } - }, - _highcharts_fire: function (name, args) { - (this._highchart_events[name] || []).each(function (fn) { - // args is never null here - if (args.stopped) { - return; // "throw $break" wasn't working. i think because of the scope of 'this'. - } - - // Attach a simple preventDefault function to skip default handler if called - args.preventDefault = function () { - args.defaultPrevented = true; - }; - - // If the event handler return false, prevent the default handler from executing - if (fn.bind(this)(args) === false) { - args.preventDefault(); - } - } -.bind(this)); - } - }); - } - } -}; -}()); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/standalone-framework.js b/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/standalone-framework.js new file mode 100755 index 00000000000..0d2d45768d3 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/highcharts/js/adapters/standalone-framework.js @@ -0,0 +1,18 @@ +/* + Highcharts JS v4.0.1 (2014-04-24) + + Standalone Highcharts Framework + + License: MIT License +*/ +var HighchartsAdapter=function(){function o(c){function b(b,a,d){b.removeEventListener(a,d,!1)}function d(b,a,d){d=b.HCProxiedMethods[d.toString()];b.detachEvent("on"+a,d)}function a(a,c){var f=a.HCEvents,i,g,k,j;if(a.removeEventListener)i=b;else if(a.attachEvent)i=d;else return;c?(g={},g[c]=!0):g=f;for(j in g)if(f[j])for(k=f[j].length;k--;)i(a,j,f[j][k])}c.HCExtended||Highcharts.extend(c,{HCExtended:!0,HCEvents:{},bind:function(b,a){var d=this,c=this.HCEvents,g;if(d.addEventListener)d.addEventListener(b, +a,!1);else if(d.attachEvent){g=function(b){b.target=b.srcElement||window;a.call(d,b)};if(!d.HCProxiedMethods)d.HCProxiedMethods={};d.HCProxiedMethods[a.toString()]=g;d.attachEvent("on"+b,g)}c[b]===r&&(c[b]=[]);c[b].push(a)},unbind:function(c,h){var f,i;c?(f=this.HCEvents[c]||[],h?(i=HighchartsAdapter.inArray(h,f),i>-1&&(f.splice(i,1),this.HCEvents[c]=f),this.removeEventListener?b(this,c,h):this.attachEvent&&d(this,c,h)):(a(this,c),this.HCEvents[c]=[])):(a(this),this.HCEvents={})},trigger:function(b, +a){var d=this.HCEvents[b]||[],c=d.length,g,k,j;k=function(){a.defaultPrevented=!0};for(g=0;g=a.duration+this.startTime){this.now=this.end; +this.pos=this.state=1;this.update();b=this.options.curAnim[this.prop]=!0;for(h in a.curAnim)a.curAnim[h]!==!0&&(b=!1);b&&a.complete&&a.complete.call(e);a=!1}else e=c-this.startTime,this.state=e/a.duration,this.pos=a.easing(e,0,1,a.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update(),a=!0;return a}};this.animate=function(b,d,a){var e,h="",f,i,g;b.stopAnimation=!1;if(typeof a!=="object"||a===null)e=arguments,a={duration:e[2],easing:e[3],complete:e[4]};if(typeof a.duration!=="number")a.duration= +400;a.easing=Math[a.easing]||Math.easeInOutSine;a.curAnim=Highcharts.extend({},d);for(g in d)i=new n(b,a,g),f=null,g==="d"?(i.paths=c.init(b,b.d,d.d),i.toD=d.d,e=0,f=1):b.attr?e=b.attr(g):(e=parseFloat(HighchartsAdapter._getStyle(b,g))||0,g!=="opacity"&&(h="px")),f||(f=parseFloat(d[g])),i.custom(e,f,h)}},_getStyle:function(c,b){return window.getComputedStyle(c,void 0).getPropertyValue(b)},getScript:function(c,b){var d=l.getElementsByTagName("head")[0],a=l.createElement("script");a.type="text/javascript"; +a.src=c;a.onload=b;d.appendChild(a)},inArray:function(c,b){return b.indexOf?b.indexOf(c):p.indexOf.call(b,c)},adapterRun:function(c,b){return parseInt(HighchartsAdapter._getStyle(c,b),10)},grep:function(c,b){return p.filter.call(c,b)},map:function(c,b){for(var d=[],a=0,e=c.length;a -1) { + events.splice(index, 1); + this.HCEvents[name] = events; + } + if (this.removeEventListener) { + removeOneEvent(this, name, fn); + } else if (this.attachEvent) { + IERemoveOneEvent(this, name, fn); + } + } else { + removeAllEvents(this, name); + this.HCEvents[name] = []; + } + } else { + removeAllEvents(this); + this.HCEvents = {}; + } + }, + + trigger: function (name, args) { + var events = this.HCEvents[name] || [], + target = this, + len = events.length, + i, + preventDefault, + fn; + + // Attach a simple preventDefault function to skip default handler if called + preventDefault = function () { + args.defaultPrevented = true; + }; + + for (i = 0; i < len; i++) { + fn = events[i]; + + // args is never null here + if (args.stopped) { + return; + } + + args.preventDefault = preventDefault; + args.target = target; + + // If the type is not set, we're running a custom event (#2297). If it is set, + // we're running a browser event, and setting it will cause en error in + // IE8 (#2465). + if (!args.type) { + args.type = name; + } + + + + // If the event handler return false, prevent the default handler from executing + if (fn.call(this, args) === false) { + args.preventDefault(); + } + } + } + }); + } + + return obj; +} + + +return { + /** + * Initialize the adapter. This is run once as Highcharts is first run. + */ + init: function (pathAnim) { + + /** + * Compatibility section to add support for legacy IE. This can be removed if old IE + * support is not needed. + */ + if (!doc.defaultView) { + this._getStyle = function (el, prop) { + var val; + if (el.style[prop]) { + return el.style[prop]; + } else { + if (prop === 'opacity') { + prop = 'filter'; + } + /*jslint unparam: true*/ + val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b) { return b.toUpperCase(); })]; + if (prop === 'filter') { + val = val.replace( + /alpha\(opacity=([0-9]+)\)/, + function (a, b) { + return b / 100; + } + ); + } + /*jslint unparam: false*/ + return val === '' ? 1 : val; + } + }; + this.adapterRun = function (elem, method) { + var alias = { width: 'clientWidth', height: 'clientHeight' }[method]; + + if (alias) { + elem.style.zoom = 1; + return elem[alias] - 2 * parseInt(HighchartsAdapter._getStyle(elem, 'padding'), 10); + } + }; + } + + if (!Array.prototype.forEach) { + this.each = function (arr, fn) { // legacy + var i = 0, + len = arr.length; + for (; i < len; i++) { + if (fn.call(arr[i], arr[i], i, arr) === false) { + return i; + } + } + }; + } + + if (!Array.prototype.indexOf) { + this.inArray = function (item, arr) { + var len, + i = 0; + + if (arr) { + len = arr.length; + + for (; i < len; i++) { + if (arr[i] === item) { + return i; + } + } + } + + return -1; + }; + } + + if (!Array.prototype.filter) { + this.grep = function (elements, callback) { + var ret = [], + i = 0, + length = elements.length; + + for (; i < length; i++) { + if (!!callback(elements[i], i)) { + ret.push(elements[i]); + } + } + + return ret; + }; + } + + //--- End compatibility section --- + + + /** + * Start of animation specific code + */ + Fx = function (elem, options, prop) { + this.options = options; + this.elem = elem; + this.prop = prop; + }; + Fx.prototype = { + + update: function () { + var styles, + paths = this.paths, + elem = this.elem, + elemelem = elem.element; // if destroyed, it is null + + // Animating a path definition on SVGElement + if (paths && elemelem) { + elem.attr('d', pathAnim.step(paths[0], paths[1], this.now, this.toD)); + + // Other animations on SVGElement + } else if (elem.attr) { + if (elemelem) { + elem.attr(this.prop, this.now); + } + + // HTML styles + } else { + styles = {}; + styles[this.prop] = this.now + this.unit; + Highcharts.css(elem, styles); + } + + if (this.options.step) { + this.options.step.call(this.elem, this.now, this); + } + + }, + custom: function (from, to, unit) { + var self = this, + t = function (gotoEnd) { + return self.step(gotoEnd); + }, + i; + + this.startTime = +new Date(); + this.start = from; + this.end = to; + this.unit = unit; + this.now = this.start; + this.pos = this.state = 0; + + t.elem = this.elem; + + if (t() && timers.push(t) === 1) { + timerId = setInterval(function () { + + for (i = 0; i < timers.length; i++) { + if (!timers[i]()) { + timers.splice(i--, 1); + } + } + + if (!timers.length) { + clearInterval(timerId); + } + }, 13); + } + }, + + step: function (gotoEnd) { + var t = +new Date(), + ret, + done, + options = this.options, + elem = this.elem, + i; + + if (elem.stopAnimation || (elem.attr && !elem.element)) { // #2616, element including flag is destroyed + ret = false; + + } else if (gotoEnd || t >= options.duration + this.startTime) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + this.options.curAnim[this.prop] = true; + + done = true; + for (i in options.curAnim) { + if (options.curAnim[i] !== true) { + done = false; + } + } + + if (done) { + if (options.complete) { + options.complete.call(elem); + } + } + ret = false; + + } else { + var n = t - this.startTime; + this.state = n / options.duration; + this.pos = options.easing(n, 0, 1, options.duration); + this.now = this.start + ((this.end - this.start) * this.pos); + this.update(); + ret = true; + } + return ret; + } + }; + + /** + * The adapter animate method + */ + this.animate = function (el, prop, opt) { + var start, + unit = '', + end, + fx, + args, + name; + + el.stopAnimation = false; // ready for new + + if (typeof opt !== 'object' || opt === null) { + args = arguments; + opt = { + duration: args[2], + easing: args[3], + complete: args[4] + }; + } + if (typeof opt.duration !== 'number') { + opt.duration = 400; + } + opt.easing = Math[opt.easing] || Math.easeInOutSine; + opt.curAnim = Highcharts.extend({}, prop); + + for (name in prop) { + fx = new Fx(el, opt, name); + end = null; + + if (name === 'd') { + fx.paths = pathAnim.init( + el, + el.d, + prop.d + ); + fx.toD = prop.d; + start = 0; + end = 1; + } else if (el.attr) { + start = el.attr(name); + } else { + start = parseFloat(HighchartsAdapter._getStyle(el, name)) || 0; + if (name !== 'opacity') { + unit = 'px'; + } + } + + if (!end) { + end = parseFloat(prop[name]); + } + fx.custom(start, end, unit); + } + }; + }, + + /** + * Internal method to return CSS value for given element and property + */ + _getStyle: function (el, prop) { + return window.getComputedStyle(el, undefined).getPropertyValue(prop); + }, + + /** + * Downloads a script and executes a callback when done. + * @param {String} scriptLocation + * @param {Function} callback + */ + getScript: function (scriptLocation, callback) { + // We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script. + var head = doc.getElementsByTagName('head')[0], + script = doc.createElement('script'); + + script.type = 'text/javascript'; + script.src = scriptLocation; + script.onload = callback; + + head.appendChild(script); + }, + + /** + * Return the index of an item in an array, or -1 if not found + */ + inArray: function (item, arr) { + return arr.indexOf ? arr.indexOf(item) : emptyArray.indexOf.call(arr, item); + }, + + + /** + * A direct link to adapter methods + */ + adapterRun: function (elem, method) { + return parseInt(HighchartsAdapter._getStyle(elem, method), 10); + }, + + /** + * Filter an array + */ + grep: function (elements, callback) { + return emptyArray.filter.call(elements, callback); + }, + + /** + * Map an array + */ + map: function (arr, fn) { + var results = [], i = 0, len = arr.length; + + for (; i < len; i++) { + results[i] = fn.call(arr[i], arr[i], i, arr); + } + + return results; + }, + + /** + * Get the element's offset position, corrected by overflow:auto. Loosely based on jQuery's offset method. + */ + offset: function (el) { + var docElem = document.documentElement, + box = el.getBoundingClientRect(); + + return { + top: box.top + (window.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0), + left: box.left + (window.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0) + }; + }, + + /** + * Add an event listener + */ + addEvent: function (el, type, fn) { + augment(el).bind(type, fn); + }, + + /** + * Remove event added with addEvent + */ + removeEvent: function (el, type, fn) { + augment(el).unbind(type, fn); + }, + + /** + * Fire an event on a custom object + */ + fireEvent: function (el, type, eventArguments, defaultFunction) { + var e; + + if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) { + e = doc.createEvent('Events'); + e.initEvent(type, true, true); + e.target = el; + + Highcharts.extend(e, eventArguments); + + if (el.dispatchEvent) { + el.dispatchEvent(e); + } else { + el.fireEvent(type, e); + } + + } else if (el.HCExtended === true) { + eventArguments = eventArguments || {}; + el.trigger(type, eventArguments); + } + + if (eventArguments && eventArguments.defaultPrevented) { + defaultFunction = null; + } + + if (defaultFunction) { + defaultFunction(eventArguments); + } + }, + + washMouseEvent: function (e) { + return e; + }, + + + /** + * Stop running animation + */ + stop: function (el) { + el.stopAnimation = true; + }, + + /** + * Utility for iterating over an array. Parameters are reversed compared to jQuery. + * @param {Array} arr + * @param {Function} fn + */ + each: function (arr, fn) { // modern browsers + return Array.prototype.forEach.call(arr, fn); + } +}; +}()); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-3d.js b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-3d.js new file mode 100755 index 00000000000..c797c3a7c78 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-3d.js @@ -0,0 +1,41 @@ +/* + Highcharts JS v4.0.1 (2014-04-24) + + (c) 2009-2013 Torstein Hønsi + + License: www.highcharts.com/license +*/ +(function(c){function x(e,a,b,d){var f,g,h;b*=n;a*=n;var i=[],j,o,t;b*=-1;j=d.x;o=d.y;t=(d.z===0?1.0E-4:d.z)*(d.vd||25);var y=k(b),v=l(b),m=k(a),q=l(a),r,u,s;c.each(e,function(a){r=a.x-j;u=a.y-o;s=a.z||0;f=v*r-y*s;g=-y*m*r-v*m*s+q*u;h=y*q*r+v*q*s+m*u;f=f*((t-h)/t)+j;g=g*((t-h)/t)+o;i.push({x:C(f),y:C(g),z:C(h)})});return i}function z(e,a,b,d,f,c,h,i){var j=[];return c>f&&c-f>m/2+1.0E-4?(j=j.concat(z(e,a,b,d,f,f+m/2,h,i)),j=j.concat(z(e,a,b,d,f+m/2,c,h,i))):cm/2+1.0E-4?(j=j.concat(z(e,a,b, +d,f,f-m/2,h,i)),j=j.concat(z(e,a,b,d,f-m/2,c,h,i))):(j=c-f,["C",e+b*l(f)-b*D*j*k(f)+h,a+d*k(f)+d*D*j*l(f)+i,e+b*l(c)+b*D*j*k(c)+h,a+d*k(c)-d*D*j*l(c)+i,e+b*l(c)+h,a+d*k(c)+i])}function F(e){if(this.chart.is3d()){var a=this.chart.options.plotOptions.column.grouping;a!==void 0&&!a&&this.group.zIndex!==void 0&&this.group.attr({zIndex:this.group.zIndex*10});if(this.userOptions.borderColor===void 0)this.options.borderColor=this.color;c.each(this.data,function(a){var d=a.options.borderColor||a.color||a.series.userOptions.borderColor; +a.options.borderColor=d;a.borderColor=d;a.pointAttr[""].stroke=d;a.pointAttr.hover.stroke=d;a.pointAttr.select.stroke=d})}e.apply(this,[].slice.call(arguments,1))}var m=Math.PI,n=m/180,k=Math.sin,l=Math.cos,C=Math.round,D=4*(Math.sqrt(2)-1)/3/(m/2);c.SVGRenderer.prototype.toLinePath=function(e,a){var b=[];c.each(e,function(a){b.push("L",a.x,a.y)});b[0]="M";a&&b.push("Z");return b};c.SVGRenderer.prototype.cuboid=function(e){var a=this.g(),e=this.cuboidPath(e);a.front=this.path(e[0]).attr({zIndex:e[3], +"stroke-linejoin":"round"}).add(a);a.top=this.path(e[1]).attr({zIndex:e[4],"stroke-linejoin":"round"}).add(a);a.side=this.path(e[2]).attr({zIndex:e[5],"stroke-linejoin":"round"}).add(a);a.fillSetter=function(a){var d=c.Color(a).brighten(0.1).get(),e=c.Color(a).brighten(-0.1).get();this.front.attr({fill:a});this.top.attr({fill:d});this.side.attr({fill:e});this.color=a;return this};a.opacitySetter=function(a){this.front.attr({opacity:a});this.top.attr({opacity:a});this.side.attr({opacity:a});return this}; +a.attr=function(a){a.shapeArgs||a.x?(a=this.renderer.cuboidPath(a.shapeArgs||a),this.front.attr({d:a[0],zIndex:a[3]}),this.top.attr({d:a[1],zIndex:a[4]}),this.side.attr({d:a[2],zIndex:a[5]})):c.SVGElement.prototype.attr.call(this,a);return this};a.animate=function(a,d,e){a.x&&a.y?(a=this.renderer.cuboidPath(a),this.front.attr({zIndex:a[3]}).animate({d:a[0]},d,e),this.top.attr({zIndex:a[4]}).animate({d:a[1]},d,e),this.side.attr({zIndex:a[5]}).animate({d:a[2]},d,e)):a.opacity?(this.front.animate(a, +d,e),this.top.animate(a,d,e),this.side.animate(a,d,e)):c.SVGElement.prototype.animate.call(this,a,d,e);return this};a.destroy=function(){this.front.destroy();this.top.destroy();this.side.destroy();return null};a.attr({zIndex:-e[3]});return a};c.SVGRenderer.prototype.cuboidPath=function(e){var a=e.x,b=e.y,d=e.z,c=e.height,g=e.width,h=e.depth,i=e.alpha,j=e.beta,a=[{x:a,y:b,z:d},{x:a+g,y:b,z:d},{x:a+g,y:b+c,z:d},{x:a,y:b+c,z:d},{x:a,y:b+c,z:d+h},{x:a+g,y:b+c,z:d+h},{x:a+g,y:b,z:d+h},{x:a,y:b,z:d+h}], +a=x(a,i,j,e.origin),e=["M",a[0].x,a[0].y,"L",a[7].x,a[7].y,"L",a[6].x,a[6].y,"L",a[1].x,a[1].y,"Z"],b=["M",a[3].x,a[3].y,"L",a[2].x,a[2].y,"L",a[5].x,a[5].y,"L",a[4].x,a[4].y,"Z"],d=["M",a[1].x,a[1].y,"L",a[2].x,a[2].y,"L",a[5].x,a[5].y,"L",a[6].x,a[6].y,"Z"],c=["M",a[0].x,a[0].y,"L",a[7].x,a[7].y,"L",a[4].x,a[4].y,"L",a[3].x,a[3].y,"Z"];return[["M",a[0].x,a[0].y,"L",a[1].x,a[1].y,"L",a[2].x,a[2].y,"L",a[3].x,a[3].y,"Z"],a[7].ya[2].y?b:[],a[6].x>a[1].x?d:a[7].x0?(a[0].z+a[7].z+a[6].z+a[1].z)/4:(a[3].z+a[2].z+a[5].z+a[4].z)/4,i>0?(a[1].z+a[2].z+a[5].z+a[6].z)/4:(a[0].z+a[7].z+a[4].z+a[3].z)/4]};c.SVGRenderer.prototype.arc3d=function(e){e.alpha*=n;e.beta*=n;var a=this.g(),b=this.arc3dPath(e),d=a.renderer,f=b.zAll*100;a.shapeArgs=e;a.side1=d.path(b.side2).attr({zIndex:b.zSide2}).add(a);a.side2=d.path(b.side1).attr({zIndex:b.zSide1}).add(a);a.inn=d.path(b.inn).attr({zIndex:b.zInn}).add(a);a.out=d.path(b.out).attr({zIndex:b.zOut}).add(a); +a.top=d.path(b.top).attr({zIndex:b.zTop}).add(a);a.fillSetter=function(a){this.color=a;var b=c.Color(a).brighten(-0.1).get();this.side1.attr({fill:b});this.side2.attr({fill:b});this.inn.attr({fill:b});this.out.attr({fill:b});this.top.attr({fill:a});return this};a.animate=function(a,b,d){c.SVGElement.prototype.animate.call(this,a,b,d);if(a.x&&a.y)b=this.renderer,a=c.splat(a)[0],a.alpha*=n,a.beta*=n,b=b.arc3dPath(a),this.shapeArgs=a,this.inn.attr({d:b.inn,zIndex:b.zInn}),this.out.attr({d:b.out,zIndex:b.zOut}), +this.side1.attr({d:b.side1,zIndex:b.zSide2}),this.side2.attr({d:b.side2,zIndex:b.zSide1}),this.top.attr({d:b.top,zIndex:b.zTop}),this.attr({fill:this.color}),this.attr({zIndex:b.zAll*100});return this};a.zIndex=f;a.attr({zIndex:f});return a};c.SVGRenderer.prototype.arc3dPath=function(e){var a=e.x,b=e.y,d=e.start,c=e.end-1.0E-5,g=e.r,h=e.innerR,i=e.depth,j=e.alpha,o=e.beta,t=l(d),y=k(d),v=l(c),n=k(c),q=g*l(o),r=g*l(j),u=h*l(o),s=h*l(j),A=i*k(o),B=i*k(j),i=["M",a+q*t,b+r*y],i=i.concat(z(a,b,q,r,d,c, +0,0)),i=i.concat(["L",a+u*v,b+s*n]),i=i.concat(z(a,b,u,s,c,d,0,0)),i=i.concat(["Z"]),e=(e.start+e.end)/2,e=k(o)*l(e)+k(-j)*k(-e),p=o>0?m/2:0,w=j>0?0:m/2,p=d>-p?d:c>-p?-p:d,x=c0?4:-1}).css({stroke:i.color}).add(); +else{var q={x:m,y:n,z:o+1,width:l,height:k+i.size,depth:j.size,alpha:f,beta:g,origin:d};this.backFrame?this.backFrame.animate(q):this.backFrame=b.cuboid(q).attr({fill:j.color,zIndex:-3}).css({stroke:j.color}).add();this.axisLine&&this.axisLine.hide();a={x:(a.yAxis[0].opposite?l:0)+m-h.size,y:n,z:0,width:h.size,height:k+i.size,depth:o+j.size,alpha:f,beta:g,origin:d};this.sideFrame?this.sideFrame.animate(a):this.sideFrame=b.cuboid(a).attr({fill:h.color,zIndex:-2}).css({stroke:h.color}).add()}}});c.wrap(c.Axis.prototype, +"getPlotLinePath",function(c){var a=c.apply(this,[].slice.call(arguments,1));if(!this.chart.is3d())return a;if(a===null)return a;var b=this.chart,d=b.options.chart.options3d,f=d.depth;d.origin={x:b.plotLeft+b.plotWidth/2,y:b.plotTop+b.plotHeight/2,z:f,vd:d.viewDistance};var a=[{x:a[1],y:a[2],z:this.horiz||this.opposite?f:0},{x:a[1],y:a[2],z:f},{x:a[4],y:a[5],z:f},{x:a[4],y:a[5],z:this.horiz||this.opposite?0:f}],f=b.options.inverted?d.beta:d.alpha,g=b.options.inverted?d.alpha:d.beta;g*=b.yAxis[0].opposite? +-1:1;a=x(a,f,g,d.origin);return a=this.chart.renderer.toLinePath(a,!1)});c.wrap(c.Tick.prototype,"getMarkPath",function(c){var a=c.apply(this,[].slice.call(arguments,1));if(!this.axis.chart.is3d())return a;var b=this.axis.chart,d=b.options.chart.options3d,f={x:b.plotLeft+b.plotWidth/2,y:b.plotTop+b.plotHeight/2,z:d.depth,vd:d.viewDistance},a=[{x:a[1],y:a[2],z:0},{x:a[4],y:a[5],z:0}],g=b.inverted?d.beta:d.alpha,d=b.inverted?d.alpha:d.beta;d*=b.yAxis[0].opposite?-1:1;a=x(a,g,d,f);return a=["M",a[0].x, +a[0].y,"L",a[1].x,a[1].y]});c.wrap(c.Tick.prototype,"getLabelPosition",function(c){var a=c.apply(this,[].slice.call(arguments,1));if(!this.axis.chart.is3d())return a;var b=this.axis.chart,d=b.options.chart.options3d,f={x:b.plotLeft+b.plotWidth/2,y:b.plotTop+b.plotHeight/2,z:d.depth,vd:d.viewDistance},g=b.inverted?d.beta:d.alpha,d=b.inverted?d.alpha:d.beta;d*=b.yAxis[0].opposite?-1:1;return a=x([{x:a.x,y:a.y,z:0}],g,d,f)[0]});c.wrap(c.Axis.prototype,"drawCrosshair",function(c){var a=arguments;this.chart.is3d()&& +a[2]&&(a[2]={plotX:a[2].plotXold||a[2].plotX,plotY:a[2].plotYold||a[2].plotY});c.apply(this,[].slice.call(a,1))});c.wrap(c.seriesTypes.column.prototype,"translate",function(e){e.apply(this,[].slice.call(arguments,1));if(this.chart.is3d()){var a=this.chart,b=a.options,d=b.plotOptions[this.chart.options.chart.type],b=b.chart.options3d,f=d.depth||25,g={x:a.plotWidth/2,y:a.plotHeight/2,z:b.depth,vd:b.viewDistance},h=b.alpha,i=b.beta*(a.yAxis[0].opposite?-1:1),j=(d.stacking?this.options.stack||0:this._i)* +(f+(d.groupZPadding||1));d.grouping!==!1&&(j=0);j+=d.groupZPadding||1;c.each(this.data,function(a){var b=a.shapeArgs,c=a.tooltipPos;a.shapeType="cuboid";b.alpha=h;b.beta=i;b.z=j;b.origin=g;b.depth=f;c=x([{x:c[0],y:c[1],z:j}],h,i,g)[0];a.tooltipPos=[c.x,c.y]})}});c.wrap(c.seriesTypes.column.prototype,"animate",function(e){if(this.chart.is3d()){var a=arguments[1],b=this.yAxis,d=this,f=this.yAxis.reversed;if(c.svg)a?c.each(d.data,function(a){a.height=a.shapeArgs.height;a.shapeArgs.height=1;if(!f)a.shapeArgs.y= +a.stackY?a.plotY+b.translate(a.stackY):a.plotY+(a.negative?-a.height:a.height)}):(c.each(d.data,function(a){a.shapeArgs.height=a.height;if(!f)a.shapeArgs.y=a.plotY-(a.negative?a.height:0);a.graphic&&a.graphic.animate(a.shapeArgs,d.options.animation)}),d.animate=null)}else e.apply(this,[].slice.call(arguments,1))});c.wrap(c.seriesTypes.column.prototype,"init",function(c){c.apply(this,[].slice.call(arguments,1));if(this.chart.is3d()){var a=this.chart.options.plotOptions.column.grouping,b=this.chart.options.plotOptions.column.stacking, +d=this.options.zIndex;if(!d&&(a===void 0||a)&&b){a=this.chart.retrieveStacks();b=this.options.stack||0;for(d=0;d0?k(h)*e:0),-c*(1-l(g))*k(b)+(k(b)>0?k(g)*e:0));a.dataLabel&&a.dataLabel.attr({x:a.dataLabel.connX+-c*(1-l(h))*l(b)+(l(b)>0?l(h)*e:0)-a.dataLabel.width/2,y:a.dataLabel.connY+-c*(1-l(g))*k(b)+(k(b)>0?k(g)*e:0)-a.dataLabel.height/2})})});c.wrap(c.seriesTypes.pie.prototype,"addPoint",function(c){c.apply(this,[].slice.call(arguments,1));this.chart.is3d()&&this.update()});c.wrap(c.seriesTypes.pie.prototype,"animate",function(e){if(this.chart.is3d()){var a=arguments[1],b=this.options.animation,d= +this.center,f=this.group,g=this.markerGroup;if(c.svg)if(b===!0&&(b={}),a){if(this.oldtranslateX=f.translateX,this.oldtranslateY=f.translateY,a={translateX:d[0],translateY:d[1],scaleX:0.001,scaleY:0.001},f.attr(a),g)g.attrSetters=f.attrSetters,g.attr(a)}else a={translateX:this.oldtranslateX,translateY:this.oldtranslateY,scaleX:1,scaleY:1},f.animate(a,b),g&&g.animate(a,b),this.animate=null}else e.apply(this,[].slice.call(arguments,1))});c.wrap(c.seriesTypes.scatter.prototype,"translate",function(e){e.apply(this, +[].slice.call(arguments,1));if(this.chart.is3d()){var a=this.chart,b=this.chart.options.chart.options3d,d=b.alpha,f=b.beta,g={x:a.inverted?a.plotHeight/2:a.plotWidth/2,y:a.inverted?a.plotWidth/2:a.plotHeight/2,z:b.depth,vd:b.viewDistance},b=b.depth,h=a.options.zAxis||{min:0,max:b},i=b/(h.max-h.min);c.each(this.data,function(a){var b={x:a.plotX,y:a.plotY,z:(a.z-h.min)*i},b=x([b],d,f,g)[0];a.plotXold=a.plotX;a.plotYold=a.plotY;a.plotX=b.x;a.plotY=b.y;a.plotZ=b.z})}});c.wrap(c.seriesTypes.scatter.prototype, +"init",function(c){var a=c.apply(this,[].slice.call(arguments,1));if(this.chart.is3d())this.pointArrayMap=["x","y","z"],this.tooltipOptions.pointFormat=this.userOptions.tooltip?this.userOptions.tooltip.pointFormat||"x: {point.x}
y: {point.y}
z: {point.z}
":"x: {point.x}
y: {point.y}
z: {point.z}
";return a});if(c.VMLRenderer)c.setOptions({animate:!1}),c.VMLRenderer.prototype.cuboid=c.SVGRenderer.prototype.cuboid,c.VMLRenderer.prototype.cuboidPath= +c.SVGRenderer.prototype.cuboidPath,c.VMLRenderer.prototype.toLinePath=c.SVGRenderer.prototype.toLinePath,c.VMLRenderer.prototype.createElement3D=c.SVGRenderer.prototype.createElement3D,c.VMLRenderer.prototype.arc3d=function(e){e=c.SVGRenderer.prototype.arc3d.call(this,e);e.css({zIndex:e.zIndex});return e},c.VMLRenderer.prototype.arc3dPath=c.SVGRenderer.prototype.arc3dPath,c.Chart.prototype.renderSeries=function(){for(var c,a=this.series.length;a--;)c=this.series[a],c.translate(),c.setTooltipPoints&& +c.setTooltipPoints(),c.render()},c.wrap(c.Axis.prototype,"render",function(c){c.apply(this,[].slice.call(arguments,1));this.sideFrame&&(this.sideFrame.css({zIndex:0}),this.sideFrame.front.attr({fill:this.sideFrame.color}));this.bottomFrame&&(this.bottomFrame.css({zIndex:1}),this.bottomFrame.front.attr({fill:this.bottomFrame.color}));this.backFrame&&(this.backFrame.css({zIndex:0}),this.backFrame.front.attr({fill:this.backFrame.color}))})})(Highcharts); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-3d.src.js b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-3d.src.js new file mode 100755 index 00000000000..9ddb7b53724 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-3d.src.js @@ -0,0 +1,1248 @@ +// ==ClosureCompiler== +// @compilation_level SIMPLE_OPTIMIZATIONS + +/** + * @license Highcharts JS v4.0.1 (2014-04-24) + * + * (c) 2009-2013 Torstein Hønsi + * + * License: www.highcharts.com/license + */ + +// JSLint options: +/*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */ + +(function (Highcharts) { + /** + Shorthands for often used function +*/ +/** + * Mathematical Functionility + */ +var PI = Math.PI, + deg2rad = (PI / 180), // degrees to radians + sin = Math.sin, + cos = Math.cos, + + round = Math.round; + +function perspective(points, angle2, angle1, origin) { + angle1 *= deg2rad; + angle2 *= deg2rad; + + var result = [], + xe, + ye, + ze; + + angle1 *= -1; + + xe = origin.x; + ye = origin.y; + ze = (origin.z === 0 ? 0.0001 : origin.z) * (origin.vd || 25); + + // some kind of minimum? + + var s1 = sin(angle1), + c1 = cos(angle1), + s2 = sin(angle2), + c2 = cos(angle2); + + var x, y, z, p; + + Highcharts.each(points, function (point) { + x = point.x - xe; + y = point.y - ye; + z = point.z || 0; + + p = { + x: c1 * x - s1 * z, + y: -s1 * s2 * x - c1 * s2 * z + c2 * y, + z: s1 * c2 * x + c1 * c2 * z + s2 * y + }; + + p.x = p.x * ((ze - p.z) / ze) + xe; + p.y = p.y * ((ze - p.z) / ze) + ye; + + result.push({x: round(p.x), y: round(p.y), z: round(p.z)}); + }); + return result; +} +/*** + EXTENSION TO THE SVG-RENDERER TO ENABLE 3D SHAPES + ***/ +////// HELPER METHODS ////// +var dFactor = (4 * (Math.sqrt(2) - 1) / 3) / (PI / 2); + +function curveTo(cx, cy, rx, ry, start, end, dx, dy) { + var result = []; + if ((end > start) && (end - start > PI / 2 + 0.0001)) { + result = result.concat(curveTo(cx, cy, rx, ry, start, start + (PI / 2), dx, dy)); + result = result.concat(curveTo(cx, cy, rx, ry, start + (PI / 2), end, dx, dy)); + return result; + } else if ((end < start) && (start - end > PI / 2 + 0.0001)) { + result = result.concat(curveTo(cx, cy, rx, ry, start, start - (PI / 2), dx, dy)); + result = result.concat(curveTo(cx, cy, rx, ry, start - (PI / 2), end, dx, dy)); + return result; + } else { + var arcAngle = end - start; + return [ + 'C', + cx + (rx * cos(start)) - ((rx * dFactor * arcAngle) * sin(start)) + dx, + cy + (ry * sin(start)) + ((ry * dFactor * arcAngle) * cos(start)) + dy, + cx + (rx * cos(end)) + ((rx * dFactor * arcAngle) * sin(end)) + dx, + cy + (ry * sin(end)) - ((ry * dFactor * arcAngle) * cos(end)) + dy, + + cx + (rx * cos(end)) + dx, + cy + (ry * sin(end)) + dy + ]; + } +} + +Highcharts.SVGRenderer.prototype.toLinePath = function (points, closed) { + var result = []; + + // Put "L x y" for each point + Highcharts.each(points, function (point) { + result.push('L', point.x, point.y); + }); + + // Set the first element to M + result[0] = 'M'; + + // If it is a closed line, add Z + if (closed) { + result.push('Z'); + } + + return result; +}; + +////// CUBOIDS ////// +Highcharts.SVGRenderer.prototype.cuboid = function (shapeArgs) { + + var result = this.g(), + paths = this.cuboidPath(shapeArgs); + + result.front = this.path(paths[0]).attr({zIndex: paths[3], 'stroke-linejoin': 'round'}).add(result); + result.top = this.path(paths[1]).attr({zIndex: paths[4], 'stroke-linejoin': 'round'}).add(result); + result.side = this.path(paths[2]).attr({zIndex: paths[5], 'stroke-linejoin': 'round'}).add(result); + + result.fillSetter = function (color) { + var c0 = color, + c1 = Highcharts.Color(color).brighten(0.1).get(), + c2 = Highcharts.Color(color).brighten(-0.1).get(); + + this.front.attr({fill: c0}); + this.top.attr({fill: c1}); + this.side.attr({fill: c2}); + + this.color = color; + return this; + }; + + result.opacitySetter = function (opacity) { + this.front.attr({opacity: opacity}); + this.top.attr({opacity: opacity}); + this.side.attr({opacity: opacity}); + return this; + }; + + result.attr = function (args) { + if (args.shapeArgs || args.x) { + var shapeArgs = args.shapeArgs || args; + var paths = this.renderer.cuboidPath(shapeArgs); + this.front.attr({d: paths[0], zIndex: paths[3]}); + this.top.attr({d: paths[1], zIndex: paths[4]}); + this.side.attr({d: paths[2], zIndex: paths[5]}); + } else { + Highcharts.SVGElement.prototype.attr.call(this, args); + } + + return this; + }; + + result.animate = function (args, duration, complete) { + if (args.x && args.y) { + var paths = this.renderer.cuboidPath(args); + this.front.attr({zIndex: paths[3]}).animate({d: paths[0]}, duration, complete); + this.top.attr({zIndex: paths[4]}).animate({d: paths[1]}, duration, complete); + this.side.attr({zIndex: paths[5]}).animate({d: paths[2]}, duration, complete); + } else if (args.opacity) { + this.front.animate(args, duration, complete); + this.top.animate(args, duration, complete); + this.side.animate(args, duration, complete); + } else { + Highcharts.SVGElement.prototype.animate.call(this, args, duration, complete); + } + return this; + }; + + result.destroy = function () { + this.front.destroy(); + this.top.destroy(); + this.side.destroy(); + + return null; + }; + + // Apply the Z index to the cuboid group + result.attr({ zIndex: -paths[3] }); + + return result; +}; + + +Highcharts.SVGRenderer.prototype.cuboidPath = function (shapeArgs) { + var x = shapeArgs.x, + y = shapeArgs.y, + z = shapeArgs.z, + h = shapeArgs.height, + w = shapeArgs.width, + d = shapeArgs.depth, + alpha = shapeArgs.alpha, + beta = shapeArgs.beta, + origin = shapeArgs.origin; + + var pArr = [ + {x: x, y: y, z: z}, + {x: x + w, y: y, z: z}, + {x: x + w, y: y + h, z: z}, + {x: x, y: y + h, z: z}, + {x: x, y: y + h, z: z + d}, + {x: x + w, y: y + h, z: z + d}, + {x: x + w, y: y, z: z + d}, + {x: x, y: y, z: z + d} + ]; + + pArr = perspective(pArr, alpha, beta, origin); + + var path1, // FRONT + path2, // TOP OR BOTTOM + path3; // LEFT OR RIGHT + + // front + path1 = [ + 'M', pArr[0].x, pArr[0].y, + 'L', pArr[1].x, pArr[1].y, + 'L', pArr[2].x, pArr[2].y, + 'L', pArr[3].x, pArr[3].y, + 'Z' + ]; + var z1 = (pArr[0].z + pArr[1].z + pArr[2].z + pArr[3].z) / 4; + + // top or bottom + var top = [ + 'M', pArr[0].x, pArr[0].y, + 'L', pArr[7].x, pArr[7].y, + 'L', pArr[6].x, pArr[6].y, + 'L', pArr[1].x, pArr[1].y, + 'Z' + ]; + var bottom = [ + 'M', pArr[3].x, pArr[3].y, + 'L', pArr[2].x, pArr[2].y, + 'L', pArr[5].x, pArr[5].y, + 'L', pArr[4].x, pArr[4].y, + 'Z' + ]; + if (pArr[7].y < pArr[1].y) { + path2 = top; + } else if (pArr[4].y > pArr[2].y) { + path2 = bottom; + } else { + path2 = []; + } + var z2 = (beta > 0 ? (pArr[0].z + pArr[7].z + pArr[6].z + pArr[1].z) / 4 : (pArr[3].z + pArr[2].z + pArr[5].z + pArr[4].z) / 4); + + // side + var right = [ + 'M', pArr[1].x, pArr[1].y, + 'L', pArr[2].x, pArr[2].y, + 'L', pArr[5].x, pArr[5].y, + 'L', pArr[6].x, pArr[6].y, + 'Z' + ]; + var left = [ + 'M', pArr[0].x, pArr[0].y, + 'L', pArr[7].x, pArr[7].y, + 'L', pArr[4].x, pArr[4].y, + 'L', pArr[3].x, pArr[3].y, + 'Z' + ]; + if (pArr[6].x > pArr[1].x) { + path3 = right; + } else if (pArr[7].x < pArr[0].x) { + path3 = left; + } else { + path3 = []; + } + var z3 = (alpha > 0 ? (pArr[1].z + pArr[2].z + pArr[5].z + pArr[6].z) / 4 : (pArr[0].z + pArr[7].z + pArr[4].z + pArr[3].z) / 4); + + return [path1, path2, path3, z1, z2, z3]; +}; + +////// SECTORS ////// +Highcharts.SVGRenderer.prototype.arc3d = function (shapeArgs) { + + shapeArgs.alpha *= deg2rad; + shapeArgs.beta *= deg2rad; + var result = this.g(), + paths = this.arc3dPath(shapeArgs), + renderer = result.renderer; + + var zIndex = paths.zAll * 100; + + result.shapeArgs = shapeArgs; // Store for later use + + result.side1 = renderer.path(paths.side2).attr({zIndex: paths.zSide2}).add(result); + result.side2 = renderer.path(paths.side1).attr({zIndex: paths.zSide1}).add(result); + result.inn = renderer.path(paths.inn).attr({zIndex: paths.zInn}).add(result); + result.out = renderer.path(paths.out).attr({zIndex: paths.zOut}).add(result); + result.top = renderer.path(paths.top).attr({zIndex: paths.zTop}).add(result); + + result.fillSetter = function (color) { + this.color = color; + + var c0 = color, + c2 = Highcharts.Color(color).brighten(-0.1).get(); + + this.side1.attr({fill: c2}); + this.side2.attr({fill: c2}); + this.inn.attr({fill: c2}); + this.out.attr({fill: c2}); + this.top.attr({fill: c0}); + return this; + }; + + result.animate = function (args, duration, complete) { + Highcharts.SVGElement.prototype.animate.call(this, args, duration, complete); + + if (args.x && args.y) { + + // Recreate + var result = this, + renderer = this.renderer, + shapeArgs = Highcharts.splat(args)[0]; + + shapeArgs.alpha *= deg2rad; + shapeArgs.beta *= deg2rad; + + var paths = renderer.arc3dPath(shapeArgs); + + result.shapeArgs = shapeArgs; // Store for later use + + result.inn.attr({d: paths.inn, zIndex: paths.zInn}); + result.out.attr({d: paths.out, zIndex: paths.zOut}); + result.side1.attr({d: paths.side1, zIndex: paths.zSide2}); + result.side2.attr({d: paths.side2, zIndex: paths.zSide1}); + result.top.attr({d: paths.top, zIndex: paths.zTop}); + + result.attr({fill: result.color}); + result.attr({zIndex: paths.zAll * 100}); + } + + return this; + }; + + result.zIndex = zIndex; + result.attr({zIndex: zIndex}); + return result; +}; + + +Highcharts.SVGRenderer.prototype.arc3dPath = function (shapeArgs) { + var cx = shapeArgs.x, + cy = shapeArgs.y, + start = shapeArgs.start, + end = shapeArgs.end - 0.00001, + r = shapeArgs.r, + ir = shapeArgs.innerR, + d = shapeArgs.depth, + alpha = shapeArgs.alpha, + beta = shapeArgs.beta; + + // Some Variables + var cs = cos(start), + ss = sin(start), + ce = cos(end), + se = sin(end), + rx = r * cos(beta), + ry = r * cos(alpha), + irx = ir * cos(beta), + iry = ir * cos(alpha), + dx = d * sin(beta), + dy = d * sin(alpha); + + // TOP + var top = ['M', cx + (rx * cs), cy + (ry * ss)]; + top = top.concat(curveTo(cx, cy, rx, ry, start, end, 0, 0)); + top = top.concat([ + 'L', cx + (irx * ce), cy + (iry * se) + ]); + top = top.concat(curveTo(cx, cy, irx, iry, end, start, 0, 0)); + top = top.concat(['Z']); + + var midAngle = ((shapeArgs.start + shapeArgs.end) / 2); + var zIndex = ((sin(beta) * cos(midAngle)) + (sin(-alpha) * sin(-midAngle))); + + // OUTSIDE + var b = (beta > 0 ? PI / 2 : 0), + a = (alpha > 0 ? 0 : PI / 2); + + var start2 = start > -b ? start : (end > -b ? -b : start), + end2 = end < PI - a ? end : (start < PI - a ? PI - a : end); + + var out = ['M', cx + (rx * cos(start2)), cy + (ry * sin(start2))]; + out = out.concat(curveTo(cx, cy, rx, ry, start2, end2, 0, 0)); + out = out.concat([ + 'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy + ]); + out = out.concat(curveTo(cx, cy, rx, ry, end2, start2, dx, dy)); + out = out.concat(['Z']); + + // INSIDE + var inn = ['M', cx + (irx * cs), cy + (iry * ss)]; + inn = inn.concat(curveTo(cx, cy, irx, iry, start, end, 0, 0)); + inn = inn.concat([ + 'L', cx + (irx * cos(end)) + dx, cy + (iry * sin(end)) + dy + ]); + inn = inn.concat(curveTo(cx, cy, irx, iry, end, start, dx, dy)); + inn = inn.concat(['Z']); + + // SIDES + var side1 = [ + 'M', cx + (rx * cs), cy + (ry * ss), + 'L', cx + (rx * cs) + dx, cy + (ry * ss) + dy, + 'L', cx + (irx * cs) + dx, cy + (iry * ss) + dy, + 'L', cx + (irx * cs), cy + (iry * ss), + 'Z' + ]; + var side2 = [ + 'M', cx + (rx * ce), cy + (ry * se), + 'L', cx + (rx * ce) + dx, cy + (ry * se) + dy, + 'L', cx + (irx * ce) + dx, cy + (iry * se) + dy, + 'L', cx + (irx * ce), cy + (iry * se), + 'Z' + ]; + + var mr = ir + ((r - ir) / 2); + + var zTop = Math.abs(zIndex * 2 * mr), + zOut = zIndex * r, + zInn = zIndex * ir, + zSide1 = ((sin(beta) * cos(start)) + (sin(-alpha) * sin(-start))) * mr, + zSide2 = ((sin(beta) * cos(end)) + (sin(-alpha) * sin(-end))) * mr; + + return { + top: top, + zTop: zTop * 100, + out: out, + zOut: zOut * 100, + inn: inn, + zInn: zInn * 100, + side1: side1, + zSide1: zSide1 * 100, + side2: side2, + zSide2: zSide2 * 100, + zAll: zIndex + }; +}; +/*** + EXTENSION FOR 3D CHARTS +***/ +// Shorthand to check the is3d flag +Highcharts.Chart.prototype.is3d = function () { + return this.options.chart.options3d && this.options.chart.options3d.enabled; +}; + +Highcharts.wrap(Highcharts.Chart.prototype, 'isInsidePlot', function (proceed) { + if (this.is3d()) { + return true; + } else { + return proceed.apply(this, [].slice.call(arguments, 1)); + } +}); + +Highcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed) { + var args = arguments; + args[1] = Highcharts.merge({ + chart: { + options3d: { + enabled: false, + alpha: 0, + beta: 0, + depth: 100, + viewDistance: 25, + + frame: { + bottom: { size: 1, color: 'rgba(255,255,255,0)' }, + side: { size: 1, color: 'rgba(255,255,255,0)' }, + back: { size: 1, color: 'rgba(255,255,255,0)' } + } + } + } + }, args[1]); + + proceed.apply(this, [].slice.call(args, 1)); +}); + +Highcharts.wrap(Highcharts.Chart.prototype, 'setChartSize', function (proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + + if (this.is3d()) { + var inverted = this.inverted, + clipBox = this.clipBox, + margin = this.margin, + x = inverted ? 'y' : 'x', + y = inverted ? 'x' : 'y', + w = inverted ? 'height' : 'width', + h = inverted ? 'width' : 'height'; + + clipBox[x] = -(margin[3] || 0); + clipBox[y] = -(margin[0] || 0); + clipBox[w] = this.chartWidth + (margin[3] || 0) + (margin[1] || 0); + clipBox[h] = this.chartHeight + (margin[0] || 0) + (margin[2] || 0); + } +}); + +Highcharts.wrap(Highcharts.Chart.prototype, 'redraw', function (proceed) { + if (this.is3d()) { + // Set to force a redraw of all elements + this.isDirtyBox = true; + } + proceed.apply(this, [].slice.call(arguments, 1)); +}); + +Highcharts.Chart.prototype.retrieveStacks = function () { + var stacks = {}, + type = this.options.chart.type, + typeOptions = this.options.plotOptions[type], + stacking = typeOptions.stacking, + grouping = typeOptions.grouping, + i = 1; + + if (grouping || !stacking) { return this.series; } + + Highcharts.each(this.series, function (S) { + if (!stacks[S.options.stack || 0]) { + stacks[S.options.stack || 0] = { series: [S], position: i}; + i++; + } else { + stacks[S.options.stack || 0].series.push(S); + } + }); + stacks.totalStacks = i + 1; + return stacks; +}; + +/*** + EXTENSION TO THE AXIS +***/ +Highcharts.wrap(Highcharts.Axis.prototype, 'init', function (proceed) { + var args = arguments; + if (args[1].is3d()) { + args[2].tickWidth = Highcharts.pick(args[2].tickWidth, 0); + args[2].gridLineWidth = Highcharts.pick(args[2].gridLineWidth, 1); + } + + proceed.apply(this, [].slice.call(arguments, 1)); +}); +Highcharts.wrap(Highcharts.Axis.prototype, 'render', function (proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + + // Do not do this if the chart is not 3D + if (!this.chart.is3d()) { + return; + } + + var chart = this.chart, + renderer = chart.renderer, + options3d = chart.options.chart.options3d, + alpha = options3d.alpha, + beta = options3d.beta * (chart.yAxis[0].opposite ? -1 : 1), + frame = options3d.frame, + fbottom = frame.bottom, + fback = frame.back, + fside = frame.side, + depth = options3d.depth, + height = this.height, + width = this.width, + left = this.left, + top = this.top; + + var origin = { + x: chart.plotLeft + (chart.plotWidth / 2), + y: chart.plotTop + (chart.plotHeight / 2), + z: depth, + vd: options3d.viewDistance + }; + if (this.horiz) { + /// BOTTOM + if (this.axisLine) { + this.axisLine.hide(); + } + var bottomShape = { + x: left, + y: top + (chart.yAxis[0].reversed ? -fbottom.size : height), + z: 0, + width: width, + height: fbottom.size, + depth: depth, + alpha: alpha, + beta: beta, + origin: origin + }; + if (!this.bottomFrame) { + this.bottomFrame = renderer.cuboid(bottomShape).attr({fill: fbottom.color, zIndex: (chart.yAxis[0].reversed && alpha > 0 ? 4 : -1)}).css({stroke: fbottom.color}).add(); + } else { + this.bottomFrame.animate(bottomShape); + } + } else { + // BACK + var backShape = { + x: left, + y: top, + z: depth + 1, + width: width, + height: height + fbottom.size, + depth: fback.size, + alpha: alpha, + beta: beta, + origin: origin + }; + if (!this.backFrame) { + this.backFrame = renderer.cuboid(backShape).attr({fill: fback.color, zIndex: -3}).css({stroke: fback.color}).add(); + } else { + this.backFrame.animate(backShape); + } + // SIDE + if (this.axisLine) { + this.axisLine.hide(); + } + var sideShape = { + x: (chart.yAxis[0].opposite ? width : 0) + left - fside.size, + y: top, + z: 0, + width: fside.size, + height: height + fbottom.size, + depth: depth + fback.size, + alpha: alpha, + beta: beta, + origin: origin + }; + if (!this.sideFrame) { + this.sideFrame = renderer.cuboid(sideShape).attr({fill: fside.color, zIndex: -2}).css({stroke: fside.color}).add(); + } else { + this.sideFrame.animate(sideShape); + } + } +}); + +Highcharts.wrap(Highcharts.Axis.prototype, 'getPlotLinePath', function (proceed) { + var path = proceed.apply(this, [].slice.call(arguments, 1)); + + // Do not do this if the chart is not 3D + if (!this.chart.is3d()) { + return path; + } + + if (path === null) { return path; } + + var chart = this.chart, + options3d = chart.options.chart.options3d; + + var d = options3d.depth; + + options3d.origin = { + x: chart.plotLeft + (chart.plotWidth / 2), + y: chart.plotTop + (chart.plotHeight / 2), + z: d, + vd: options3d.viewDistance + }; + + var pArr = [ + { x: path[1], y: path[2], z : (this.horiz || this.opposite ? d : 0)}, + { x: path[1], y: path[2], z : d }, + { x: path[4], y: path[5], z : d }, + { x: path[4], y: path[5], z : (this.horiz || this.opposite ? 0 : d)} + ]; + + var alpha = chart.options.inverted ? options3d.beta : options3d.alpha, + beta = chart.options.inverted ? options3d.alpha : options3d.beta; + + beta *= (chart.yAxis[0].opposite ? -1 : 1); + + pArr = perspective(pArr, alpha, beta, options3d.origin); + path = this.chart.renderer.toLinePath(pArr, false); + + return path; +}); + +/*** + EXTENSION TO THE TICKS +***/ + +Highcharts.wrap(Highcharts.Tick.prototype, 'getMarkPath', function (proceed) { + var path = proceed.apply(this, [].slice.call(arguments, 1)); + + // Do not do this if the chart is not 3D + if (!this.axis.chart.is3d()) { + return path; + } + + var chart = this.axis.chart, + options3d = chart.options.chart.options3d; + + var origin = { + x: chart.plotLeft + (chart.plotWidth / 2), + y: chart.plotTop + (chart.plotHeight / 2), + z: options3d.depth, + vd: options3d.viewDistance + }; + + var pArr = [ + {x: path[1], y: path[2], z: 0}, + {x: path[4], y: path[5], z: 0} + ]; + + var alpha = chart.inverted ? options3d.beta : options3d.alpha, + beta = chart.inverted ? options3d.alpha : options3d.beta; + + beta *= (chart.yAxis[0].opposite ? -1 : 1); + + pArr = perspective(pArr, alpha, beta, origin); + path = [ + 'M', pArr[0].x, pArr[0].y, + 'L', pArr[1].x, pArr[1].y + ]; + return path; +}); + +Highcharts.wrap(Highcharts.Tick.prototype, 'getLabelPosition', function (proceed) { + var pos = proceed.apply(this, [].slice.call(arguments, 1)); + + // Do not do this if the chart is not 3D + if (!this.axis.chart.is3d()) { + return pos; + } + + var chart = this.axis.chart, + options3d = chart.options.chart.options3d; + + var origin = { + x: chart.plotLeft + (chart.plotWidth / 2), + y: chart.plotTop + (chart.plotHeight / 2), + z: options3d.depth, + vd: options3d.viewDistance + }; + + var alpha = chart.inverted ? options3d.beta : options3d.alpha, + beta = chart.inverted ? options3d.alpha : options3d.beta; + + beta *= (chart.yAxis[0].opposite ? -1 : 1); + + pos = perspective([{x: pos.x, y: pos.y, z: 0}], alpha, beta, origin)[0]; + return pos; +}); + +Highcharts.wrap(Highcharts.Axis.prototype, 'drawCrosshair', function (proceed) { + var args = arguments; + if (this.chart.is3d()) { + if (args[2]) { + args[2] = { + plotX: args[2].plotXold || args[2].plotX, + plotY: args[2].plotYold || args[2].plotY + }; + } + } + proceed.apply(this, [].slice.call(args, 1)); +});/*** + EXTENSION FOR 3D COLUMNS +***/ +Highcharts.wrap(Highcharts.seriesTypes.column.prototype, 'translate', function (proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + + // Do not do this if the chart is not 3D + if (!this.chart.is3d()) { + return; + } + + var type = this.chart.options.chart.type, + series = this, + chart = series.chart, + options = chart.options, + typeOptions = options.plotOptions[type], + options3d = options.chart.options3d, + + depth = typeOptions.depth || 25, + origin = { + x: chart.plotWidth / 2, + y: chart.plotHeight / 2, + z: options3d.depth, + vd: options3d.viewDistance + }, + alpha = options3d.alpha, + beta = options3d.beta * (chart.yAxis[0].opposite ? -1 : 1); + + var stack = typeOptions.stacking ? (this.options.stack || 0) : series._i; + var z = stack * (depth + (typeOptions.groupZPadding || 1)); + + if (typeOptions.grouping !== false) { z = 0; } + + z += (typeOptions.groupZPadding || 1); + + Highcharts.each(series.data, function (point) { + var shapeArgs = point.shapeArgs, + tooltipPos = point.tooltipPos; + + point.shapeType = 'cuboid'; + shapeArgs.alpha = alpha; + shapeArgs.beta = beta; + shapeArgs.z = z; + shapeArgs.origin = origin; + shapeArgs.depth = depth; + + // Translate the tooltip position in 3d space + tooltipPos = perspective([{ x: tooltipPos[0], y: tooltipPos[1], z: z }], alpha, beta, origin)[0]; + point.tooltipPos = [tooltipPos.x, tooltipPos.y]; + + }); +}); + +Highcharts.wrap(Highcharts.seriesTypes.column.prototype, 'animate', function (proceed) { + if (!this.chart.is3d()) { + proceed.apply(this, [].slice.call(arguments, 1)); + } else { + var args = arguments, + init = args[1], + yAxis = this.yAxis, + series = this, + reversed = this.yAxis.reversed; + + if (Highcharts.svg) { // VML is too slow anyway + if (init) { + Highcharts.each(series.data, function (point) { + point.height = point.shapeArgs.height; + point.shapeArgs.height = 1; + if (!reversed) { + if (point.stackY) { + point.shapeArgs.y = point.plotY + yAxis.translate(point.stackY); + } else { + point.shapeArgs.y = point.plotY + (point.negative ? -point.height : point.height); + } + } + }); + + } else { // run the animation + Highcharts.each(series.data, function (point) { + point.shapeArgs.height = point.height; + if (!reversed) { + point.shapeArgs.y = point.plotY - (point.negative ? point.height : 0); + } + // null value do not have a graphic + if (point.graphic) { + point.graphic.animate(point.shapeArgs, series.options.animation); + } + }); + + // delete this function to allow it only once + series.animate = null; + } + } + } +}); + + +Highcharts.wrap(Highcharts.seriesTypes.column.prototype, 'init', function (proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + + if (this.chart.is3d()) { + var grouping = this.chart.options.plotOptions.column.grouping, + stacking = this.chart.options.plotOptions.column.stacking, + z = this.options.zIndex; + if (!z) { + if (!(grouping !== undefined && !grouping) && stacking) { + var stacks = this.chart.retrieveStacks(), + stack = this.options.stack || 0, + i; // position within the stack + for (i = 0; i < stacks[stack].series.length; i++) { + if (stacks[stack].series[i] === this) { + break; + } + } + z = (stacks.totalStacks * 10) - (10 * (stacks.totalStacks - stacks[stack].position)) - i; + + this.options.zIndex = z; + } + } + } +}); +function draw3DPoints(proceed) { + // Do not do this if the chart is not 3D + if (this.chart.is3d()) { + var grouping = this.chart.options.plotOptions.column.grouping; + if (grouping !== undefined && !grouping && this.group.zIndex !== undefined) { + this.group.attr({zIndex : (this.group.zIndex * 10)}); + } + if (this.userOptions.borderColor === undefined) { + this.options.borderColor = this.color; + } + + // Set the border color to the fill color to provide a smooth edge + Highcharts.each(this.data, function (point) { + var c = point.options.borderColor || point.color || point.series.userOptions.borderColor; + point.options.borderColor = c; + point.borderColor = c; + point.pointAttr[''].stroke = c; + // same bordercolor on hover and select + point.pointAttr.hover.stroke = c; + point.pointAttr.select.stroke = c; + }); + } + + proceed.apply(this, [].slice.call(arguments, 1)); +} + +if (Highcharts.seriesTypes.columnrange) { + Highcharts.wrap(Highcharts.seriesTypes.columnrange.prototype, 'drawPoints', draw3DPoints); +} + +Highcharts.wrap(Highcharts.seriesTypes.column.prototype, 'drawPoints', draw3DPoints); + +/*** + EXTENSION FOR 3D CYLINDRICAL COLUMNS + Not supported +***/ +var defaultOptions = Highcharts.getOptions(); +defaultOptions.plotOptions.cylinder = Highcharts.merge(defaultOptions.plotOptions.column); +var CylinderSeries = Highcharts.extendClass(Highcharts.seriesTypes.column, { + type: 'cylinder' +}); +Highcharts.seriesTypes.cylinder = CylinderSeries; + +Highcharts.wrap(Highcharts.seriesTypes.cylinder.prototype, 'translate', function (proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + + // Do not do this if the chart is not 3D + if (!this.chart.is3d()) { + return; + } + + var series = this, + chart = series.chart, + options = chart.options, + cylOptions = options.plotOptions.cylinder, + options3d = options.chart.options3d, + depth = cylOptions.depth || 0, + origin = { + x: chart.inverted ? chart.plotHeight / 2 : chart.plotWidth / 2, + y: chart.inverted ? chart.plotWidth / 2 : chart.plotHeight / 2, + z: options3d.depth, + vd: options3d.viewDistance + }, + alpha = options3d.alpha; + + var z = cylOptions.stacking ? (this.options.stack || 0) * depth : series._i * depth; + z += depth / 2; + + if (cylOptions.grouping !== false) { z = 0; } + + Highcharts.each(series.data, function (point) { + var shapeArgs = point.shapeArgs; + point.shapeType = 'arc3d'; + shapeArgs.x += depth / 2; + shapeArgs.z = z; + shapeArgs.start = 0; + shapeArgs.end = 2 * PI; + shapeArgs.r = depth * 0.95; + shapeArgs.innerR = 0; + shapeArgs.depth = shapeArgs.height * (1 / sin((90 - alpha) * deg2rad)) - z; + shapeArgs.alpha = 90 - alpha; + shapeArgs.beta = 0; + shapeArgs.origin = origin; + }); +}); +/*** + EXTENSION FOR 3D PIES +***/ + +Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'translate', function (proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + + // Do not do this if the chart is not 3D + if (!this.chart.is3d()) { + return; + } + + var series = this, + chart = series.chart, + options = chart.options, + pieOptions = options.plotOptions.pie, + depth = pieOptions.depth || 0, + options3d = options.chart.options3d, + origin = { + x: chart.plotWidth / 2, + y: chart.plotHeight / 2, + z: options3d.depth + }, + alpha = options3d.alpha, + beta = options3d.beta; + + var z = pieOptions.stacking ? (this.options.stack || 0) * depth : series._i * depth; + z += depth / 2; + + if (pieOptions.grouping !== false) { z = 0; } + + Highcharts.each(series.data, function (point) { + point.shapeType = 'arc3d'; + var shapeArgs = point.shapeArgs; + + shapeArgs.z = z; + shapeArgs.depth = depth * 0.75; + shapeArgs.origin = origin; + shapeArgs.alpha = alpha; + shapeArgs.beta = beta; + + var angle = (shapeArgs.end + shapeArgs.start) / 2; + + point.slicedTranslation = { + translateX : round(cos(angle) * series.options.slicedOffset * cos(alpha * deg2rad)), + translateY : round(sin(angle) * series.options.slicedOffset * cos(alpha * deg2rad)) + }; + }); +}); + +Highcharts.wrap(Highcharts.seriesTypes.pie.prototype.pointClass.prototype, 'haloPath', function (proceed) { + return this.series.chart.is3d() ? [] : proceed.call(this); +}); + +Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'drawPoints', function (proceed) { + // Do not do this if the chart is not 3D + if (this.chart.is3d()) { + // Set the border color to the fill color to provide a smooth edge + Highcharts.each(this.data, function (point) { + var c = point.options.borderColor || point.color || point.series.userOptions.borderColor || point.series.color; + point.options.borderColor = c; + point.borderColor = c; + point.pointAttr[''].stroke = c; + // same bordercolor on hover and select + point.pointAttr.hover.stroke = c; + point.pointAttr.select.stroke = c; + }); + } + + proceed.apply(this, [].slice.call(arguments, 1)); +}); + +Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'drawDataLabels', function (proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + // Do not do this if the chart is not 3D + if (!this.chart.is3d()) { + return; + } + + var series = this; + Highcharts.each(series.data, function (point) { + var shapeArgs = point.shapeArgs; + var r = shapeArgs.r, + d = shapeArgs.depth, + a1 = shapeArgs.alpha * deg2rad, + b1 = shapeArgs.beta * deg2rad, + a2 = (shapeArgs.start + shapeArgs.end) / 2; + + if (point.connector) { + point.connector.translate( + (-r * (1 - cos(b1)) * cos(a2)) + (cos(a2) > 0 ? sin(b1) * d : 0), + (-r * (1 - cos(a1)) * sin(a2)) + (sin(a2) > 0 ? sin(a1) * d : 0) + ); + } + if (point.dataLabel) { + point.dataLabel.attr({ + x: point.dataLabel.connX + (-r * (1 - cos(b1)) * cos(a2)) + (cos(a2) > 0 ? cos(b1) * d : 0) - (point.dataLabel.width / 2), + y: point.dataLabel.connY + (-r * (1 - cos(a1)) * sin(a2)) + (sin(a2) > 0 ? sin(a1) * d : 0) - (point.dataLabel.height / 2) + }); + } + }); +}); + +Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'addPoint', function (proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + if (this.chart.is3d()) { + // destroy (and rebuild) everything!!! + this.update(); + } +}); + +Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'animate', function (proceed) { + if (!this.chart.is3d()) { + proceed.apply(this, [].slice.call(arguments, 1)); + } else { + var args = arguments, + init = args[1], + animation = this.options.animation, + attribs, + center = this.center, + group = this.group, + markerGroup = this.markerGroup; + + if (Highcharts.svg) { // VML is too slow anyway + + if (animation === true) { + animation = {}; + } + // Initialize the animation + if (init) { + + // Scale down the group and place it in the center + this.oldtranslateX = group.translateX; + this.oldtranslateY = group.translateY; + attribs = { + translateX: center[0], + translateY: center[1], + scaleX: 0.001, // #1499 + scaleY: 0.001 + }; + + group.attr(attribs); + if (markerGroup) { + markerGroup.attrSetters = group.attrSetters; + markerGroup.attr(attribs); + } + + // Run the animation + } else { + attribs = { + translateX: this.oldtranslateX, + translateY: this.oldtranslateY, + scaleX: 1, + scaleY: 1 + }; + group.animate(attribs, animation); + if (markerGroup) { + markerGroup.animate(attribs, animation); + } + + // Delete this function to allow it only once + this.animate = null; + } + + } + } +});/*** + EXTENSION FOR 3D SCATTER CHART +***/ +Highcharts.wrap(Highcharts.seriesTypes.scatter.prototype, 'translate', function (proceed) { +//function translate3d(proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + + if (!this.chart.is3d()) { + return; + } + + var series = this, + chart = series.chart, + options3d = series.chart.options.chart.options3d, + alpha = options3d.alpha, + beta = options3d.beta, + origin = { + x: chart.inverted ? chart.plotHeight / 2 : chart.plotWidth / 2, + y: chart.inverted ? chart.plotWidth / 2 : chart.plotHeight / 2, + z: options3d.depth, + vd: options3d.viewDistance + }, + depth = options3d.depth, + zAxis = chart.options.zAxis || { min : 0, max: depth }; + + var rangeModifier = depth / (zAxis.max - zAxis.min); + + Highcharts.each(series.data, function (point) { + var pCo = { + x: point.plotX, + y: point.plotY, + z: (point.z - zAxis.min) * rangeModifier + }; + + pCo = perspective([pCo], alpha, beta, origin)[0]; + + point.plotXold = point.plotX; + point.plotYold = point.plotY; + + point.plotX = pCo.x; + point.plotY = pCo.y; + point.plotZ = pCo.z; + }); +}); + +Highcharts.wrap(Highcharts.seriesTypes.scatter.prototype, 'init', function (proceed) { + var result = proceed.apply(this, [].slice.call(arguments, 1)); + + if (this.chart.is3d()) { + // Add a third coordinate + this.pointArrayMap = ['x', 'y', 'z']; + + // Set a new default tooltip formatter + var default3dScatterTooltip = 'x: {point.x}
y: {point.y}
z: {point.z}
'; + if (this.userOptions.tooltip) { + this.tooltipOptions.pointFormat = this.userOptions.tooltip.pointFormat || default3dScatterTooltip; + } else { + this.tooltipOptions.pointFormat = default3dScatterTooltip; + } + } + return result; +});/** + * Extension to the VML Renderer + */ +if (Highcharts.VMLRenderer) { + +Highcharts.setOptions({animate: false}); + +Highcharts.VMLRenderer.prototype.cuboid = Highcharts.SVGRenderer.prototype.cuboid; +Highcharts.VMLRenderer.prototype.cuboidPath = Highcharts.SVGRenderer.prototype.cuboidPath; + +Highcharts.VMLRenderer.prototype.toLinePath = Highcharts.SVGRenderer.prototype.toLinePath; + +Highcharts.VMLRenderer.prototype.createElement3D = Highcharts.SVGRenderer.prototype.createElement3D; + +Highcharts.VMLRenderer.prototype.arc3d = function (shapeArgs) { + var result = Highcharts.SVGRenderer.prototype.arc3d.call(this, shapeArgs); + result.css({zIndex: result.zIndex}); + return result; +}; + +Highcharts.VMLRenderer.prototype.arc3dPath = Highcharts.SVGRenderer.prototype.arc3dPath; + +// Draw the series in the reverse order +Highcharts.Chart.prototype.renderSeries = function () { + var serie, + i = this.series.length; + while (i--) { + serie = this.series[i]; + serie.translate(); + if (serie.setTooltipPoints) { + serie.setTooltipPoints(); + } + serie.render(); + } +}; + +Highcharts.wrap(Highcharts.Axis.prototype, 'render', function (proceed) { + proceed.apply(this, [].slice.call(arguments, 1)); + // VML doesn't support a negative z-index + if (this.sideFrame) { + this.sideFrame.css({zIndex: 0}); + this.sideFrame.front.attr({fill: this.sideFrame.color}); + } + if (this.bottomFrame) { + this.bottomFrame.css({zIndex: 1}); + this.bottomFrame.front.attr({fill: this.bottomFrame.color}); + } + if (this.backFrame) { + this.backFrame.css({zIndex: 0}); + this.backFrame.front.attr({fill: this.backFrame.color}); + } +}); + +} + +}(Highcharts)); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-all.js b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-all.js new file mode 100755 index 00000000000..b3164f4db85 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-all.js @@ -0,0 +1,453 @@ +/* + Highcharts JS v4.0.1 (2014-04-24) + + Standalone Highcharts Framework + + License: MIT License +*/ +var HighchartsAdapter=function(){function o(c){function b(b,a,d){b.removeEventListener(a,d,!1)}function d(b,a,d){d=b.HCProxiedMethods[d.toString()];b.detachEvent("on"+a,d)}function a(a,c){var f=a.HCEvents,i,g,k,j;if(a.removeEventListener)i=b;else if(a.attachEvent)i=d;else return;c?(g={},g[c]=!0):g=f;for(j in g)if(f[j])for(k=f[j].length;k--;)i(a,j,f[j][k])}c.HCExtended||Highcharts.extend(c,{HCExtended:!0,HCEvents:{},bind:function(b,a){var d=this,c=this.HCEvents,g;if(d.addEventListener)d.addEventListener(b, +a,!1);else if(d.attachEvent){g=function(b){b.target=b.srcElement||window;a.call(d,b)};if(!d.HCProxiedMethods)d.HCProxiedMethods={};d.HCProxiedMethods[a.toString()]=g;d.attachEvent("on"+b,g)}c[b]===r&&(c[b]=[]);c[b].push(a)},unbind:function(c,h){var f,i;c?(f=this.HCEvents[c]||[],h?(i=HighchartsAdapter.inArray(h,f),i>-1&&(f.splice(i,1),this.HCEvents[c]=f),this.removeEventListener?b(this,c,h):this.attachEvent&&d(this,c,h)):(a(this,c),this.HCEvents[c]=[])):(a(this),this.HCEvents={})},trigger:function(b, +a){var d=this.HCEvents[b]||[],c=d.length,g,k,j;k=function(){a.defaultPrevented=!0};for(g=0;g=a.duration+this.startTime){this.now=this.end; +this.pos=this.state=1;this.update();b=this.options.curAnim[this.prop]=!0;for(h in a.curAnim)a.curAnim[h]!==!0&&(b=!1);b&&a.complete&&a.complete.call(e);a=!1}else e=c-this.startTime,this.state=e/a.duration,this.pos=a.easing(e,0,1,a.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update(),a=!0;return a}};this.animate=function(b,d,a){var e,h="",f,i,g;b.stopAnimation=!1;if(typeof a!=="object"||a===null)e=arguments,a={duration:e[2],easing:e[3],complete:e[4]};if(typeof a.duration!=="number")a.duration= +400;a.easing=Math[a.easing]||Math.easeInOutSine;a.curAnim=Highcharts.extend({},d);for(g in d)i=new n(b,a,g),f=null,g==="d"?(i.paths=c.init(b,b.d,d.d),i.toD=d.d,e=0,f=1):b.attr?e=b.attr(g):(e=parseFloat(HighchartsAdapter._getStyle(b,g))||0,g!=="opacity"&&(h="px")),f||(f=parseFloat(d[g])),i.custom(e,f,h)}},_getStyle:function(c,b){return window.getComputedStyle(c,void 0).getPropertyValue(b)},getScript:function(c,b){var d=l.getElementsByTagName("head")[0],a=l.createElement("script");a.type="text/javascript"; +a.src=c;a.onload=b;d.appendChild(a)},inArray:function(c,b){return b.indexOf?b.indexOf(c):p.indexOf.call(b,c)},adapterRun:function(c,b){return parseInt(HighchartsAdapter._getStyle(c,b),10)},grep:function(c,b){return p.filter.call(c,b)},map:function(c,b){for(var d=[],a=0,e=c.length;a3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+M(a-c).toFixed(f).slice(2):"")}function Ha(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function Ma(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments); +a.unshift(d);return c.apply(this,a)}}function Ia(a,b){for(var c="{",d=!1,e,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");i=g.length;e=b;for(h=0;h-1?h.thousandsSep:""))):e=cb(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function mb(a){return U.pow(10,T(U.log(a)/ +U.LN10))}function nb(a,b,c,d){var e,c=m(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;dc&&(c=a[b]);return c}function Oa(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Pa(a){db||(db=Y(Ja));a&&db.appendChild(a);db.innerHTML=""}function ra(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw c;else I.console&&console.log(c)}function da(a){return parseFloat(a.toPrecision(14))}function Qa(a,b){va=m(a,b.animation)}function Cb(){var a=E.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";Ra=(a&&E.global.timezoneOffset|| +0)*6E4;eb=a?Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,m(c,1),m(g,0),m(h,0),m(i,0))).getTime()};pb=b+"Minutes";qb=b+"Hours";rb=b+"Day";Xa=b+"Date";fb=b+"Month";gb=b+"FullYear";Db=c+"Minutes";Eb=c+"Hours";sb=c+"Date";Fb=c+"Month";Gb=c+"FullYear"}function P(){}function Sa(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function la(){this.init.apply(this,arguments)}function Ya(){this.init.apply(this,arguments)}function Hb(a,b,c,d,e){var f=a.chart.inverted; +this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.alignOptions={align:b.align||(f?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(f?"middle":c?"bottom":"top"),y:m(b.y,f?4:c?14:-6),x:m(b.x,f?c?-6:6:0)};this.textAlign=b.textAlign||(f?c?"right":"left":"center")}var t,y=document,I=window,U=Math,u=U.round,T=U.floor,Ka=U.ceil,v=U.max,C=U.min,M=U.abs,Z=U.cos,ea=U.sin,ma=U.PI,Ca=ma*2/360,wa=navigator.userAgent,Ib=I.opera,Aa=/msie/i.test(wa)&& +!Ib,hb=y.documentMode===8,ib=/AppleWebKit/.test(wa),Ta=/Firefox/.test(wa),Jb=/(Mobile|Android|Windows Phone)/.test(wa),xa="http://www.w3.org/2000/svg",aa=!!y.createElementNS&&!!y.createElementNS(xa,"svg").createSVGRect,Nb=Ta&&parseInt(wa.split("Firefox/")[1],10)<4,fa=!aa&&!Aa&&!!y.createElement("canvas").getContext,Za,$a,Kb={},tb=0,db,E,cb,va,ub,A,sa=function(){},V=[],ab=0,Ja="div",Q="none",Ob=/^[0-9]+$/,Pb="stroke-width",eb,Ra,pb,qb,rb,Xa,fb,gb,Db,Eb,sb,Fb,Gb,F={},R=I.Highcharts=I.Highcharts?ra(16, +!0):{};cb=function(a,b,c){if(!r(b)||isNaN(b))return"Invalid date";var a=m(a,"%Y-%m-%d %H:%M:%S"),d=new Date(b-Ra),e,f=d[qb](),g=d[rb](),h=d[Xa](),i=d[fb](),j=d[gb](),k=E.lang,l=k.weekdays,d=q({a:l[g].substr(0,3),A:l[g],d:Ha(h),e:h,b:k.shortMonths[i],B:k.months[i],m:Ha(i+1),y:j.toString().substr(2,2),Y:j,H:Ha(f),I:Ha(f%12||12),l:f%12||12,M:Ha(d[pb]()),p:f<12?"AM":"PM",P:f<12?"am":"pm",S:Ha(d.getSeconds()),L:Ha(u(b%1E3),3)},R.dateFormats);for(e in d)for(;a.indexOf("%"+e)!==-1;)a=a.replace("%"+e,typeof d[e]=== +"function"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};Bb.prototype={wrapColor:function(a){if(this.color>=a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};A=function(){for(var a=0,b=arguments,c=b.length,d={};a-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c), +h,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length{point.key}
',pointFormat:'● {series.name}: {point.y}
',shadow:!0,snap:Jb?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px",padding:"8px", +whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var ba=E.plotOptions,S=ba.line;Cb();var Tb=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,Ub=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,Vb=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,ya=function(a){var b=[],c, +d;(function(a){a&&a.stops?d=Ua(a.stops,function(a){return ya(a[1])}):(c=Tb.exec(a))?b=[z(c[1]),z(c[2]),z(c[3]),parseFloat(c[4],10)]:(c=Ub.exec(a))?b=[z(c[1],16),z(c[2],16),z(c[3],16),1]:(c=Vb.exec(a))&&(b=[z(c[1]),z(c[2]),z(c[3]),1])})(a);return{get:function(c){var f;d?(f=w(a),f.stops=[].concat(f.stops),p(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)p(d, +function(b){b.brighten(a)});else if(ha(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=z(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};P.prototype={init:function(a,b){this.element=b==="span"?Y(b):y.createElementNS(xa,b);this.renderer=a},opacity:1,animate:function(a,b,c){b=m(b,va,!0);bb(this);if(b){b=w(b,{});if(c)b.complete=c;kb(this,a,b)}else this.attr(a),c&&c()},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,i,j,k,l,o,n,s=[];a.linearGradient? +f="linearGradient":a.radialGradient&&(f="radialGradient");if(f){g=a[f];h=d.gradients;j=a.stops;o=c.radialReference;La(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"});f==="radialGradient"&&o&&!r(g.gradientUnits)&&(g=w(g,{cx:o[0]-o[2]/2+g.cx*o[2],cy:o[1]-o[2]/2+g.cy*o[2],r:g.r*o[2],gradientUnits:"userSpaceOnUse"}));for(n in g)n!=="id"&&s.push(n,g[n]);for(n in j)s.push(j[n]);s=s.join(",");h[s]?a=h[s].attr("id"):(g.id=a="highcharts-"+tb++,h[s]=i=d.createElement(f).attr(g).add(d.defs), +i.stops=[],p(j,function(a){a[1].indexOf("rgba")===0?(e=ya(a[1]),k=e.get("rgb"),l=e.get("a")):(k=a[1],l=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":k,"stop-opacity":l}).add(i);i.stops.push(a)}));c.setAttribute(b,"url("+d.url+"#"+a+")")}},attr:function(a,b){var c,d,e=this.element,f,g=this,h;typeof a==="string"&&b!==t&&(c=a,a={},a[c]=b);if(typeof a==="string")g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(c in a){d=a[c];h=!1;this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&& +(f||(this.symbolAttr(a),f=!0),h=!0);if(this.rotation&&(c==="x"||c==="y"))this.doTransform=!0;h||(this[c+"Setter"]||this._defaultSetter).call(this,d,c,e);this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c)&&this.updateShadows(c,d)}if(this.doTransform)this.updateTransform(),this.doTransform=!1}return g},updateShadows:function(a,b){for(var c=this.shadows,d=c.length;d--;)c[d].setAttribute(a,a==="height"?v(b-(c[d].cutHeight||0),0):a==="d"?this.d:b)},addClass:function(a){var b=this.element, +c=H(b,"class")||"";c.indexOf(a)===-1&&H(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;p("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=m(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",a?"url("+this.renderer.url+"#"+a.id+")":Q)},crisp:function(a){var b,c={},d,e=a.strokeWidth||this.strokeWidth||this.attr&&this.attr("stroke-width")||0;d=u(e)%2/2;a.x=T(a.x||this.x|| +0)+d;a.y=T(a.y||this.y||0)+d;a.width=T((a.width||this.width||0)-2*d);a.height=T((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&&(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;if(a&&a.color)a.fill=a.color;if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&a.width&&d.nodeName.toLowerCase()==="text"&&z(a.width);b&&(a=q(b,c));this.styles=a;e&&(fa||!aa&&this.renderer.forExport)&&delete a.width;if(Aa&&!aa)G(this.element, +a);else{b=function(a,b){return"-"+b.toLowerCase()};for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";H(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;$a&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=Date.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(wa.indexOf("Android")===-1||Date.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference= +a;return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")"); +(r(c)||r(d))&&a.push("scale("+m(c,1)+" "+m(d,1)+")");a.length&&g.setAttribute("transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||Fa(c))this.alignTo=d=c||"renderer",ja(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=m(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x|| +0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=u(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=u(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d,e=this.rotation;c=this.element;var f=this.styles,g=e*Ca;d=this.textStr;var h;if(d===""||Ob.test(d))h="num."+d.toString().length+ +(f?"|"+f.fontSize+"|"+f.fontFamily:"");h&&(a=b.cache[h]);if(!a){if(c.namespaceURI===xa||b.forExport){try{a=c.getBBox?q({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(i){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){c=a.width;d=a.height;if(Aa&&f&&f.fontSize==="11px"&&d.toPrecision(3)==="16.9")a.height=d=14;if(e)a.width=M(d*ea(g))+M(c*Z(g)),a.height=M(d*Z(g))+M(c*ea(g))}this.bBox=a;h&&(b.cache[h]=a)}return a},show:function(a){return a&&this.element.namespaceURI=== +xa?(this.element.removeAttribute("visibility"),this):this.attr({visibility:a?"inherit":"visible"})},hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.hide()}})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=this.element,f=this.zIndex,g,h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(f)c.handleZ=!0,f=z(f);if(c.handleZ){a=d.childNodes; +for(g=0;gf||!r(f)&&r(c))){d.insertBefore(e,b);h=!0;break}}h||d.appendChild(e);this.added=!0;if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;bb(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f= +0;f/,i=/<.*href="(http[^"]+)".*>/,l&&!a.added&&this.box.appendChild(b), +e=f?e.replace(/<(b|strong)>/g,'').replace(/<(i|em)>/g,'').replace(//g,"").split(//g):[e],e[e.length-1]===""&&e.pop(),p(e,function(e,f){var g,n=0,e=e.replace(//g,"|||");g=e.split("|||");p(g,function(e){if(e!==""||g.length===1){var o={},m=y.createElementNS(xa,"tspan"),p;h.test(e)&&(p=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"), +H(m,"style",p));i.test(e)&&!d&&(H(m,"onclick",'location.href="'+e.match(i)[1]+'"'),G(m,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");if(e!==" "){m.appendChild(y.createTextNode(e));if(n)o.dx=0;else if(f&&j!==null)o.x=j;H(m,o);!n&&f&&(!aa&&d&&G(m,{display:"block"}),H(m,"dy",s(m),ib&&m.offsetHeight));b.appendChild(m);n++;if(l)for(var e=e.replace(/([^\^])-/g,"$1- ").split(" "),o=e.length>1&&k.whiteSpace!=="nowrap",$,r,B=a._clipHeight,q=[],v=s(),t= +1;o&&(e.length||q.length);)delete a.bBox,$=a.getBBox(),r=$.width,!aa&&c.forExport&&(r=c.measureSpanWidth(m.firstChild.data,a.styles)),$=r>l,!$||e.length===1?(e=q,q=[],e.length&&(t++,B&&t*v>B?(e=["..."],a.attr("title",a.textStr)):(m=y.createElementNS(xa,"tspan"),H(m,{dy:v,x:j}),p&&H(m,"style",p),b.appendChild(m),r>l&&(l=r)))):(m.removeChild(m.firstChild),q.unshift(e.pop())),e.length&&m.appendChild(y.createTextNode(e.join(" ").replace(/- /g,"-")))}}})}))},button:function(a,b,c,d,e,f,g,h,i){var j=this.label(a, +b,c,i,null,null,null,null,"button"),k=0,l,o,n,s,m,p,a={x1:0,y1:0,x2:0,y2:1},e=w({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);n=e.style;delete e.style;f=w(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);s=f.style;delete f.style;g=w(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);m=g.style;delete g.style;h=w(e,{style:{color:"#CCC"}},h);p=h.style;delete h.style; +K(j.element,Aa?"mouseover":"mouseenter",function(){k!==3&&j.attr(f).css(s)});K(j.element,Aa?"mouseout":"mouseleave",function(){k!==3&&(l=[e,f,g][k],o=[n,s,m][k],j.attr(l).css(o))});j.setState=function(a){(j.state=k=a)?a===2?j.attr(g).css(m):a===3&&j.attr(h).css(p):j.attr(e).css(n)};return j.on("click",function(){k!==3&&d.call(j)}).attr(e).css(q({cursor:"default"},n))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=u(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=u(a[2])+b%2/2);return a},path:function(a){var b= +{fill:Q};La(a)?b.d=a:ca(a)&&q(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=ca(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=function(a){this.element.setAttribute("cx",a)};b.ySetter=function(a){this.element.setAttribute("cy",a)};return b.attr(a)},arc:function(a,b,c,d,e,f){if(ca(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){var e=ca(a)?a.r: +e,g=this.createElement("rect"),a=ca(a)?a:a===t?{}:{x:a,y:b,width:v(c,0),height:v(d,0)};if(f!==t)a.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a){H(this.element,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[m(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return r(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f= +{preserveAspectRatio:Q};arguments.length>1&&q(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(u(b),u(c),d,e,f),i=/^url\((.*?)\)$/,j,k;if(h)g=this.path(h),q(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&q(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}), +a.alignByTranslate||a.translate(u((d-b[0])/2),u((e-b[1])/2)))},j=a.match(i)[1],a=Kb[j],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),Y("img",{onload:function(){k(g,Kb[j]=[this.width,this.height])},src:j}));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L", +a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=Z(f),j=ea(f),k=Z(g),g=ea(g),e=e.end-fc&&i>b+g&&ib+g&&id&&h>a+g&&ha+g&&hl&&/[ \-]/.test(b.textContent||b.innerText))G(b,{width:l+"px",display:"block",whiteSpace:"normal"}),i=l;this.getSpanCorrection(i,k,h,j,g)}G(b,{left:e+(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(ib)k=b.offsetHeight; +this.cTT=o}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=Aa?"-ms-transform":ib?"-webkit-transform":Ta?"MozTransform":Ib?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(Ta?"Origin":"-origin")]=d.transformOrigin=b*100+"% "+c+"px";G(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr=-a*c;this.yCorr=-b}});q(ta.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element,f=d.renderer;d.textSetter=function(a){a!==e.innerHTML&&delete this.bBox; +e.innerHTML=this.textStr=a};d.xSetter=d.ySetter=d.alignSetter=d.rotationSetter=function(a,b){b==="align"&&(b="textAlign");d[b]=a;d.htmlUpdateTransform()};d.attr({text:a,x:u(b),y:u(c)}).css({position:"absolute",whiteSpace:"nowrap",fontFamily:this.style.fontFamily,fontSize:this.style.fontSize});d.css=d.htmlCss;if(f.isSVG)d.add=function(a){var b,c=f.box.parentNode,j=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)j.push(a),a=a.parentGroup;p(j.reverse(),function(a){var d;b=a.div=a.div||Y(Ja,{className:H(a.element, +"class")},{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);d=b.style;q(a,{translateXSetter:function(b,c){d.left=b+"px";a[c]=b;a.doTransform=!0},translateYSetter:function(b,c){d.top=b+"px";a[c]=b;a.doTransform=!0},visibilitySetter:function(a,b){d[b]=a}})})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&&d.htmlUpdateTransform();return d};return d}});var X;if(!aa&&!fa){R.VMLElement=X={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute", +";"],e=b===Ja;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""),'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=Y(c);this.renderer=a},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();if(this.onAdd)this.onAdd();return this},updateTransform:P.prototype.htmlUpdateTransform, +setSpanRotation:function(){var a=this.rotation,b=Z(a*Ca),c=ea(a*Ca);G(this.element,{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",b,", M12=",-c,", M21=",c,", M22=",b,", sizingMethod='auto expand')"].join(""):Q})},getSpanCorrection:function(a,b,c,d,e){var f=d?Z(d*Ca):1,g=d?ea(d*Ca):0,h=m(this.elemHeight,this.element.offsetHeight),i;this.xCorr=f<0&&-a;this.yCorr=g<0&&-h;i=f*g<0;this.xCorr+=g*b*(i?1-c:c);this.yCorr-=f*b*(d?i?c:1-c:1);e&&e!=="left"&&(this.xCorr-=a*c*(f<0?-1:1),d&&(this.yCorr-= +h*c*(g<0?-1:1)),G(this.element,{textAlign:e}))},pathToVML:function(a){for(var b=a.length,c=[];b--;)if(ha(a[b]))c[b]=u(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))c[b+5]===c[b+7]&&(c[b+7]+=a[b+7]>a[b+5]?1:-1),c[b+6]===c[b+8]&&(c[b+8]+=a[b+8]>a[b+6]?1:-1);return c.join(" ")||"x"},clip:function(a){var b=this,c;a?(c=a.members,ja(c,b),c.push(b),b.destroyClip=function(){ja(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:hb?"inherit":"rect(auto)"}); +return b.css(a)},css:P.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Pa(a)},destroy:function(){this.destroyClip&&this.destroyClip();return P.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=I.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=z(a[c-2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path, +l,o,n,s;k&&typeof k.value!=="string"&&(k="x");o=k;if(a){n=m(a.width,3);s=(a.opacity||0.15)/n;for(e=1;e<=3;e++){l=n*2+1-2*e;c&&(o=this.cutOffPath(k.value,l+0.5));j=[''];h=Y(g.prepVML(j),null,{left:z(i.left)+m(a.offsetX,1),top:z(i.top)+m(a.offsetY,1)});if(c)h.cutOff=l+1;j=[''];Y(g.prepVML(j),null,null,h);b?b.element.appendChild(h): +f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this},updateShadows:sa,setAttr:function(a,b){hb?this.element[a]=b:this.element.setAttribute(a,b)},classSetter:function(a){this.element.className=a},dashstyleSetter:function(a,b,c){(c.getElementsByTagName("stroke")[0]||Y(this.renderer.prepVML([""]),null,null,c))[b]=a||"solid";this[b]=a},dSetter:function(a,b,c){var d=this.shadows,a=a||[];this.d=a.join(" ");c.path=a=this.pathToVML(a);if(d)for(c=d.length;c--;)d[c].path=d[c].cutOff? +this.cutOffPath(a,d[c].cutOff):a;this.setAttr(b,a)},fillSetter:function(a,b,c){var d=c.nodeName;if(d==="SPAN")c.style.color=a;else if(d!=="IMG")c.filled=a!==Q,this.setAttr("fillcolor",this.renderer.color(a,c,b,this))},opacitySetter:sa,rotationSetter:function(a,b,c){c=c.style;this[b]=c[b]=a;c.left=-u(ea(a*Ca)+1)+"px";c.top=u(Z(a*Ca))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b))},"stroke-widthSetter":function(a,b,c){c.stroked=!!a;this[b]=a;ha(a)&&(a+="px"); +this.setAttr("strokeweight",a)},titleSetter:function(a,b){this.setAttr(b,a)},visibilitySetter:function(a,b,c){a==="inherit"&&(a="visible");this.shadows&&p(this.shadows,function(c){c.style[b]=a});c.nodeName==="DIV"&&(a=a==="hidden"?"-999em":0,hb||(c.style[b]=a?"visible":"hidden"),b="top");c.style[b]=a},xSetter:function(a,b,c){this[b]=a;b==="x"?b="left":b==="y"&&(b="top");this.updateClipping?(this[b]=a,this.updateClipping()):c.style[b]=a},zIndexSetter:function(a,b,c){c.style[b]=a}};X=ka(P,X);X.prototype.ySetter= +X.prototype.widthSetter=X.prototype.heightSetter=X.prototype.xSetter;var ga={Element:X,isIE8:wa.indexOf("MSIE 8.0")>-1,init:function(a,b,c,d){var e;this.alignedObjects=[];d=this.createElement(Ja).css(q(this.getStyle(d),{position:"relative"}));e=d.element;a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.cache={};this.setSize(b,c,!1);if(!y.namespaces.hcv){y.namespaces.add("hcv","urn:schemas-microsoft-com:vml");try{y.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){y.styleSheets[0].cssText+= +"hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=ca(a);return q(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+u(a?e:d)+"px,"+u(a? +f:b)+"px,"+u(a?b:f)+"px,"+u(a?d:e)+"px)"};!a&&hb&&c==="DIV"&&q(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){p(e.members,function(a){a.element&&a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=Q;a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,o=a.linearGradient||a.radialGradient,n,s,m,J,L,x="",a=a.stops,r,v=[],q=function(){h=['']; +Y(e.prepVML(h),null,null,b)};n=a[0];r=a[a.length-1];n[0]>0&&a.unshift([0,n[1]]);r[0]<1&&a.push([1,r[1]]);p(a,function(a,b){g.test(a[1])?(f=ya(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);v.push(a[0]*100+"% "+k);b?(m=l,J=k):(s=l,L=k)});if(c==="fill")if(i==="gradient")c=o.x1||o[0]||0,a=o.y1||o[1]||0,n=o.x2||o[2]||0,o=o.y2||o[3]||0,x='angle="'+(90-U.atan((o-a)/(n-c))*180/ma)+'"',q();else{var j=o.r,t=j*2,u=j*2,y=o.cx,B=o.cy,na=b.radialReference,w,j=function(){na&&(w=d.getBBox(),y+=(na[0]-w.x)/w.width- +0.5,B+=(na[1]-w.y)/w.height-0.5,t*=na[2]/w.width,u*=na[2]/w.height);x='src="'+E.global.VMLRadialGradientURL+'" size="'+t+","+u+'" origin="0.5,0.5" position="'+y+","+B+'" color2="'+L+'" ';q()};d.added?j():d.onAdd=j;j=J}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=ya(a),h=["<",c,' opacity="',f.get("a"),'"/>'],Y(this.prepVML(h),null,null,b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b? +(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","1&&f.attr({x:b,y:c,width:d,height:e});return f},createElement:function(a){return a==="rect"?this.symbol(a):ta.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;G(a,{flip:"x",left:z(d.width)-(e?z(e.top): +1),top:z(d.height)-(e?z(e.left):1),rotation:-90});p(a.childNodes,function(b){c.invertChild(b,a)})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=Z(f),i=ea(f),j=Z(g),k=ea(g);if(g-f===0)return["x"];f=["wa",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+ +c,b+d/2,"e"]},rect:function(a,b,c,d,e){return ta.prototype.symbols[!r(e)||!e.r?"square":"callout"].call(0,a,b,c,d,e)}}};R.VMLRenderer=X=function(){this.init.apply(this,arguments)};X.prototype=w(ta.prototype,ga);Za=X}ta.prototype.measureSpanWidth=function(a,b){var c=y.createElement("span"),d;d=y.createTextNode(a);c.appendChild(d);G(c,b);this.box.appendChild(c);d=c.offsetWidth;Pa(c);return d};var Lb;if(fa)R.CanVGRenderer=X=function(){xa="http://www.w3.org/1999/xhtml"},X.prototype.symbols={},Lb=function(){function a(){var a= +b.length,d;for(d=0;dl[s]?l[s]= +g+j:o||(c=!1);if(o){l=(o=d.justifyToPlot)?d.pos:0;o=o?l+d.len:d.chart.chartWidth;do a+=e?1:-1,n=d.ticks[i[a]];while(i[a]&&(!n||n.label.line!==s));d=n&&n.label.xy&&n.label.xy.x+n.getLabelSides()[e?0:1];e&&!h||f&&h?g+kd&&(c=!1)):g+j>o&&(g=o-j,n&&g+k0&&b.height>0){f=w({align:c&&k&&"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g){q={align:f.textAlign||f.align,rotation:f.rotation};if(r(L))q.zIndex=L;a.label=g=t.text(f.text,0,0,f.useHTML).attr(q).css(f.style).add()}b=[s[1], +s[4],m(s[6],s[1])];s=[s[2],s[5],m(s[7],s[2])];c=Na(b);k=Na(s);g.align(f,!1,{x:c,y:k,width:Ba(b)-c,height:Ba(s)-k});g.show()}else g&&g.hide();return a},destroy:function(){ja(this.axis.plotLinesAndBands,this);delete this.axis;Oa(this)}};la.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:N,lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01, +maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0, +tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return Ga(this.total,-1)},style:N.style}},defaultLeftAxisOptions:{labels:{x:-15,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:20},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-15},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite= +b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.names=[];this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=r(d.linkedTo);this.tickmarkOffset=this.categories&&d.tickmarkPlacement=== +"between"?0.5:0;this.ticks={};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.min=this.max=null;this.crosshair=m(d.crosshair,qa(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;Da(this,a.axes)===-1&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this)); +this.series=this.series||[];if(a.inverted&&c&&this.reversed===t)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)K(this,f,d[f]);if(this.isLog)this.val2lin=za,this.lin2val=ia},setOptions:function(a){this.options=w(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],w(E[this.coll],a))},defaultLabelFormatter:function(){var a= +this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=E.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ia(h,this);else if(c)g=b;else if(d)g=cb(d,b);else if(f&&a>=1E3)for(;f--&&g===t;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=Ga(b/c,-1)+e[f]);g===t&&(g=M(b)>=1E4?Ga(b,0):Ga(b,-1,t,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=null;a.buildStacks&&a.buildStacks();p(a.series,function(c){if(c.visible|| +!b.options.chart.ignoreHiddenSeries){var d;d=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&d<=0&&(d=null);if(a.isXAxis){if(d=c.xData,d.length)a.dataMin=C(m(a.dataMin,d[0]),Na(d)),a.dataMax=v(m(a.dataMax,d[0]),Ba(d))}else{c.getExtremes();e=c.dataMax;c=c.dataMin;if(r(c)&&r(e))a.dataMin=C(m(a.dataMin,c),c),a.dataMax=v(m(a.dataMax,e),e);if(r(d))if(a.dataMin>=d)a.dataMin=d,a.ignoreMinPadding=!0;else if(a.dataMaxg+this.width)o=!0}else if(a=g,c=l-this.right,ih+this.height)o=!0;return o&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,j],b||1)},getLinearTickPositions:function(a, +b,c){var d,e=da(T(b/a)*a),f=da(Ka(c/a)*a),g=[];if(b===c&&ha(b))return[b];for(b=e;b<=f;){g.push(b);b=da(b+a);if(b===d)break;d=b}return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog){e=b.length;for(a=1;a=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===t&&!this.isLog)r(a.min)||r(a.max)?this.minRange=null:(p(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]-i[g-1],f===t||hc&&(h=0);d=v(d,h);f=v(f,Fa(j)?0:h/2);g=v(g,j==="on"?0:h);!a.noSharedTooltip&&r(n)&&(e=r(e)?C(e,n):n)}),h=b.ordinalSlope&&e?b.ordinalSlope/e:1,b.minPointOffset=f*=h,b.pointRangePadding=g*=h,b.pointRange=C(d,c),b.closestPointRange=e;if(a)b.oldTransA=j;b.translationSlope=b.transA=j=b.len/(c+g||1);b.transB=b.horiz?b.left:b.bottom;b.minPixelPadding=j*f},setTickPositions:function(a){var b=this,c=b.chart,d=b.options,e=b.isLog,f=b.isDatetimeAxis,g=b.isXAxis,h=b.isLinked,i=b.options.tickPositioner,j=d.maxPadding, +k=d.minPadding,l=d.tickInterval,o=d.minTickInterval,n=d.tickPixelInterval,s,$=b.categories;h?(b.linkedParent=c[b.coll][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=m(c.min,c.dataMin),b.max=m(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&ra(11,1)):(b.min=m(b.userMin,d.min,b.dataMin),b.max=m(b.userMax,d.max,b.dataMax));if(e)!a&&C(b.min,m(b.dataMin,b.min))<=0&&ra(10,1),b.min=da(za(b.min)),b.max=da(za(b.max));if(b.range&&r(b.max))b.userMin=b.min=v(b.min,b.max-b.range),b.userMax=b.max,b.range= +null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!$&&!b.axisPointRange&&!b.usePercentage&&!h&&r(b.min)&&r(b.max)&&(c=b.max-b.min)){if(!r(d.min)&&!r(b.userMin)&&k&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*k;if(!r(d.max)&&!r(b.userMax)&&j&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*j}if(ha(d.floor))b.min=v(b.min,d.floor);if(ha(d.ceiling))b.max=C(b.max,d.ceiling);b.min===b.max||b.min===void 0||b.max===void 0?b.tickInterval=1:h&&!l&&n===b.linkedParent.options.tickPixelInterval?b.tickInterval= +b.linkedParent.tickInterval:(b.tickInterval=m(l,$?1:(b.max-b.min)*n/v(b.len,n)),!r(l)&&b.lenv(2*b.len,200)&&ra(19,!0),a=f?b.getTimeTicks(b.normalizeTimeTickInterval(b.tickInterval,d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange, +!0):e?b.getLogTickPositions(b.tickInterval,b.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),s&&a.splice(1,a.length-2),b.tickPositions=a;if(!h)e=a[0],f=a[a.length-1],h=b.minPointOffset||0,d.startOnTick?b.min=e:b.min-h>e&&a.shift(),d.endOnTick?b.max=f:b.max+h1E13?1:0.001,b.min-=d,b.max+=d)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=[this.coll,this.pos,this.len].join("-");if(!this.isLinked&& +!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&this.options.alignTicks!==!1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1&&this.min!==t){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e=v(d,m(e.max,d))&&(b=t));this.displayBtn=a!==t||b!==t;this.setExtremes(a,b,!1,t,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=m(b.width,a.plotWidth-c+(b.offsetRight||0)),f=m(b.height,a.plotHeight),g=m(b.top,a.plotTop),b=m(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=parseInt(f,10)/100*a.plotHeight);c.test(g)&&(g=parseInt(g,10)/100*a.plotHeight+a.plotTop); +this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=v(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a=this.isLog;return{min:a?da(ia(this.min)):this.min,max:a?da(ia(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?ia(this.min):this.min,b=b?ia(this.max):this.max;c>a||a===null?a=c:b15&&a<165?"right":a>195&&a<345?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k=0,l,o=0,n=d.title,s=d.labels,$=0,J=b.axisOffset,L=b.clipOffset,x=[-1,1,1,-1][h],q,u=1,w=m(s.maxStaggerLines,5),y,z,A,B,na=h===2?c.fontMetrics(s.style.fontSize).b:0;a.hasData=j=a.hasVisibleSeries||r(a.min)&&r(a.max)&&!!e;a.showAxis=b=j||m(d.showEmpty,!0);a.staggerLines= +a.horiz&&s.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:s.zIndex||7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add();if(j||a.isLinked){a.labelAlign=m(s.align||a.autoLabelAlign(s.rotation));p(e,function(b){f[b]?f[b].addLabel():f[b]=new Sa(a,b)});if(a.horiz&&!a.staggerLines&&w&&!s.rotation){for(q=a.reversed?[].concat(e).reverse():e;u1)a.staggerLines=u}p(e,function(b){if(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign)$=v(f[b].getLabelSize(),$)});if(a.staggerLines)$*=a.staggerLines,a.labelOffset=$}else for(q in f)f[q].destroy(),delete f[q];if(n&&n.text&&n.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(n.text,0,0,n.useHTML).attr({zIndex:7,rotation:n.rotation|| +0,align:n.textAlign||{low:"left",middle:"center",high:"right"}[n.align]}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(n.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(b)k=a.axisTitle.getBBox()[g?"height":"width"],o=m(n.margin,g?5:10),l=n.offset;a.axisTitle[b?"show":"hide"]()}a.offset=x*m(d.offset,J[h]);a.axisTitleMargin=m(l,$+o+($&&x*d.labels[g?"y":"x"]-na));J[h]=v(J[h],a.axisTitleMargin+k+x*a.offset);L[i]=v(L[i],T(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite, +d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=z(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)* +this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,b=a.horiz,c=a.reversed,d=a.chart,e=d.renderer,f=a.options,g=a.isLog,h=a.isLinked,i=a.tickPositions,j,k=a.axisTitle,l=a.ticks,o=a.minorTicks,n=a.alternateBands,s=f.stackLabels,m=f.alternateGridColor,J=a.tickmarkOffset,L=f.lineWidth,x=d.hasRendered&&r(a.oldMin)&&!isNaN(a.oldMin),q=a.hasData,v=a.showAxis,u,w=f.labels.overflow,y=a.justifyLabels=b&&w!== +!1,z;a.labelEdge.length=0;a.justifyToPlot=w==="justify";p([l,o,n],function(a){for(var b in a)a[b].isActive=!1});if(q||h)if(a.minorTickInterval&&!a.categories&&p(a.getMinorTickPositions(),function(b){o[b]||(o[b]=new Sa(a,b,"minor"));x&&o[b].isNew&&o[b].render(null,!0);o[b].render(null,!1,1)}),i.length&&(j=i.slice(),(b&&c||!b&&!c)&&j.reverse(),y&&(j=j.slice(1).concat([j[0]])),p(j,function(b,c){y&&(c=c===j.length-1?0:c+1);if(!h||b>=a.min&&b<=a.max)l[b]||(l[b]=new Sa(a,b)),x&&l[b].isNew&&l[b].render(c, +!0,0.1),l[b].render(c,!1,1)}),J&&a.min===0&&(l[-1]||(l[-1]=new Sa(a,-1,null,!0)),l[-1].render(-1))),m&&p(i,function(b,c){if(c%2===0&&b=A.second&&(i.setMilliseconds(0),i.setSeconds(j>=A.minute?0:k*T(i.getSeconds()/k)));if(j>=A.minute)i[Db](j>=A.hour?0:k*T(i[pb]()/k));if(j>=A.hour)i[Eb](j>=A.day?0:k*T(i[qb]()/k));if(j>=A.day)i[sb](j>=A.month?1:k*T(i[Xa]()/k));j>=A.month&&(i[Fb](j>=A.year?0:k*T(i[fb]()/k)),h=i[gb]());j>=A.year&&(h-=h%k,i[Gb](h));if(j===A.week)i[sb](i[Xa]()-i[rb]()+m(d,1));b=1;Ra&&(i=new Date(i.getTime()+Ra));h=i[gb]();for(var d= +i.getTime(),l=i[fb](),o=i[Xa](),n=g?Ra:(864E5+i.getTimezoneOffset()*6E4)%864E5;d=0.5)a=u(a),g=this.getLinearTickPositions(a, +b,c);else if(a>=0.08)for(var f=T(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];fb&&(!d||k<=c)&&g.push(k),k>c&&(l=!0),k=j}else if(b=ia(b),c=ia(c),a=e[d?"minorTickInterval":"tickInterval"],a=m(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=nb(a,null,mb(a)),g=Ua(this.getLinearTickPositions(a,b,c),za),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval= +a;return g};var Mb=R.Tooltip=function(){this.init.apply(this,arguments)};Mb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=z(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});fa||this.label.shadow(b.shadow);this.shared=b.shared}, +destroy:function(){if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden,h=e.followPointer||e.len>1;q(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?t:g?(2*f.anchorX+c)/3:c,anchorY:h?t:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g&&(M(a-f.x)>1||M(b-f.y)>1))clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(){var a= +this,b;clearTimeout(this.hideTimer);if(!this.isHidden)b=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){a.label.fadeOut();a.isHidden=!0},m(this.options.hideDelay,500)),b&&p(b,function(a){a.setState()}),this.chart.hoverPoints=null},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=0,i,a=qa(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===t&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(p(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=(a.plotLow? +(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:h]);return Ua(c,u)},getPosition:function(a,b,c){var d=this.chart,e=this.distance,f={},g,h=["y",d.chartHeight,b,c.plotY+d.plotTop],i=["x",d.chartWidth,a,c.plotX+d.plotLeft],j=c.ttBelow||d.inverted&&!c.negative||!d.inverted&&c.negative,k=function(a,b,c,d){var g=cb-e)return!1;else f[a]=db-c/2?b-c-2:d-c/2},o=function(a){var b=h;h=i;i=b;g=a},n=function(){k.apply(0,h)!==!1?l.apply(0,i)===!1&&!g&&(o(!0),n()):g?f.x=f.y=0:(o(!0),n())};(d.inverted||this.len>1)&&o();n();return f},defaultFormatter:function(a){var b=this.points||qa(this),c=b[0].series,d;d=[a.tooltipHeaderFormatter(b[0])];p(b,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))}); +d.push(a.options.footerFormat||"");return d.join("")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints,k,l=this.shared;clearTimeout(this.hideTimer);this.followPointer=qa(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];l&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&p(h,function(a){a.setState()}),p(a,function(a){a.setState("hover");j.push(a.getLabelConfig())}),h={x:a[0].category, +y:a[0].y},h.points=j,this.len=j.length,a=a[0]):h=a.getLabelConfig();i=i.call(h,this);h=a.series;this.distance=m(h.tooltipOptions.distance,16);i===!1?this.hide():(this.isHidden&&(bb(d),d.attr("opacity",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow}),this.isHidden=!1);D(c,"tooltipRefresh",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart, +c=this.label,c=(this.options.positioner||this.getPosition).call(this,c.width,c.height,a);this.move(u(c.x),u(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)},tooltipHeaderFormatter:function(a){var b=a.series,c=b.tooltipOptions,d=c.dateTimeLabelFormats,e=c.xDateFormat,f=b.xAxis,g=f&&f.options.type==="datetime"&&ha(a.key),c=c.headerFormat,f=f&&f.closestPointRange,h;if(g&&!e){if(f)for(h in A){if(A[h]>=f||A[h]<=A.day&&a.key%A[h]>0){e=d[h];break}}else e=d.day;e=e||d.year}g&&e&&(c=c.replace("{point.key}","{point.key:"+ +e+"}"));return Ia(c,{point:a,series:b})}};var oa;$a=y.documentElement.ontouchstart!==t;var Wa=R.Pointer=function(a,b){this.init(a,b)};Wa.prototype={init:function(a,b){var c=b.chart,d=c.events,e=fa?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(R.Tooltip&&b.tooltip.enabled)a.tooltip=new Mb(a,b.tooltip), +this.followTouchMove=b.tooltip.followTouchMove;this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||window.event,a=Sb(a);if(!a.target)a.target=a.srcElement;d=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;if(!b)this.chartPosition=b=Rb(this.chart.container);d.pageX===t?(c=v(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return q(a,{chartX:u(c),chartY:u(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};p(this.chart.axes,function(c){b[c.isXAxis?"xAxis": +"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f,g=b.hoverPoint,h=b.hoverSeries,i,j,k=b.chartWidth,l=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!h||!h.noSharedTooltip)){f=[];i=c.length;for(j=0;jk&&f.splice(i,1);if(f.length&&f[0].clientX!==this.hoverX)d.refresh(f,a),this.hoverX=f[0].clientX}c=h&&h.tooltipOptions.followPointer;if(h&&h.tracker&&!c){if((e=h.tooltipPoints[l])&&e!==g)e.onMouseOver(a)}else d&&c&&!d.isHidden&&(h=d.getAnchor([{}],a),d.updatePosition({plotX:h[0],plotY:h[1]}));if(d&&!this._onDocumentMouseMove)this._onDocumentMouseMove= +function(a){if(V[oa])V[oa].pointer.onDocumentMouseMove(a)},K(y,"mousemove",this._onDocumentMouseMove);p(b.axes,function(b){b.drawCrosshair(a,m(e,g))})},reset:function(a){var b=this.chart,c=b.hoverSeries,d=b.hoverPoint,e=b.tooltip,f=e&&e.shared?b.hoverPoints:d;(a=a&&e&&f)&&qa(f)[0].plotX===t&&(a=!1);if(a)e.refresh(f),d&&d.setState(d.state,!0);else{if(d)d.onMouseOut();if(c)c.onMouseOut();e&&e.hide();if(this._onDocumentMouseMove)W(y,"mousemove",this._onDocumentMouseMove),this._onDocumentMouseMove=null; +p(b.axes,function(a){a.hideCrosshair()});this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;p(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY}, +drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,j=b.plotWidth,k=b.plotHeight,l,o=this.mouseDownX,n=this.mouseDownY;dh+j&&(d=h+j);ei+k&&(e=i+k);this.hasDragged=Math.sqrt(Math.pow(o-d,2)+Math.pow(n-e,2));if(this.hasDragged>10){l=b.isInsidePlot(o-h,n-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,f?1:j,g?1:k,0).attr({fill:c.selectionMarkerFill|| +"rgba(69,114,167,0.25)",zIndex:7}).add();this.selectionMarker&&f&&(d-=o,this.selectionMarker.attr({width:M(d),x:(d>0?0:d)+o}));this.selectionMarker&&g&&(d=e-n,this.selectionMarker.attr({height:M(d),y:(d>0?0:d)+n}));l&&!this.selectionMarker&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},a=this.selectionMarker,e=a.attr?a.attr("x"):a.x,f=a.attr?a.attr("y"):a.y,g=a.attr?a.attr("width"): +a.width,h=a.attr?a.attr("height"):a.height,i;if(this.hasDragged||c)p(b.axes,function(a){if(a.zoomEnabled){var b=a.horiz,c=a.toValue(b?e:f),b=a.toValue(b?e+g:f+h);!isNaN(c)&&!isNaN(b)&&(d[a.coll].push({axis:a,min:C(c,b),max:v(c,b)}),i=!0)}}),i&&D(b,"selection",d,function(a){b.zoom(q(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)G(b.container,{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched= +!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){V[oa]&&V[oa].pointer.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,d=b.hoverSeries,a=this.normalize(a,c);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){var a=V[oa];if(a)a.pointer.reset(),a.pointer.chartPosition= +null},onContainerMouseMove:function(a){var b=this.chart;oa=b.index;a=this.normalize(a);b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=H(a,"class"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf("highcharts-container")!==-1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,c=(a=a.relatedTarget|| +a.toElement)&&a.point&&a.point.series;if(b&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&c!==b)b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,a=this.normalize(a);a.cancelBubble=!0;b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(D(c.series,"click",q(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(q(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&D(b,"click",a)))},setDOMEvents:function(){var a= +this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)};b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};K(b,"mouseleave",a.onContainerMouseLeave);ab===1&&K(y,"mouseup",a.onDocumentMouseUp);if($a)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},ab===1&&K(y,"touchend",a.onDocumentTouchEnd)},destroy:function(){var a;W(this.chart.container,"mouseleave",this.onContainerMouseLeave); +ab||(W(y,"mouseup",this.onDocumentMouseUp),W(y,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};q(R.Pointer.prototype,{pinchTranslate:function(a,b,c,d,e,f){(this.zoomHor||this.pinchHor)&&this.pinchTranslateDirection(!0,a,b,c,d,e,f);(this.zoomVert||this.pinchVert)&&this.pinchTranslateDirection(!1,a,b,c,d,e,f)},pinchTranslateDirection:function(a,b,c,d,e,f,g,h){var i=this.chart,j=a?"x":"y",k=a?"X":"Y",l="chart"+k,o=a?"width":"height",n=i["plot"+(a? +"Left":"Top")],s,m,p=h||1,q=i.inverted,x=i.bounds[a?"h":"v"],r=b.length===1,v=b[0][l],u=c[0][l],t=!r&&b[1][l],w=!r&&c[1][l],y,c=function(){!r&&M(v-t)>20&&(p=h||M(u-w)/M(v-t));m=(n-u)/p+v;s=i["plot"+(a?"Width":"Height")]/p};c();b=m;bx.max&&(b=x.max-s,y=!0);y?(u-=0.8*(u-g[j][0]),r||(w-=0.8*(w-g[j][1])),c()):g[j]=[u,w];q||(f[j]=m-n,f[o]=s);f=q?1/p:p;e[o]=s;e[j]=b;d[q?a?"scaleY":"scaleX":"scale"+k]=p;d["translate"+k]=f*n+(u-f*v)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown, +e=b.followTouchMove,f=a.touches,g=f.length,h=b.lastValidTouch,i=b.hasZoom,j=b.selectionMarker,k={},l=g===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||c.runChartClick),o={};(i||e)&&!l&&a.preventDefault();Ua(f,function(a){return b.normalize(a)});if(a.type==="touchstart")p(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,d[1]&&d[1].chartY],p(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding, +e=a.toPixels(a.dataMin),f=a.toPixels(a.dataMax),g=C(e,f),e=v(e,f);b.min=C(a.pos,g-d);b.max=v(a.pos+a.len,e+d)}});else if(d.length){if(!j)b.selectionMarker=j=q({destroy:sa},c.plotBox);b.pinchTranslate(d,f,k,j,o,h);b.hasPinched=i;b.scaleGroups(k,o);!i&&e&&g===1&&this.runPointActions(b.normalize(a))}},onContainerTouchStart:function(a){var b=this.chart;oa=b.index;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),this.pinch(a)):this.reset()): +a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length===1||a.touches.length===2)&&this.pinch(a)},onDocumentTouchEnd:function(a){V[oa]&&V[oa].pointer.drop(a)}});if(I.PointerEvent||I.MSPointerEvent){var ua={},zb=!!I.PointerEvent,Wb=function(){var a,b=[];b.item=function(a){return this[a]};for(a in ua)ua.hasOwnProperty(a)&&b.push({pageX:ua[a].pageX,pageY:ua[a].pageY,target:ua[a].target});return b},Ab=function(a,b,c,d){a=a.originalEvent||a;if((a.pointerType==="touch"|| +a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&V[oa])d(a),d=V[oa].pointer,d[b]({type:c,target:a.currentTarget,preventDefault:sa,touches:Wb()})};q(Wa.prototype,{onContainerPointerDown:function(a){Ab(a,"onContainerTouchStart","touchstart",function(a){ua[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){Ab(a,"onContainerTouchMove","touchmove",function(a){ua[a.pointerId]={pageX:a.pageX,pageY:a.pageY};if(!ua[a.pointerId].target)ua[a.pointerId].target=a.currentTarget})}, +onDocumentPointerUp:function(a){Ab(a,"onContainerTouchEnd","touchend",function(a){delete ua[a.pointerId]})},batchMSEvents:function(a){a(this.chart.container,zb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,zb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(y,zb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}});Ma(Wa.prototype,"init",function(a,b,c){a.call(this,b,c);(this.hasZoom||this.followTouchMove)&&G(b.container,{"-ms-touch-action":Q,"touch-action":Q})}); +Ma(Wa.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&&this.batchMSEvents(K)});Ma(Wa.prototype,"destroy",function(a){this.batchMSEvents(W);a.call(this)})}var lb=R.Legend=function(a,b){this.init(a,b)};lb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=m(b.padding,8),f=b.itemMarginTop||0;this.options=b;if(b.enabled)c.baseline=z(d.fontSize)+3+f,c.itemStyle=d,c.itemHiddenStyle=w(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY= +e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.symbolWidth=m(b.symbolWidth,16),c.pages=[],c.render(),K(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options,d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&a.options.marker,i={fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in i.stroke=h,g=a.convertAttribs(g), +g)d=g[j],d!==t&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;p(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Pa(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()}, +positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c=b.translateY,p(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,G(f,{left:b.translateX+e.checkboxOffset+f.x-20+"px",top:g+"px",display:g>c-6&&g(o||b.chartWidth-2*j-p-d.x))this.itemX=p,this.itemY+=s+this.lastLineHeight+n,this.lastLineHeight=0;this.maxItemWidth=v(this.maxItemWidth,f);this.lastItemY=s+this.itemY+n;this.lastLineHeight=v(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=s+g+n,this.lastLineHeight=g);this.offsetWidth=o||v((e?this.itemX-p-k:f)+j,this.offsetWidth)},getAllItems:function(){var a= +[];p(this.chart.series,function(b){var c=b.options;if(m(c.showInLegend,!r(c.linkedTo)?t:!1,!0))a=a.concat(b.legendItems||(c.legendType==="point"?b.data:b))});return a},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,o=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup); +a.renderTitle();e=a.getAllItems();ob(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;p(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(l||o){g+=k;h+=k;if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp({width:g,height:h})),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor, +"stroke-width":l||0,fill:o||Q}).add(d).shadow(j.shadow),i.isNew=!0;i[f?"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;p(e,function(b){a.positionItem(b)});f&&d.align(q({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h,i=this.clipRect,j=e.navigation,k=m(j.animation,!0),l=j.arrowSize||12,o=this.nav,n=this.pages, +s,q=this.allItems;e.layout==="horizontal"&&(f/=2);g&&(f=C(f,g));n.length=0;if(a>f&&!e.useHTML){this.clipHeight=h=f-20-this.titleHeight-this.padding;this.currentPage=m(this.currentPage,1);this.fullHeight=a;p(q,function(a,b){var c=a._legendItemPos[1],d=u(a.legendItem.getBBox().height),e=n.length;if(!e||c-n[e-1]>h&&(s||c)!==n[e-1])n.push(s||c),e++;b===q.length-1&&c+d-n[e-1]>h&&n.push(c);c!==s&&(s=c)});if(!i)i=b.clipRect=d.clipRect(0,this.padding,9999,0),b.contentGroup.clip(i);i.attr({height:h});if(!o)this.nav= +o=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,l,l).on("click",function(){b.scroll(-1,k)}).add(o),this.pager=d.text("",15,10).css(j.style).add(o),this.down=d.symbol("triangle-down",0,0,l,l).on("click",function(){b.scroll(1,k)}).add(o);b.scroll(0);a=f}else if(o)i.attr({height:c.chartHeight}),o.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a,f=this.clipHeight,g=this.options.navigation, +h=g.activeColor,g=g.inactiveColor,i=this.pager,j=this.padding;e>d&&(e=d);if(e>0)b!==t&&Qa(b,this.chart),this.nav.attr({translateX:j,translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:e===1?g:h}).css({cursor:e===1?"default":"pointer"}),i.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}),this.currentPage=e,this.positionCheckboxes(c)}}; +N=R.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||12;b.legendSymbol=this.chart.renderer.rect(0,a.baseline-5-c/2,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=this.options,c=b.marker,d;d=a.symbolWidth;var e=this.chart.renderer,f=this.legendGroup,a=a.baseline-u(e.fontMetrics(a.options.itemStyle.fontSize).b*0.3),g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle=b.dashStyle;this.legendLine= +e.path(["M",0,a,"L",d,a]).attr(g).add(f)}if(c&&c.enabled!==!1)b=c.radius,this.legendSymbol=d=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0}};(/Trident\/7\.0/.test(wa)||Ta)&&Ma(lb.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});Ya.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=w(E,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing", +d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=V.length;V.push(f);ab++;d.reflow!==!1&&K(f,"load",function(){f.initReflow()});if(e)for(g in e)K(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=fa?!1:m(d.animation,!0);f.pointCount=0;f.counters=new Bb;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=F[a.type||b.type||b.defaultSeriesType])||ra(17,!0);b=new b;b.init(this, +a);return b},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&p(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.isDirtyBox,j=c.length,k=j,l=this.renderer,o=l.isHidden(),n=[];Qa(a,this);o&&this.cloneRenderTo();for(this.layOutTitles();k--;)if(a=c[k],a.options.stacking&& +(g=!0,a.isDirty)){h=!0;break}if(h)for(k=j;k--;)if(a=c[k],a.options.stacking)a.isDirty=!0;p(c,function(a){a.isDirty&&a.options.legendType==="point"&&(f=!0)});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(this.hasCartesianSeries){if(!this.isResizing)this.maxTicks=null,p(b,function(a){a.setScale()});this.adjustTickAmounts();this.getMargins();p(b,function(a){a.isDirty&&(i=!0)});p(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,n.push(function(){D(a,"afterSetExtremes", +q(a.eventArgs,a.getExtremes()));delete a.eventArgs});(i||g)&&a.redraw()})}i&&this.drawChartBox();p(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);l.draw();D(this,"redraw");o&&this.cloneRenderTo(!0);p(n,function(a){a.call()})},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container; +a?b&&(this.renderTo.appendChild(c),Pa(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),G(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),y.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+tb++;if(Fa(a))this.renderTo=a=y.getElementById(a); +a||ra(13,!0);c=z(H(a,"data-highcharts-chart"));!isNaN(c)&&V[c]&&V[c].hasRendered&&V[c].destroy();H(a,"data-highcharts-chart",this.index);a.innerHTML="";!b.skipClone&&!a.offsetWidth&&this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=Y(Ja,{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},q({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"}, +b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=b.forExport?new ta(a,c,d,b.style,!0):new Za(a,c,d,b.style);fa&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,f=m(e.margin,20),g=e.x,h=e.y,i=e.align,j=e.verticalAlign,k=this.titleOffset;this.resetMargins();b=this.axisOffset;if(k&&!r(d[0]))this.plotTop=v(this.plotTop,k+this.options.title.margin+a[0]);if(c.display&&!e.floating)if(i==="right"){if(!r(d[1]))this.marginRight= +v(this.marginRight,c.legendWidth-g+f+a[1])}else if(i==="left"){if(!r(d[3]))this.plotLeft=v(this.plotLeft,c.legendWidth+g+f+a[3])}else if(j==="top"){if(!r(d[0]))this.plotTop=v(this.plotTop,c.legendHeight+h+f+a[0])}else if(j==="bottom"&&!r(d[2]))this.marginBottom=v(this.marginBottom,c.legendHeight-h+f+a[2]);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&p(this.axes,function(a){a.getOffset()});r(d[3])|| +(this.plotLeft+=b[3]);r(d[0])||(this.plotTop+=b[0]);r(d[2])||(this.marginBottom+=b[2]);r(d[1])||(this.marginRight+=b[1]);this.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||jb(d,"width"),f=c.height||jb(d,"height"),c=a?a.target:I,d=function(){if(b.container)b.setSize(e,f,!1),b.hasUserSize=null};if(!b.hasUserSize&&e&&f&&(c===I||c===y)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),a?b.reflowTimeout=setTimeout(d,100):d();b.containerWidth= +e;b.containerHeight=f}},initReflow:function(){var a=this,b=function(b){a.reflow(b)};K(I,"resize",b);K(a,"destroy",function(){W(I,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&D(d,"endResize",null,function(){d.isResizing-=1})};Qa(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(r(a))d.chartWidth=e=v(0,u(a)),d.hasUserSize=!!e;if(r(b))d.chartHeight=f=v(0,u(b));(va?kb:G)(d.container,{width:e+"px",height:f+"px"},va);d.setChartSize(!0);d.renderer.setSize(e, +f,c);d.maxTicks=null;p(d.axes,function(a){a.isDirty=!0;a.setScale()});p(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;D(d,"resize");va===!1?g():setTimeout(g,va&&va.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=u(this.plotLeft);this.plotTop=j=u(this.plotTop);this.plotWidth= +k=v(0,u(d-i-this.marginRight));this.plotHeight=l=v(0,u(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l};d=2*T(this.plotBorderWidth/2);b=Ka(v(d,h[3])/2);c=Ka(v(d,h[0])/2);this.clipBox={x:b,y:c,width:T(this.plotSizeX-v(d,h[1])/2-b),height:T(this.plotSizeY-v(d,h[2])/2-c)};a||p(this.axes,function(a){a.setAxisSize(); +a.setAxisTranslation()})},resetMargins:function(){var a=this.spacing,b=this.margin;this.plotTop=m(b[0],a[0]);this.marginRight=m(b[1],a[1]);this.marginBottom=m(b[2],a[2]);this.plotLeft=m(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage, +o=a.plotBorderWidth||0,n,s=this.plotLeft,m=this.plotTop,p=this.plotWidth,q=this.plotHeight,r=this.plotBox,v=this.clipRect,u=this.clipBox;n=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp({width:c-n,height:d-n}));else{e={fill:j||Q};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(n/2,n/2,c-n,d-n,a.borderRadius,i).attr(e).addClass("highcharts-background").add().shadow(a.shadow)}if(k)f?f.animate(r):this.plotBackground=b.rect(s,m,p,q,0).attr({fill:k}).add().shadow(a.plotShadow); +if(l)h?h.animate(r):this.plotBGImage=b.image(l,s,m,p,q).add();v?v.animate({width:u.width,height:u.height}):this.clipRect=b.clipRect(u);if(o)g?g.animate(g.crisp({x:s,y:m,width:p,height:q})):this.plotBorder=b.rect(s,m,p,q,0,-o).attr({stroke:a.plotBorderColor,"stroke-width":o,fill:Q,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;p(["inverted","angular","polar"],function(g){c=F[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g]; +for(e=d&&d.length;!f&&e--;)(c=F[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;p(b,function(a){a.linkedSeries.length=0});p(b,function(b){var d=b.options.linkedTo;if(Fa(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d})},renderSeries:function(){p(this.series,function(a){a.translate();a.setTooltipPoints&&a.setTooltipPoints();a.render()})},render:function(){var a=this,b=a.axes,c=a.renderer,d=a.options,e=d.labels,f= +d.credits,g;a.setTitle();a.legend=new lb(a,d.legend);a.getStacks();p(b,function(a){a.setScale()});a.getMargins();a.maxTicks=null;p(b,function(a){a.setTickPositions(!0);a.setMaxTicks()});a.adjustTickAmounts();a.getMargins();a.drawChartBox();a.hasCartesianSeries&&p(b,function(a){a.render()});if(!a.seriesGroup)a.seriesGroup=c.g("series-group").attr({zIndex:3}).add();a.renderSeries();e.items&&p(e.items,function(b){var d=q(e.style,b.style),f=z(d.left)+a.plotLeft,g=z(d.top)+a.plotTop+12;delete d.left;delete d.top; +c.text(b.html,f,g).attr({zIndex:2}).css(d).add()});if(f.enabled&&!a.credits)g=f.href,a.credits=c.text(f.text,0,0).on("click",function(){if(g)location.href=g}).attr({align:f.position.align,zIndex:8}).css(f.style).add().align(f.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;D(a,"destroy");V[a.index]=t;ab--;a.renderTo.removeAttribute("data-highcharts-chart");W(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy(); +p("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",W(d),f&&Pa(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!aa&&I==I.top&&y.readyState!=="complete"||fa&&!I.canvg?(fa?Lb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):y.attachEvent("onreadystatechange", +function(){y.detachEvent("onreadystatechange",a.firstRender);y.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender()){a.getContainer();D(a,"init");a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();p(b.series||[],function(b){a.initSeries(b)});a.linkSeries();D(a,"beforeRender");if(R.Pointer)a.pointer=new Wa(a,b);a.render();a.renderer.draw();c&&c.apply(a,[a]);p(a.callbacks,function(b){b.apply(a,[a])});a.cloneRenderTo(!0); +D(a,"load")}},splashArray:function(a,b){var c=b[a],c=ca(c)?c:[c,c,c,c];return[m(b[a+"Top"],c[0]),m(b[a+"Right"],c[1]),m(b[a+"Bottom"],c[2]),m(b[a+"Left"],c[3])]}};Ya.prototype.callbacks=[];X=R.CenteredSeriesMixin={getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,a=[m(b[0],"50%"),m(b[1],"50%"),a.size||"100%",a.innerSize||0],g=C(e,f),h;return Ua(a,function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*z(a)/100: +a)+(d?c:0)})}};var Ea=function(){};Ea.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.pointValKey,a=Ea.prototype.optionsToObject.call(this,a);q(this,a);this.options=this.options?q(this.options,a):a;if(d)this.y=this[d];if(this.x=== +t&&c)this.x=b===t?c.autoIncrement():b;return this},optionsToObject:function(a){var b={},c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if(typeof a==="number"||a===null)b[d[0]]=a;else if(La(a)){if(a.length>e){c=typeof a[0];if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];f++}for(;ga+1&&b.push(d.slice(a+ +1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=w(e,c.series,a);this.tooltipOptions=w(E.tooltip,E.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&d[this.type].tooltip,a.tooltip);e.marker===null&&delete c.marker;return c},getColor:function(){var a=this.options,b=this.userOptions,c=this.chart.options.colors,d=this.chart.counters, +e;e=a.color||ba[this.type].color;if(!e&&!a.colorByPoint)r(b._colorIndex)?a=b._colorIndex:(b._colorIndex=d.color,a=d.color++),e=c[a];this.color=e;d.wrapColor(c.length)},getSymbol:function(){var a=this.userOptions,b=this.options.marker,c=this.chart,d=c.options.symbols,c=c.counters;this.symbol=b.symbol;if(!this.symbol)r(a._symbolIndex)?a=a._symbolIndex:(a._symbolIndex=c.symbol,a=c.symbol++),this.symbol=d[a];if(/^url/.test(this.symbol))b.radius=0;c.wrapSymbol(d.length)},drawLegendSymbol:N.drawLineMarker, +setData:function(a,b,c,d){var e=this,f=e.points,g=f&&f.length||0,h,i=e.options,j=e.chart,k=null,l=e.xAxis,o=l&&!!l.categories,n=e.tooltipPoints,s=i.turboThreshold,q=this.xData,r=this.yData,v=(h=e.pointArrayMap)&&h.length,a=a||[];h=a.length;b=m(b,!0);if(d!==!1&&h&&g===h&&!e.cropped&&!e.hasGroupedData)p(a,function(a,b){f[b].update(a,!1)});else{e.xIncrement=null;e.pointRange=o?1:i.pointRange;e.colorCounter=0;p(this.parallelArrays,function(a){e[a+"Data"].length=0});if(s&&h>s){for(c=0;k===null&&cj||this.forceCrop))if(o=h.min,n=h.max,b[d-1]n)b=[],c=[];else if(b[0]n)e=this.cropData(this.xData,this.yData,o,n),b=e.xData,c=e.yData, +e=e.start,f=!0,k=b.length;for(d=b.length-1;d>=0;d--)a=b[d]-b[d-1],!f&&b[d]>o&&b[d]0&&(g===t||a=c){f=v(0,i-h);break}for(;id){g=i+h;break}return{xData:a.slice(f,g),yData:b.slice(f, +g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],o;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(o=0;o0),j=this.getExtremesFromAll||this.cropped||(c[l+1]||j)>=g&&(c[l-1]||j)<=h,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=k;this.dataMin=m(void 0,Na(e));this.dataMax=m(void 0,Ba(e))},translate:function(){this.processedXData|| +this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i==="between"||ha(i),k=a.threshold,a=0;a0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h).attr(a).add(n)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a, +b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=m(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=ba[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color;f={stroke:g,fill:g};var h=a.points||[],i,j=[],k,l=a.pointAttrToOptions;k=a.hasPointSpecificOptions;var o=b.negativeColor,n=c.lineColor,s=c.fillColor;i=b.turboThreshold;var m;b.marker?(e.radius=e.radius||c.radius+2,e.lineWidth=e.lineWidth||c.lineWidth+1):e.color= +e.color||ya(e.color||g).brighten(e.brightness).get();j[""]=a.convertAttribs(c,f);p(["hover","select"],function(b){j[b]=a.convertAttribs(d[b],j[""])});a.pointAttr=j;g=h.length;if(!i||g1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=b.linecap!=="square",g=this.getGraphPath(), +h=b.negativeColor;h&&c.push(["graphNeg",h]);p(c,function(c,h){var k=c[0],l=a[k];if(l)bb(l),l.animate({d:g});else if(d&&g.length)l={stroke:c[1],"stroke-width":d,fill:Q,zIndex:1},e?l.dashstyle=e:f&&(l["stroke-linecap"]=l["stroke-linejoin"]="round"),a[k]=a.chart.renderer.path(g).attr(l).add(a.group).shadow(!h&&b.shadow)})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j= +b.chartHeight,k=v(e,j),l=this.yAxis;if(d&&(f||g)){d=u(l.toPixels(a.threshold||0,!0));d<0&&(k-=d);a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e});l.reversed?(b=k,e=a):(b=a,e=k);h?(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}}, +invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};p(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)K(c,"resize",a),K(b,"destroy",function(){W(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a=this.chart,b=this.xAxis,c=this.yAxis; +if(a.inverted)b=c,c=this.xAxis;return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=(c=d.animation)&&!!a.animate&&b.renderer.isSVG&&m(c.duration,500)||0,f=a.visible?"visible":"hidden",g=d.zIndex,h=a.hasRendered,i=b.seriesGroup;c=a.plotGroup("group","series",f,g,i);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,i);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted:!1;a.drawGraph&&(a.drawGraph(), +a.clipNeg());a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&a.options.enableMouseTracking!==!1&&a.drawTracker();b.inverted&&a.invertGroups();d.clip!==!1&&!a.sharedClipKey&&!h&&c.clip(b.clipRect);e&&a.animate();if(!h)e?a.animationTimeout=setTimeout(function(){a.afterAnimate()},e):a.afterAnimate();a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth, +height:a.plotHeight}),c.animate({translateX:m(d&&d.left,a.plotLeft),translateY:m(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints&&this.setTooltipPoints(!0);this.render();b&&D(this,"updatedData")}};Hb.prototype={destroy:function(){Oa(this,this.axis)},render:function(a){var b=this.options,c=b.format,c=c?Ia(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:"hidden"}):this.label=this.axis.chart.renderer.text(c,null,null,b.useHTML).css(b.style).attr({align:this.textAlign, +rotation:b.rotation,visibility:"hidden"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,g=c.translate(c.usePercentage?100:this.total,0,0,0,1),c=c.translate(0),c=M(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e[this.options.crop===!1||d.isInsidePlot(f.x,f.y)?"show":"hide"](!0)}};la.prototype.buildStacks=function(){var a= +this.series,b=m(this.options.reversedStacks,!0),c=a.length;if(!this.isXAxis){for(this.usePercentage=!1;c--;)a[b?c:a.length-c-1].setStackedPoints();if(this.usePercentage)for(c=0;cg;)h--;this.updateParallelArrays(d,"splice",h,0,0);this.updateParallelArrays(d, +h);if(j)j[g]=d.name;l.splice(h,0,a);o&&(this.data.splice(h,0,null),this.processData());e.legendType==="point"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),this.updateParallelArrays(d,"shift"),l.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),i.redraw())},remove:function(a,b){var c=this,d=c.chart,a=m(a,!0);if(!c.isRemoving)c.isRemoving=!0,D(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving= +!1},update:function(a,b){var c=this.chart,d=this.type,e=F[d].prototype,f,a=w(this.userOptions,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(f in e)e.hasOwnProperty(f)&&(this[f]=t);q(this,F[a.type||d].prototype);this.init(c,a);m(b,!0)&&c.redraw(!1)}});q(la.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=w(this.userOptions,a);this.destroy(!0);this._addedPlotLB=t;this.init(c,q(a,{events:t}));c.isDirtyBox= +!0;m(b,!0)&&c.redraw()},remove:function(a){for(var b=this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1);ja(b.axes,this);ja(b[c],this);b.options[c].splice(this.options.index,1);p(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;m(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});ga=ka(O);F.line=ga;ba.area=w(S,{threshold:0});var pa=ka(O,{type:"area",getSegments:function(){var a= +[],b=[],c=[],d=this.xAxis,e=this.yAxis,f=e.stacks[this.stackKey],g={},h,i,j=this.points,k=this.options.connectNulls,l,o,n;if(this.options.stacking&&!this.cropped){for(o=0;o=0;d--)g=m(a[d].yBottom,f),da&&i>e?(i=v(a,e),k=2*e-i):ig&&k>e?(k=v(g,e),i=2*e-k):k0.5*a.xAxis.len?0:1),e=a.yAxis,f=a.translatedThreshold=e.getThreshold(c.threshold),g=m(c.minPointLength,5),c=a.getColumnMetrics(),h=c.width,i=a.barW=Ka(v(h,1+2*d)),j=a.pointXOffset=c.offset,k=-(d%2?0.5:0),l=d%2?0.5:1;b.renderer.isVML&&b.inverted&&(l+=1);O.prototype.translate.apply(a);p(a.points,function(c){var d=m(c.yBottom,f),p=C(v(-999-d,c.plotY),e.len+ +999+d),q=c.plotX+j,r=i,t=C(p,d),x;x=v(p,d)-t;M(x)g?d-g:f-(e.translate(c.y,0,1,0,1)<=f?g:0)));c.barX=q;c.pointWidth=h;c.tooltipPos=b.inverted?[e.len-p,a.xAxis.len-q-r/2]:[q+r/2,p];d=M(q)<0.5;r=u(q+r)+k;q=u(q)+k;r-=q;p=M(t)<0.5;x=u(t+x)+l;t=u(t)+l;x-=t;d&&(q+=1,r-=1);p&&(t-=1,x+=1);c.shapeType="rect";c.shapeArgs={x:q,y:t,width:r,height:x}})},getSymbol:sa,drawLegendSymbol:N.drawRectangle,drawGraph:sa,drawPoints:function(){var a=this,b=this.chart,c=a.options,d=b.renderer,e=c.animationLimit|| +250,f,g,h;p(a.points,function(i){var j=i.plotY,k=i.graphic;if(j!==t&&!isNaN(j)&&i.y!==null)f=i.shapeArgs,h=r(a.borderWidth)?{"stroke-width":a.borderWidth}:{},g=i.pointAttr[i.selected?"select":""]||a.pointAttr[""],k?(bb(k),k.attr(h)[b.pointCount● {series.name}
', +pointFormat:"x: {point.x}
y: {point.y}
"},stickyTracking:!1});pa=ka(O,{type:"scatter",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["markerGroup"],takeOrdinalPosition:!1,singularTooltips:!0,drawGraph:function(){this.options.lineWidth&&O.prototype.drawGraph.call(this)}});F.scatter=pa;ba.pie=w(S,{borderColor:"#FFFFFF",borderWidth:1,center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},ignoreHiddenPoint:!0, +legendType:"point",marker:null,size:null,showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});S={type:"pie",isCartesian:!1,pointClass:ka(Ea,{init:function(){Ea.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;q(a,{visible:a.visible!==!1,name:m(a.name,"Slice")});b=function(b){a.slice(b.type==="select")};K(a,"select",b);K(a,"unselect",b);return a},setVisible:function(a){var b=this,c=b.series,d=c.chart;b.visible=b.options.visible= +a=a===t?!b.visible:a;c.options.data[Da(b,c.data)]=b.options;p(["graphic","dataLabel","connector","shadowGroup"],function(c){if(b[c])b[c][a?"show":"hide"](!0)});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;Qa(c,d.chart);m(b,!0);this.sliced=this.options.sliced=a=r(a)?a:!this.sliced;d.options.data[Da(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a); +this.shadowGroup&&this.shadowGroup.animate(a)},haloPath:function(a){var b=this.shapeArgs,c=this.series.chart;return this.series.chart.renderer.symbols.arc(c.plotLeft+b.x,c.plotTop+b.y,b.r+a,b.r+a,{innerR:this.shapeArgs.r,start:b.start,end:b.end})}}),requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},singularTooltips:!0,getColor:sa,animate:function(a){var b=this,c=b.points,d= +b.startAngleRad;if(!a)p(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b,c,d){O.prototype.setData.call(this,a,!1,c,d);this.processData();this.generatePoints();m(b,!0)&&this.chart.redraw(c)},generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;O.prototype.generatePoints.call(this);c=this.points;d=c.length;for(a=0;a0?e.y/b*100:0,e.total=b},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=ma/180*(i-90),i=(this.endAngleRad=ma/180*(m(c.endAngle,i+360)-90))-j,k=this.points,l=c.dataLabels.distance,c=c.ignoreHiddenPoint,o,n=k.length,p;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=U.asin(C((b-a[1])/(a[2]/2+l),1));return a[0]+(c?-1:1)*Z(h)*(a[2]/ +2+l)};for(o=0;o1.5*ma?h-=2*ma:h<-ma/2&&(h+=2*ma);p.slicedTranslation={translateX:u(Z(h)*d),translateY:u(ea(h)*d)};f=Z(h)*a[2]/2;g=ea(h)*a[2]/2;p.tooltipPos=[a[0]+f*0.7,a[1]+g*0.7];p.half=h<-ma/2||h>ma/2?1:0;p.angle=h;e=C(e,l/2);p.labelPos=[a[0]+f+Z(h)*l,a[1]+g+ea(h)*l,a[0]+f+Z(h)*e,a[1]+g+ea(h)*e,a[0]+f,a[1]+g,l<0? +"center":p.half?"right":"left",h]}},drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);p(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g("shadow").add(a.shadowGroup);c=h.sliced?h.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(q(g,c)):h.graphic=d=b[h.shapeType](g).setRadialReference(a.center).attr(h.pointAttr[h.selected?"select": +""]).attr({"stroke-linejoin":"round"}).attr(c).add(a.group).shadow(e,f);h.visible!==void 0&&h.setVisible(h.visible)})},sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawLegendSymbol:N.drawRectangle,getCenter:X.getCenter,getSymbol:sa};S=ka(O,S);F.pie=S;O.prototype.drawDataLabels=function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h,i;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),i=a.plotGroup("dataLabelsGroup", +"data-labels","hidden",d.zIndex||6),!a.hasRendered&&m(d.defer,!0)&&(i.attr({opacity:0}),K(a,"afterAnimate",function(){a.dataLabelsGroup.show()[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,p(e,function(b){var e,l=b.dataLabel,o,n,p=b.connector,u=!0;f=b.options&&b.options.dataLabels;e=m(f&&f.enabled,g.enabled);if(l&&!e)b.dataLabel=l.destroy();else if(e){d=w(g,f);e=d.rotation;o=b.getLabelConfig();h=d.format?Ia(d.format,o):d.formatter.call(o,d);d.style.color=m(d.color,d.style.color, +a.color,"black");if(l)if(r(h))l.attr({text:h}),u=!1;else{if(b.dataLabel=l=l.destroy(),p)b.connector=p.destroy()}else if(r(h)){l={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:e,padding:d.padding,zIndex:1};for(n in l)l[n]===t&&delete l[n];l=b.dataLabel=a.chart.renderer[e?"text":"label"](h,0,-999,null,null,null,d.useHTML).attr(l).css(q(d.style,c&&{cursor:c})).add(i).shadow(d.shadow)}l&&a.alignDataLabel(b,l,d,null,u)}})};O.prototype.alignDataLabel= +function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=m(a.plotX,-999),i=m(a.plotY,-999),j=b.getBBox();if(a=this.visible&&(a.series.forceDL||f.isInsidePlot(h,u(i),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-1,g)))d=q({x:g?f.plotWidth-i:h,y:u(g?f.plotHeight-h:i),width:0,height:0},d),q(c,{width:j.width,height:j.height}),c.rotation?(g={align:c.align,x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2},b[e?"attr":"animate"](g)):(b.align(c,null,d),g=b.alignAttr,m(c.overflow,"justify")==="justify"?this.justifyDataLabel(b, +c,g,j,d,e):m(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)));if(!a)b.attr({y:-999}),b.placed=!1};O.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k;j=c.x;if(j<0)h==="right"?b.align="left":b.x=-j,k=!0;j=c.x+d.width;if(j>g.plotWidth)h==="left"?b.align="right":b.x=g.plotWidth-j,k=!0;j=c.y;if(j<0)i==="bottom"?b.verticalAlign="top":b.y=-j,k=!0;j=c.y+d.height;if(j>g.plotHeight)i==="top"?b.verticalAlign="bottom":b.y=g.plotHeight- +j,k=!0;if(k)a.placed=!f,a.align(b,null,e)};if(F.pie)F.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=m(e.connectorPadding,10),g=m(e.connectorWidth,1),h=d.plotWidth,d=d.plotHeight,i,j,k=m(e.softConnector,!0),l=e.distance,o=a.center,n=o[2]/2,q=o[1],r=l>0,t,w,x,y,z=[[],[]],A,C,G,D,B,F=[0,0,0,0],N=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){O.prototype.drawDataLabels.apply(a);p(b,function(a){a.dataLabel&&a.visible&&z[a.half].push(a)}); +for(D=0;!y&&b[D];)y=b[D]&&b[D].dataLabel&&(b[D].dataLabel.getBBox().height||21),D++;for(D=2;D--;){var b=[],K=[],H=z[D],I=H.length,E;a.sortByAngle(H,D-0.5);if(l>0){for(B=q-n-l;B<=q+n+l;B+=y)b.push(B);w=b.length;if(I>w){c=[].concat(H);c.sort(N);for(B=I;B--;)c[B].rank=B;for(B=I;B--;)H[B].rank>=w&&H.splice(B,1);I=H.length}for(B=0;B0){if(w=K.pop(),E=w.i,C=w.y,c>C&&b[E+1]!==null||ch-f&&(F[1]=v(u(A+w-h+f),F[1])),C-y/2<0?F[0]= +v(u(-C+y/2),F[0]):C+y/2>d&&(F[2]=v(u(C+y/2-d),F[2]))}}if(Ba(F)===0||this.verifyDataLabelOverflow(F))this.placeDataLabels(),r&&g&&p(this.points,function(b){i=b.connector;x=b.labelPos;if((t=b.dataLabel)&&t._pos)G=t._attr.visibility,A=t.connX,C=t.connY,j=k?["M",A+(x[6]==="left"?5:-5),C,"C",A,C,2*x[2]-x[4],2*x[3]-x[5],x[2],x[3],"L",x[4],x[5]]:["M",A+(x[6]==="left"?5:-5),C,"L",x[2],x[3],"L",x[4],x[5]],i?(i.animate({d:j}),i.attr("visibility",G)):b.connector=i=a.chart.renderer.path(j).attr({"stroke-width":g, +stroke:e.connectorColor||b.color||"#606060",visibility:G}).add(a.dataLabelsGroup);else if(i)b.connector=i.destroy()})}},F.pie.prototype.placeDataLabels=function(){p(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999})})},F.pie.prototype.alignDataLabel=sa,F.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||80,f;d[0]!==null?e=v(b[2]-v(a[1],a[3]),c):(e=v(b[2]- +a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);d[1]!==null?e=v(C(e,b[2]-v(a[0],a[2])),c):(e=v(C(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);em(this.translatedThreshold,f.plotSizeY),j=m(c.inside,!!this.options.stacking);if(h&& +(d=w(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=m(c.align,!g||j?"center":i?"right":"left");c.verticalAlign=m(c.verticalAlign,g||j?"middle":i?"top":"bottom");O.prototype.alignDataLabel.call(this,a,b,c,d,e)};S=R.TrackerMixin={drawTrackerPoint:function(){var a=this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver(); +for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==t&&e!==b.hoverPoint)e.onMouseOver(c)};p(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)p(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),$a))a[b].on("touchstart",f)}),a._hasTracking=!0},drawTrackerGraph:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath: +a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,n=function(){if(f.hoverSeries!==a)a.onMouseOver()},q="rgba(192,192,192,"+(aa?1.0E-4:0.002)+")";if(e&&!c)for(m=e+1;m--;)d[m]==="M"&&d.splice(m+1,0,d[m+1]-i,d[m+2],"L"),(m&&d[m]==="M"||m===e)&&d.splice(m,0,"L",d[m-2]+i,d[m-1]);for(m=0;mC(k.dataMin,k.min)&&i=f.min&&c<=f.max){h=b[i+1];c=d===t?0:d+1;for(d=b[i+1]?C(v(0,T((e.clientX+ +(h?h.wrappedClientX||h.clientX:g))/2)),g):g;c>=0&&c<=d;)j[c++]=e}this.tooltipPoints=j}},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===t?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;D(this,a?"select":"unselect")},drawTracker:S.drawTrackerGraph});q(R,{Axis:la,Chart:Ya,Color:ya,Point:Ea,Tick:Sa,Renderer:Za,Series:O,SVGElement:P,SVGRenderer:ta,arrayMin:Na,arrayMax:Ba,charts:V,dateFormat:cb,format:Ia,pathAnim:ub,getOptions:function(){return E}, +hasBidiBug:Nb,isTouchDevice:Jb,numberFormat:Ga,seriesTypes:F,setOptions:function(a){E=w(!0,E,a);Cb();return E},addEvent:K,removeEvent:W,createElement:Y,discardElement:Pa,css:G,each:p,extend:q,map:Ua,merge:w,pick:m,splat:qa,extendClass:ka,pInt:z,wrap:Ma,svg:aa,canvas:fa,vml:!aa&&!fa,product:"Highcharts",version:"4.0.1"})})(); +/* + Highcharts JS v4.0.1 (2014-04-24) + + (c) 2009-2014 Torstein Honsi + + License: www.highcharts.com/license +*/ +(function(m,C){function K(a,b,c){this.init.call(this,a,b,c)}var O=m.arrayMin,P=m.arrayMax,s=m.each,F=m.extend,o=m.merge,Q=m.map,q=m.pick,x=m.pInt,p=m.getOptions().plotOptions,h=m.seriesTypes,u=m.extendClass,L=m.splat,r=m.wrap,M=m.Axis,y=m.Tick,H=m.Point,R=m.Pointer,S=m.CenteredSeriesMixin,z=m.TrackerMixin,t=m.Series,v=Math,D=v.round,A=v.floor,T=v.max,U=m.Color,w=function(){};F(K.prototype,{init:function(a,b,c){var d=this,e=d.defaultOptions;d.chart=b;if(b.angular)e.background={};d.options=a=o(e,a); +(a=a.background)&&s([].concat(L(a)).reverse(),function(a){var g=a.backgroundColor,a=o(d.defaultBackgroundOptions,a);if(g)a.backgroundColor=g;a.color=a.backgroundColor;c.options.plotBands.unshift(a)})},defaultOptions:{center:["50%","50%"],size:"85%",startAngle:0},defaultBackgroundOptions:{shape:"circle",borderWidth:1,borderColor:"silver",backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,"#FFF"],[1,"#DDD"]]},from:Number.MIN_VALUE,innerRadius:0,to:Number.MAX_VALUE,outerRadius:"105%"}}); +var G=M.prototype,y=y.prototype,V={getOffset:w,redraw:function(){this.isDirty=!1},render:function(){this.isDirty=!1},setScale:w,setCategories:w,setTitle:w},N={isRadial:!0,defaultRadialGaugeOptions:{labels:{align:"center",x:0,y:null},minorGridLineWidth:0,minorTickInterval:"auto",minorTickLength:10,minorTickPosition:"inside",minorTickWidth:1,tickLength:10,tickPosition:"inside",tickWidth:2,title:{rotation:0},zIndex:2},defaultRadialXOptions:{gridLineWidth:1,labels:{align:null,distance:15,x:0,y:null}, +maxPadding:0,minPadding:0,showLastLabel:!1,tickLength:0},defaultRadialYOptions:{gridLineInterpolation:"circle",labels:{align:"right",x:-3,y:-2},showLastLabel:!1,title:{x:4,text:null,rotation:90}},setOptions:function(a){a=this.options=o(this.defaultOptions,this.defaultRadialOptions,a);if(!a.plotBands)a.plotBands=[]},getOffset:function(){G.getOffset.call(this);this.chart.axisOffset[this.side]=0;this.center=this.pane.center=S.getCenter.call(this.pane)},getLinePath:function(a,b){var c=this.center,b=q(b, +c[2]/2-this.offset);return this.chart.renderer.symbols.arc(this.left+c[0],this.top+c[1],b,b,{start:this.startAngleRad,end:this.endAngleRad,open:!0,innerR:0})},setAxisTranslation:function(){G.setAxisTranslation.call(this);if(this.center)this.transA=this.isCircular?(this.endAngleRad-this.startAngleRad)/(this.max-this.min||1):this.center[2]/2/(this.max-this.min||1),this.minPixelPadding=this.isXAxis?this.transA*this.minPointOffset:0},beforeSetTickPositions:function(){this.autoConnect&&(this.max+=this.categories&& +1||this.pointRange||this.closestPointRange||0)},setAxisSize:function(){G.setAxisSize.call(this);if(this.isRadial){this.center=this.pane.center=m.CenteredSeriesMixin.getCenter.call(this.pane);if(this.isCircular)this.sector=this.endAngleRad-this.startAngleRad;this.len=this.width=this.height=this.center[2]*q(this.sector,1)/2}},getPosition:function(a,b){return this.postTranslate(this.isCircular?this.translate(a):0,q(this.isCircular?b:this.translate(a),this.center[2]/2)-this.offset)},postTranslate:function(a, +b){var c=this.chart,d=this.center,a=this.startAngleRad+a;return{x:c.plotLeft+d[0]+Math.cos(a)*b,y:c.plotTop+d[1]+Math.sin(a)*b}},getPlotBandPath:function(a,b,c){var d=this.center,e=this.startAngleRad,f=d[2]/2,g=[q(c.outerRadius,"100%"),c.innerRadius,q(c.thickness,10)],k=/%$/,l,n=this.isCircular;this.options.gridLineInterpolation==="polygon"?d=this.getPlotLinePath(a).concat(this.getPlotLinePath(b,!0)):(n||(g[0]=this.translate(a),g[1]=this.translate(b)),g=Q(g,function(a){k.test(a)&&(a=x(a,10)*f/100); +return a}),c.shape==="circle"||!n?(a=-Math.PI/2,b=Math.PI*1.5,l=!0):(a=e+this.translate(a),b=e+this.translate(b)),d=this.chart.renderer.symbols.arc(this.left+d[0],this.top+d[1],g[0],g[0],{start:a,end:b,innerR:q(g[1],g[0]-g[2]),open:l}));return d},getPlotLinePath:function(a,b){var c=this,d=c.center,e=c.chart,f=c.getPosition(a),g,k,l;c.isCircular?l=["M",d[0]+e.plotLeft,d[1]+e.plotTop,"L",f.x,f.y]:c.options.gridLineInterpolation==="circle"?(a=c.translate(a))&&(l=c.getLinePath(0,a)):(s(e.xAxis,function(a){a.pane=== +c.pane&&(g=a)}),l=[],a=c.translate(a),d=g.tickPositions,g.autoConnect&&(d=d.concat([d[0]])),b&&(d=[].concat(d).reverse()),s(d,function(f,c){k=g.getPosition(f,a);l.push(c?"L":"M",k.x,k.y)}));return l},getTitlePosition:function(){var a=this.center,b=this.chart,c=this.options.title;return{x:b.plotLeft+a[0]+(c.x||0),y:b.plotTop+a[1]-{high:0.5,middle:0.25,low:0}[c.align]*a[2]+(c.y||0)}}};r(G,"init",function(a,b,c){var i;var d=b.angular,e=b.polar,f=c.isX,g=d&&f,k,l;l=b.options;var n=c.pane||0;if(d){if(F(this, +g?V:N),k=!f)this.defaultRadialOptions=this.defaultRadialGaugeOptions}else if(e)F(this,N),this.defaultRadialOptions=(k=f)?this.defaultRadialXOptions:o(this.defaultYAxisOptions,this.defaultRadialYOptions);a.call(this,b,c);if(!g&&(d||e)){a=this.options;if(!b.panes)b.panes=[];this.pane=(i=b.panes[n]=b.panes[n]||new K(L(l.pane)[n],b,this),n=i);n=n.options;b.inverted=!1;l.chart.zoomType=null;this.startAngleRad=b=(n.startAngle-90)*Math.PI/180;this.endAngleRad=l=(q(n.endAngle,n.startAngle+360)-90)*Math.PI/ +180;this.offset=a.offset||0;if((this.isCircular=k)&&c.max===C&&l-b===2*Math.PI)this.autoConnect=!0}});r(y,"getPosition",function(a,b,c,d,e){var f=this.axis;return f.getPosition?f.getPosition(c):a.call(this,b,c,d,e)});r(y,"getLabelPosition",function(a,b,c,d,e,f,g,k,l){var n=this.axis,j=f.y,i=f.align,h=(n.translate(this.pos)+n.startAngleRad+Math.PI/2)/Math.PI*180%360;n.isRadial?(a=n.getPosition(this.pos,n.center[2]/2+q(f.distance,-25)),f.rotation==="auto"?d.attr({rotation:h}):j===null&&(j=n.chart.renderer.fontMetrics(d.styles.fontSize).b- +d.getBBox().height/2),i===null&&(i=n.isCircular?h>20&&h<160?"left":h>200&&h<340?"right":"center":"center",d.attr({align:i})),a.x+=f.x,a.y+=j):a=a.call(this,b,c,d,e,f,g,k,l);return a});r(y,"getMarkPath",function(a,b,c,d,e,f,g){var k=this.axis;k.isRadial?(a=k.getPosition(this.pos,k.center[2]/2+d),b=["M",b,c,"L",a.x,a.y]):b=a.call(this,b,c,d,e,f,g);return b});p.arearange=o(p.area,{lineWidth:1,marker:null,threshold:null,tooltip:{pointFormat:'● {series.name}: {point.low} - {point.high}
'}, +trackByArea:!0,dataLabels:{verticalAlign:null,xLow:0,xHigh:0,yLow:0,yHigh:0},states:{hover:{halo:!1}}});h.arearange=u(h.area,{type:"arearange",pointArrayMap:["low","high"],toYData:function(a){return[a.low,a.high]},pointValKey:"low",getSegments:function(){var a=this;s(a.points,function(b){if(!a.options.connectNulls&&(b.low===null||b.high===null))b.y=null;else if(b.low===null&&b.high!==null)b.y=b.high});t.prototype.getSegments.call(this)},translate:function(){var a=this.yAxis;h.area.prototype.translate.apply(this); +s(this.points,function(b){var c=b.low,d=b.high,e=b.plotY;d===null&&c===null?b.y=null:c===null?(b.plotLow=b.plotY=null,b.plotHigh=a.translate(d,0,1,0,1)):d===null?(b.plotLow=e,b.plotHigh=null):(b.plotLow=e,b.plotHigh=a.translate(d,0,1,0,1))})},getSegmentPath:function(a){var b,c=[],d=a.length,e=t.prototype.getSegmentPath,f,g;g=this.options;var k=g.step;for(b=HighchartsAdapter.grep(a,function(a){return a.plotLow!==null});d--;)f=a[d],f.plotHigh!==null&&c.push({plotX:f.plotX,plotY:f.plotHigh});a=e.call(this, +b);if(k)k===!0&&(k="left"),g.step={left:"right",center:"center",right:"left"}[k];c=e.call(this,c);g.step=k;g=[].concat(a,c);c[0]="L";this.areaPath=this.areaPath.concat(a,c);return g},drawDataLabels:function(){var a=this.data,b=a.length,c,d=[],e=t.prototype,f=this.options.dataLabels,g,k=this.chart.inverted;if(f.enabled||this._hasPointLabels){for(c=b;c--;)g=a[c],g.y=g.high,g._plotY=g.plotY,g.plotY=g.plotHigh,d[c]=g.dataLabel,g.dataLabel=g.dataLabelUpper,g.below=!1,k?(f.align="left",f.x=f.xHigh):f.y= +f.yHigh;e.drawDataLabels&&e.drawDataLabels.apply(this,arguments);for(c=b;c--;)g=a[c],g.dataLabelUpper=g.dataLabel,g.dataLabel=d[c],g.y=g.low,g.plotY=g._plotY,g.below=!0,k?(f.align="right",f.x=f.xLow):f.y=f.yLow;e.drawDataLabels&&e.drawDataLabels.apply(this,arguments)}},alignDataLabel:function(){h.column.prototype.alignDataLabel.apply(this,arguments)},getSymbol:h.column.prototype.getSymbol,drawPoints:w});p.areasplinerange=o(p.arearange);h.areasplinerange=u(h.arearange,{type:"areasplinerange",getPointSpline:h.spline.prototype.getPointSpline}); +(function(){var a=h.column.prototype;p.columnrange=o(p.column,p.arearange,{lineWidth:1,pointRange:null});h.columnrange=u(h.arearange,{type:"columnrange",translate:function(){var b=this,c=b.yAxis,d;a.translate.apply(b);s(b.points,function(a){var f=a.shapeArgs,g=b.options.minPointLength,k;a.tooltipPos=null;a.plotHigh=d=c.translate(a.high,0,1,0,1);a.plotLow=a.plotY;k=d;a=a.plotY-d;a● {series.name}
Maximum: {point.high}
Upper quartile: {point.q3}
Median: {point.median}
Lower quartile: {point.q1}
Minimum: {point.low}
'}, +whiskerLength:"50%",whiskerWidth:2});h.boxplot=u(h.column,{type:"boxplot",pointArrayMap:["low","q1","median","q3","high"],toYData:function(a){return[a.low,a.q1,a.median,a.q3,a.high]},pointValKey:"high",pointAttrToOptions:{fill:"fillColor",stroke:"color","stroke-width":"lineWidth"},drawDataLabels:w,translate:function(){var a=this.yAxis,b=this.pointArrayMap;h.column.prototype.translate.apply(this);s(this.points,function(c){s(b,function(b){c[b]!==null&&(c[b+"Plot"]=a.translate(c[b],0,1,0,1))})})},drawPoints:function(){var a= +this,b=a.points,c=a.options,d=a.chart.renderer,e,f,g,k,l,n,j,i,h,m,p,I,r,o,J,u,w,t,v,x,z,y,E=a.doQuartiles!==!1,B=parseInt(a.options.whiskerLength,10)/100;s(b,function(b){h=b.graphic;z=b.shapeArgs;p={};o={};u={};y=b.color||a.color;if(b.plotY!==C)if(e=b.pointAttr[b.selected?"selected":""],w=z.width,t=A(z.x),v=t+w,x=D(w/2),f=A(E?b.q1Plot:b.lowPlot),g=A(E?b.q3Plot:b.lowPlot),k=A(b.highPlot),l=A(b.lowPlot),p.stroke=b.stemColor||c.stemColor||y,p["stroke-width"]=q(b.stemWidth,c.stemWidth,c.lineWidth),p.dashstyle= +b.stemDashStyle||c.stemDashStyle,o.stroke=b.whiskerColor||c.whiskerColor||y,o["stroke-width"]=q(b.whiskerWidth,c.whiskerWidth,c.lineWidth),u.stroke=b.medianColor||c.medianColor||y,u["stroke-width"]=q(b.medianWidth,c.medianWidth,c.lineWidth),u["stroke-linecap"]="round",j=p["stroke-width"]%2/2,i=t+x+j,m=["M",i,g,"L",i,k,"M",i,f,"L",i,l],E&&(j=e["stroke-width"]%2/2,i=A(i)+j,f=A(f)+j,g=A(g)+j,t+=j,v+=j,I=["M",t,g,"L",t,f,"L",v,f,"L",v,g,"L",t,g,"z"]),B&&(j=o["stroke-width"]%2/2,k+=j,l+=j,r=["M",i-x*B, +k,"L",i+x*B,k,"M",i-x*B,l,"L",i+x*B,l]),j=u["stroke-width"]%2/2,n=D(b.medianPlot)+j,J=["M",t,n,"L",v,n],h)b.stem.animate({d:m}),B&&b.whiskers.animate({d:r}),E&&b.box.animate({d:I}),b.medianShape.animate({d:J});else{b.graphic=h=d.g().add(a.group);b.stem=d.path(m).attr(p).add(h);if(B)b.whiskers=d.path(r).attr(o).add(h);if(E)b.box=d.path(I).attr(e).add(h);b.medianShape=d.path(J).attr(u).add(h)}})}});p.errorbar=o(p.boxplot,{color:"#000000",grouping:!1,linkedTo:":previous",tooltip:{pointFormat:'● {series.name}: {point.low} - {point.high}
'}, +whiskerWidth:null});h.errorbar=u(h.boxplot,{type:"errorbar",pointArrayMap:["low","high"],toYData:function(a){return[a.low,a.high]},pointValKey:"high",doQuartiles:!1,drawDataLabels:h.arearange?h.arearange.prototype.drawDataLabels:w,getColumnMetrics:function(){return this.linkedParent&&this.linkedParent.columnMetrics||h.column.prototype.getColumnMetrics.call(this)}});p.waterfall=o(p.column,{lineWidth:1,lineColor:"#333",dashStyle:"dot",borderColor:"#333"});h.waterfall=u(h.column,{type:"waterfall",upColorProp:"fill", +pointArrayMap:["low","y"],pointValKey:"y",init:function(a,b){b.stacking=!0;h.column.prototype.init.call(this,a,b)},translate:function(){var a=this.yAxis,b,c,d,e,f,g,k,l,n;b=this.options.threshold;h.column.prototype.translate.apply(this);l=b;d=this.points;for(c=0,b=d.length;c0&&!a.color)a.pointAttr=d,a.color=c})},getGraphPath:function(){var a=this.data,b=a.length, +c=D(this.options.lineWidth+this.borderWidth)%2/2,d=[],e,f,g;for(g=1;g0?(k[f]-a)/(b- +a):0.5,n&&g>=0&&(g=Math.sqrt(g)),l.push(v.ceil(c+g*(d-c))/2);this.radii=l},animate:function(a){var b=this.options.animation;if(!a)s(this.points,function(a){var d=a.graphic,a=a.shapeArgs;d&&a&&(d.attr("r",1),d.animate({r:a.r},b))}),this.animate=null},translate:function(){var a,b=this.data,c,d,e=this.radii;h.scatter.prototype.translate.call(this);for(a=b.length;a--;)c=b[a],d=e?e[a]:0,c.negative=c.z<(this.options.zThreshold||0),d>=this.minPxSize/2?(c.shapeType="circle",c.shapeArgs={x:c.plotX,y:c.plotY, +r:d},c.dlBox={x:c.plotX-d,y:c.plotY-d,width:2*d,height:2*d}):c.shapeArgs=c.plotY=c.dlBox=C},drawLegendSymbol:function(a,b){var c=x(a.itemStyle.fontSize)/2;b.legendSymbol=this.chart.renderer.circle(c,a.baseline-c,c).attr({zIndex:3}).add(b.legendGroup);b.legendSymbol.isMarker=!0},drawPoints:h.column.prototype.drawPoints,alignDataLabel:h.column.prototype.alignDataLabel});M.prototype.beforePadding=function(){var a=this,b=this.len,c=this.chart,d=0,e=b,f=this.isXAxis,g=f?"xData":"yData",k=this.min,l={}, +n=v.min(c.plotWidth,c.plotHeight),j=Number.MAX_VALUE,i=-Number.MAX_VALUE,h=this.max-k,m=b/h,p=[];this.tickPositions&&(s(this.series,function(b){var g=b.options;if(b.bubblePadding&&(b.visible||!c.options.chart.ignoreHiddenSeries))if(a.allowZoomOutside=!0,p.push(b),f)s(["minSize","maxSize"],function(a){var b=g[a],f=/%$/.test(b),b=x(b);l[a]=f?n*b/100:b}),b.minPxSize=l.minSize,b=b.zData,b.length&&(j=v.min(j,v.max(O(b),g.displayNegative===!1?g.zThreshold:-Number.MAX_VALUE)),i=v.max(i,P(b)))}),s(p,function(a){var b= +a[g],c=b.length,n;f&&a.getRadii(j,i,l.minSize,l.maxSize);if(h>0)for(;c--;)typeof b[c]==="number"&&(n=a.radii[c],d=Math.min((b[c]-k)*m-n,d),e=Math.max((b[c]-k)*m+n,e))}),p.length&&h>0&&q(this.options.min,this.userMin)===C&&q(this.options.max,this.userMax)===C&&(e-=b,m*=(b+d-e)/b,this.min+=d/m,this.max+=e/m))};(function(){function a(a,b,c){a.call(this,b,c);if(this.chart.polar)this.closeSegment=function(a){var b=this.xAxis.center;a.push("L",b[0],b[1])},this.closedStacks=!0}function b(a,b){var c=this.chart, +d=this.options.animation,e=this.group,j=this.markerGroup,i=this.xAxis.center,h=c.plotLeft,m=c.plotTop;if(c.polar){if(c.renderer.isSVG)d===!0&&(d={}),b?(c={translateX:i[0]+h,translateY:i[1]+m,scaleX:0.001,scaleY:0.001},e.attr(c),j&&j.attr(c)):(c={translateX:h,translateY:m,scaleX:1,scaleY:1},e.animate(c,d),j&&j.animate(c,d),this.animate=null)}else a.call(this,b)}var c=t.prototype,d=R.prototype,e;c.toXY=function(a){var b,c=this.chart,d=a.plotX;b=a.plotY;a.rectPlotX=d;a.rectPlotY=b;d=(d/Math.PI*180+this.xAxis.pane.options.startAngle)% +360;d<0&&(d+=360);a.clientX=d;b=this.xAxis.postTranslate(a.plotX,this.yAxis.len-b);a.plotX=a.polarPlotX=b.x-c.plotLeft;a.plotY=a.polarPlotY=b.y-c.plotTop};c.orderTooltipPoints=function(a){if(this.chart.polar&&(a.sort(function(a,b){return a.clientX-b.clientX}),a[0]))a[0].wrappedClientX=a[0].clientX+360,a.push(a[0])};h.area&&r(h.area.prototype,"init",a);h.areaspline&&r(h.areaspline.prototype,"init",a);h.spline&&r(h.spline.prototype,"getPointSpline",function(a,b,c,d){var e,j,i,h,m,p,o;if(this.chart.polar){e= +c.plotX;j=c.plotY;a=b[d-1];i=b[d+1];this.connectEnds&&(a||(a=b[b.length-2]),i||(i=b[1]));if(a&&i)h=a.plotX,m=a.plotY,b=i.plotX,p=i.plotY,h=(1.5*e+h)/2.5,m=(1.5*j+m)/2.5,i=(1.5*e+b)/2.5,o=(1.5*j+p)/2.5,b=Math.sqrt(Math.pow(h-e,2)+Math.pow(m-j,2)),p=Math.sqrt(Math.pow(i-e,2)+Math.pow(o-j,2)),h=Math.atan2(m-j,h-e),m=Math.atan2(o-j,i-e),o=Math.PI/2+(h+m)/2,Math.abs(h-o)>Math.PI/2&&(o-=Math.PI),h=e+Math.cos(o)*b,m=j+Math.sin(o)*b,i=e+Math.cos(Math.PI+o)*p,o=j+Math.sin(Math.PI+o)*p,c.rightContX=i,c.rightContY= +o;d?(c=["C",a.rightContX||a.plotX,a.rightContY||a.plotY,h||e,m||j,e,j],a.rightContX=a.rightContY=null):c=["M",e,j]}else c=a.call(this,b,c,d);return c});r(c,"translate",function(a){a.call(this);if(this.chart.polar&&!this.preventPostTranslate)for(var a=this.points,b=a.length;b--;)this.toXY(a[b])});r(c,"getSegmentPath",function(a,b){var c=this.points;if(this.chart.polar&&this.options.connectEnds!==!1&&b[b.length-1]===c[c.length-1]&&c[0].y!==null)this.connectEnds=!0,b=[].concat(b,[c[0]]);return a.call(this, +b)});r(c,"animate",b);r(c,"setTooltipPoints",function(a,b){this.chart.polar&&F(this.xAxis,{tooltipLen:360});return a.call(this,b)});if(h.column)e=h.column.prototype,r(e,"animate",b),r(e,"translate",function(a){var b=this.xAxis,c=this.yAxis.len,d=b.center,e=b.startAngleRad,h=this.chart.renderer,i,m;this.preventPostTranslate=!0;a.call(this);if(b.isRadial){b=this.points;for(m=b.length;m--;)i=b[m],a=i.barX+e,i.shapeType="path",i.shapeArgs={d:h.symbols.arc(d[0],d[1],c-i.plotY,null,{start:a,end:a+i.pointWidth, +innerR:c-q(i.yBottom,c)})},this.toXY(i),i.tooltipPos=[i.plotX,i.plotY],i.ttBelow=i.plotY>d[1]}}),r(e,"alignDataLabel",function(a,b,d,e,h,j){if(this.chart.polar){a=b.rectPlotX/Math.PI*180;if(e.align===null)e.align=a>20&&a<160?"left":a>200&&a<340?"right":"center";if(e.verticalAlign===null)e.verticalAlign=a<45||a>315?"bottom":a>135&&a<225?"top":"middle";c.alignDataLabel.call(this,b,d,e,h,j)}else a.call(this,b,d,e,h,j)});r(d,"getIndex",function(a,b){var c,d=this.chart,e;d.polar?(e=d.xAxis[0].center,c= +b.chartX-e[0]-d.plotLeft,d=b.chartY-e[1]-d.plotTop,c=180-Math.round(Math.atan2(c,d)/Math.PI*180)):c=a.call(this,b);return c});r(d,"getCoordinates",function(a,b){var c=this.chart,d={xAxis:[],yAxis:[]};c.polar?s(c.axes,function(a){var e=a.isXAxis,f=a.center,h=b.chartX-f[0]-c.plotLeft,f=b.chartY-f[1]-c.plotTop;d[e?"xAxis":"yAxis"].push({axis:a,value:a.translate(e?Math.PI-Math.atan2(h,f):Math.sqrt(Math.pow(h,2)+Math.pow(f,2)),!0)})}):d=a.call(this,b);return d})})()})(Highcharts); +/* + Highcharts JS v4.0.1 (2014-04-24) + + (c) 2009-2013 Torstein Hønsi + + License: www.highcharts.com/license +*/ +(function(c){function x(e,a,b,d){var f,g,h;b*=n;a*=n;var i=[],j,o,t;b*=-1;j=d.x;o=d.y;t=(d.z===0?1.0E-4:d.z)*(d.vd||25);var y=k(b),v=l(b),m=k(a),q=l(a),r,u,s;c.each(e,function(a){r=a.x-j;u=a.y-o;s=a.z||0;f=v*r-y*s;g=-y*m*r-v*m*s+q*u;h=y*q*r+v*q*s+m*u;f=f*((t-h)/t)+j;g=g*((t-h)/t)+o;i.push({x:C(f),y:C(g),z:C(h)})});return i}function z(e,a,b,d,f,c,h,i){var j=[];return c>f&&c-f>m/2+1.0E-4?(j=j.concat(z(e,a,b,d,f,f+m/2,h,i)),j=j.concat(z(e,a,b,d,f+m/2,c,h,i))):cm/2+1.0E-4?(j=j.concat(z(e,a,b, +d,f,f-m/2,h,i)),j=j.concat(z(e,a,b,d,f-m/2,c,h,i))):(j=c-f,["C",e+b*l(f)-b*D*j*k(f)+h,a+d*k(f)+d*D*j*l(f)+i,e+b*l(c)+b*D*j*k(c)+h,a+d*k(c)-d*D*j*l(c)+i,e+b*l(c)+h,a+d*k(c)+i])}function F(e){if(this.chart.is3d()){var a=this.chart.options.plotOptions.column.grouping;a!==void 0&&!a&&this.group.zIndex!==void 0&&this.group.attr({zIndex:this.group.zIndex*10});if(this.userOptions.borderColor===void 0)this.options.borderColor=this.color;c.each(this.data,function(a){var d=a.options.borderColor||a.color||a.series.userOptions.borderColor; +a.options.borderColor=d;a.borderColor=d;a.pointAttr[""].stroke=d;a.pointAttr.hover.stroke=d;a.pointAttr.select.stroke=d})}e.apply(this,[].slice.call(arguments,1))}var m=Math.PI,n=m/180,k=Math.sin,l=Math.cos,C=Math.round,D=4*(Math.sqrt(2)-1)/3/(m/2);c.SVGRenderer.prototype.toLinePath=function(e,a){var b=[];c.each(e,function(a){b.push("L",a.x,a.y)});b[0]="M";a&&b.push("Z");return b};c.SVGRenderer.prototype.cuboid=function(e){var a=this.g(),e=this.cuboidPath(e);a.front=this.path(e[0]).attr({zIndex:e[3], +"stroke-linejoin":"round"}).add(a);a.top=this.path(e[1]).attr({zIndex:e[4],"stroke-linejoin":"round"}).add(a);a.side=this.path(e[2]).attr({zIndex:e[5],"stroke-linejoin":"round"}).add(a);a.fillSetter=function(a){var d=c.Color(a).brighten(0.1).get(),e=c.Color(a).brighten(-0.1).get();this.front.attr({fill:a});this.top.attr({fill:d});this.side.attr({fill:e});this.color=a;return this};a.opacitySetter=function(a){this.front.attr({opacity:a});this.top.attr({opacity:a});this.side.attr({opacity:a});return this}; +a.attr=function(a){a.shapeArgs||a.x?(a=this.renderer.cuboidPath(a.shapeArgs||a),this.front.attr({d:a[0],zIndex:a[3]}),this.top.attr({d:a[1],zIndex:a[4]}),this.side.attr({d:a[2],zIndex:a[5]})):c.SVGElement.prototype.attr.call(this,a);return this};a.animate=function(a,d,e){a.x&&a.y?(a=this.renderer.cuboidPath(a),this.front.attr({zIndex:a[3]}).animate({d:a[0]},d,e),this.top.attr({zIndex:a[4]}).animate({d:a[1]},d,e),this.side.attr({zIndex:a[5]}).animate({d:a[2]},d,e)):a.opacity?(this.front.animate(a, +d,e),this.top.animate(a,d,e),this.side.animate(a,d,e)):c.SVGElement.prototype.animate.call(this,a,d,e);return this};a.destroy=function(){this.front.destroy();this.top.destroy();this.side.destroy();return null};a.attr({zIndex:-e[3]});return a};c.SVGRenderer.prototype.cuboidPath=function(e){var a=e.x,b=e.y,d=e.z,c=e.height,g=e.width,h=e.depth,i=e.alpha,j=e.beta,a=[{x:a,y:b,z:d},{x:a+g,y:b,z:d},{x:a+g,y:b+c,z:d},{x:a,y:b+c,z:d},{x:a,y:b+c,z:d+h},{x:a+g,y:b+c,z:d+h},{x:a+g,y:b,z:d+h},{x:a,y:b,z:d+h}], +a=x(a,i,j,e.origin),e=["M",a[0].x,a[0].y,"L",a[7].x,a[7].y,"L",a[6].x,a[6].y,"L",a[1].x,a[1].y,"Z"],b=["M",a[3].x,a[3].y,"L",a[2].x,a[2].y,"L",a[5].x,a[5].y,"L",a[4].x,a[4].y,"Z"],d=["M",a[1].x,a[1].y,"L",a[2].x,a[2].y,"L",a[5].x,a[5].y,"L",a[6].x,a[6].y,"Z"],c=["M",a[0].x,a[0].y,"L",a[7].x,a[7].y,"L",a[4].x,a[4].y,"L",a[3].x,a[3].y,"Z"];return[["M",a[0].x,a[0].y,"L",a[1].x,a[1].y,"L",a[2].x,a[2].y,"L",a[3].x,a[3].y,"Z"],a[7].ya[2].y?b:[],a[6].x>a[1].x?d:a[7].x0?(a[0].z+a[7].z+a[6].z+a[1].z)/4:(a[3].z+a[2].z+a[5].z+a[4].z)/4,i>0?(a[1].z+a[2].z+a[5].z+a[6].z)/4:(a[0].z+a[7].z+a[4].z+a[3].z)/4]};c.SVGRenderer.prototype.arc3d=function(e){e.alpha*=n;e.beta*=n;var a=this.g(),b=this.arc3dPath(e),d=a.renderer,f=b.zAll*100;a.shapeArgs=e;a.side1=d.path(b.side2).attr({zIndex:b.zSide2}).add(a);a.side2=d.path(b.side1).attr({zIndex:b.zSide1}).add(a);a.inn=d.path(b.inn).attr({zIndex:b.zInn}).add(a);a.out=d.path(b.out).attr({zIndex:b.zOut}).add(a); +a.top=d.path(b.top).attr({zIndex:b.zTop}).add(a);a.fillSetter=function(a){this.color=a;var b=c.Color(a).brighten(-0.1).get();this.side1.attr({fill:b});this.side2.attr({fill:b});this.inn.attr({fill:b});this.out.attr({fill:b});this.top.attr({fill:a});return this};a.animate=function(a,b,d){c.SVGElement.prototype.animate.call(this,a,b,d);if(a.x&&a.y)b=this.renderer,a=c.splat(a)[0],a.alpha*=n,a.beta*=n,b=b.arc3dPath(a),this.shapeArgs=a,this.inn.attr({d:b.inn,zIndex:b.zInn}),this.out.attr({d:b.out,zIndex:b.zOut}), +this.side1.attr({d:b.side1,zIndex:b.zSide2}),this.side2.attr({d:b.side2,zIndex:b.zSide1}),this.top.attr({d:b.top,zIndex:b.zTop}),this.attr({fill:this.color}),this.attr({zIndex:b.zAll*100});return this};a.zIndex=f;a.attr({zIndex:f});return a};c.SVGRenderer.prototype.arc3dPath=function(e){var a=e.x,b=e.y,d=e.start,c=e.end-1.0E-5,g=e.r,h=e.innerR,i=e.depth,j=e.alpha,o=e.beta,t=l(d),y=k(d),v=l(c),n=k(c),q=g*l(o),r=g*l(j),u=h*l(o),s=h*l(j),A=i*k(o),B=i*k(j),i=["M",a+q*t,b+r*y],i=i.concat(z(a,b,q,r,d,c, +0,0)),i=i.concat(["L",a+u*v,b+s*n]),i=i.concat(z(a,b,u,s,c,d,0,0)),i=i.concat(["Z"]),e=(e.start+e.end)/2,e=k(o)*l(e)+k(-j)*k(-e),p=o>0?m/2:0,w=j>0?0:m/2,p=d>-p?d:c>-p?-p:d,x=c0?4:-1}).css({stroke:i.color}).add(); +else{var q={x:m,y:n,z:o+1,width:l,height:k+i.size,depth:j.size,alpha:f,beta:g,origin:d};this.backFrame?this.backFrame.animate(q):this.backFrame=b.cuboid(q).attr({fill:j.color,zIndex:-3}).css({stroke:j.color}).add();this.axisLine&&this.axisLine.hide();a={x:(a.yAxis[0].opposite?l:0)+m-h.size,y:n,z:0,width:h.size,height:k+i.size,depth:o+j.size,alpha:f,beta:g,origin:d};this.sideFrame?this.sideFrame.animate(a):this.sideFrame=b.cuboid(a).attr({fill:h.color,zIndex:-2}).css({stroke:h.color}).add()}}});c.wrap(c.Axis.prototype, +"getPlotLinePath",function(c){var a=c.apply(this,[].slice.call(arguments,1));if(!this.chart.is3d())return a;if(a===null)return a;var b=this.chart,d=b.options.chart.options3d,f=d.depth;d.origin={x:b.plotLeft+b.plotWidth/2,y:b.plotTop+b.plotHeight/2,z:f,vd:d.viewDistance};var a=[{x:a[1],y:a[2],z:this.horiz||this.opposite?f:0},{x:a[1],y:a[2],z:f},{x:a[4],y:a[5],z:f},{x:a[4],y:a[5],z:this.horiz||this.opposite?0:f}],f=b.options.inverted?d.beta:d.alpha,g=b.options.inverted?d.alpha:d.beta;g*=b.yAxis[0].opposite? +-1:1;a=x(a,f,g,d.origin);return a=this.chart.renderer.toLinePath(a,!1)});c.wrap(c.Tick.prototype,"getMarkPath",function(c){var a=c.apply(this,[].slice.call(arguments,1));if(!this.axis.chart.is3d())return a;var b=this.axis.chart,d=b.options.chart.options3d,f={x:b.plotLeft+b.plotWidth/2,y:b.plotTop+b.plotHeight/2,z:d.depth,vd:d.viewDistance},a=[{x:a[1],y:a[2],z:0},{x:a[4],y:a[5],z:0}],g=b.inverted?d.beta:d.alpha,d=b.inverted?d.alpha:d.beta;d*=b.yAxis[0].opposite?-1:1;a=x(a,g,d,f);return a=["M",a[0].x, +a[0].y,"L",a[1].x,a[1].y]});c.wrap(c.Tick.prototype,"getLabelPosition",function(c){var a=c.apply(this,[].slice.call(arguments,1));if(!this.axis.chart.is3d())return a;var b=this.axis.chart,d=b.options.chart.options3d,f={x:b.plotLeft+b.plotWidth/2,y:b.plotTop+b.plotHeight/2,z:d.depth,vd:d.viewDistance},g=b.inverted?d.beta:d.alpha,d=b.inverted?d.alpha:d.beta;d*=b.yAxis[0].opposite?-1:1;return a=x([{x:a.x,y:a.y,z:0}],g,d,f)[0]});c.wrap(c.Axis.prototype,"drawCrosshair",function(c){var a=arguments;this.chart.is3d()&& +a[2]&&(a[2]={plotX:a[2].plotXold||a[2].plotX,plotY:a[2].plotYold||a[2].plotY});c.apply(this,[].slice.call(a,1))});c.wrap(c.seriesTypes.column.prototype,"translate",function(e){e.apply(this,[].slice.call(arguments,1));if(this.chart.is3d()){var a=this.chart,b=a.options,d=b.plotOptions[this.chart.options.chart.type],b=b.chart.options3d,f=d.depth||25,g={x:a.plotWidth/2,y:a.plotHeight/2,z:b.depth,vd:b.viewDistance},h=b.alpha,i=b.beta*(a.yAxis[0].opposite?-1:1),j=(d.stacking?this.options.stack||0:this._i)* +(f+(d.groupZPadding||1));d.grouping!==!1&&(j=0);j+=d.groupZPadding||1;c.each(this.data,function(a){var b=a.shapeArgs,c=a.tooltipPos;a.shapeType="cuboid";b.alpha=h;b.beta=i;b.z=j;b.origin=g;b.depth=f;c=x([{x:c[0],y:c[1],z:j}],h,i,g)[0];a.tooltipPos=[c.x,c.y]})}});c.wrap(c.seriesTypes.column.prototype,"animate",function(e){if(this.chart.is3d()){var a=arguments[1],b=this.yAxis,d=this,f=this.yAxis.reversed;if(c.svg)a?c.each(d.data,function(a){a.height=a.shapeArgs.height;a.shapeArgs.height=1;if(!f)a.shapeArgs.y= +a.stackY?a.plotY+b.translate(a.stackY):a.plotY+(a.negative?-a.height:a.height)}):(c.each(d.data,function(a){a.shapeArgs.height=a.height;if(!f)a.shapeArgs.y=a.plotY-(a.negative?a.height:0);a.graphic&&a.graphic.animate(a.shapeArgs,d.options.animation)}),d.animate=null)}else e.apply(this,[].slice.call(arguments,1))});c.wrap(c.seriesTypes.column.prototype,"init",function(c){c.apply(this,[].slice.call(arguments,1));if(this.chart.is3d()){var a=this.chart.options.plotOptions.column.grouping,b=this.chart.options.plotOptions.column.stacking, +d=this.options.zIndex;if(!d&&(a===void 0||a)&&b){a=this.chart.retrieveStacks();b=this.options.stack||0;for(d=0;d0?k(h)*e:0),-c*(1-l(g))*k(b)+(k(b)>0?k(g)*e:0));a.dataLabel&&a.dataLabel.attr({x:a.dataLabel.connX+-c*(1-l(h))*l(b)+(l(b)>0?l(h)*e:0)-a.dataLabel.width/2,y:a.dataLabel.connY+-c*(1-l(g))*k(b)+(k(b)>0?k(g)*e:0)-a.dataLabel.height/2})})});c.wrap(c.seriesTypes.pie.prototype,"addPoint",function(c){c.apply(this,[].slice.call(arguments,1));this.chart.is3d()&&this.update()});c.wrap(c.seriesTypes.pie.prototype,"animate",function(e){if(this.chart.is3d()){var a=arguments[1],b=this.options.animation,d= +this.center,f=this.group,g=this.markerGroup;if(c.svg)if(b===!0&&(b={}),a){if(this.oldtranslateX=f.translateX,this.oldtranslateY=f.translateY,a={translateX:d[0],translateY:d[1],scaleX:0.001,scaleY:0.001},f.attr(a),g)g.attrSetters=f.attrSetters,g.attr(a)}else a={translateX:this.oldtranslateX,translateY:this.oldtranslateY,scaleX:1,scaleY:1},f.animate(a,b),g&&g.animate(a,b),this.animate=null}else e.apply(this,[].slice.call(arguments,1))});c.wrap(c.seriesTypes.scatter.prototype,"translate",function(e){e.apply(this, +[].slice.call(arguments,1));if(this.chart.is3d()){var a=this.chart,b=this.chart.options.chart.options3d,d=b.alpha,f=b.beta,g={x:a.inverted?a.plotHeight/2:a.plotWidth/2,y:a.inverted?a.plotWidth/2:a.plotHeight/2,z:b.depth,vd:b.viewDistance},b=b.depth,h=a.options.zAxis||{min:0,max:b},i=b/(h.max-h.min);c.each(this.data,function(a){var b={x:a.plotX,y:a.plotY,z:(a.z-h.min)*i},b=x([b],d,f,g)[0];a.plotXold=a.plotX;a.plotYold=a.plotY;a.plotX=b.x;a.plotY=b.y;a.plotZ=b.z})}});c.wrap(c.seriesTypes.scatter.prototype, +"init",function(c){var a=c.apply(this,[].slice.call(arguments,1));if(this.chart.is3d())this.pointArrayMap=["x","y","z"],this.tooltipOptions.pointFormat=this.userOptions.tooltip?this.userOptions.tooltip.pointFormat||"x: {point.x}
y: {point.y}
z: {point.z}
":"x: {point.x}
y: {point.y}
z: {point.z}
";return a});if(c.VMLRenderer)c.setOptions({animate:!1}),c.VMLRenderer.prototype.cuboid=c.SVGRenderer.prototype.cuboid,c.VMLRenderer.prototype.cuboidPath= +c.SVGRenderer.prototype.cuboidPath,c.VMLRenderer.prototype.toLinePath=c.SVGRenderer.prototype.toLinePath,c.VMLRenderer.prototype.createElement3D=c.SVGRenderer.prototype.createElement3D,c.VMLRenderer.prototype.arc3d=function(e){e=c.SVGRenderer.prototype.arc3d.call(this,e);e.css({zIndex:e.zIndex});return e},c.VMLRenderer.prototype.arc3dPath=c.SVGRenderer.prototype.arc3dPath,c.Chart.prototype.renderSeries=function(){for(var c,a=this.series.length;a--;)c=this.series[a],c.translate(),c.setTooltipPoints&& +c.setTooltipPoints(),c.render()},c.wrap(c.Axis.prototype,"render",function(c){c.apply(this,[].slice.call(arguments,1));this.sideFrame&&(this.sideFrame.css({zIndex:0}),this.sideFrame.front.attr({fill:this.sideFrame.color}));this.bottomFrame&&(this.bottomFrame.css({zIndex:1}),this.bottomFrame.front.attr({fill:this.bottomFrame.color}));this.backFrame&&(this.backFrame.css({zIndex:0}),this.backFrame.front.attr({fill:this.backFrame.color}))})})(Highcharts); +/* + Highcharts JS v4.0.1 (2014-04-24) + Exporting module + + (c) 2010-2014 Torstein Honsi + + License: www.highcharts.com/license +*/ +(function(f){var A=f.Chart,t=f.addEvent,B=f.removeEvent,l=f.createElement,o=f.discardElement,v=f.css,k=f.merge,r=f.each,p=f.extend,D=Math.max,j=document,C=window,E=f.isTouchDevice,F=f.Renderer.prototype.symbols,s=f.getOptions(),y;p(s.lang,{printChart:"Print chart",downloadPNG:"Download PNG image",downloadJPEG:"Download JPEG image",downloadPDF:"Download PDF document",downloadSVG:"Download SVG vector image",contextButtonTitle:"Chart context menu"});s.navigation={menuStyle:{border:"1px solid #A0A0A0", +background:"#FFFFFF",padding:"5px 0"},menuItemStyle:{padding:"0 10px",background:"none",color:"#303030",fontSize:E?"14px":"11px"},menuItemHoverStyle:{background:"#4572A5",color:"#FFFFFF"},buttonOptions:{symbolFill:"#E0E0E0",symbolSize:14,symbolStroke:"#666",symbolStrokeWidth:3,symbolX:12.5,symbolY:10.5,align:"right",buttonSpacing:3,height:22,theme:{fill:"white",stroke:"none"},verticalAlign:"top",width:24}};s.exporting={type:"image/png",url:"http://export.highcharts.com/",buttons:{contextButton:{menuClassName:"highcharts-contextmenu", +symbol:"menu",_titleKey:"contextButtonTitle",menuItems:[{textKey:"printChart",onclick:function(){this.print()}},{separator:!0},{textKey:"downloadPNG",onclick:function(){this.exportChart()}},{textKey:"downloadJPEG",onclick:function(){this.exportChart({type:"image/jpeg"})}},{textKey:"downloadPDF",onclick:function(){this.exportChart({type:"application/pdf"})}},{textKey:"downloadSVG",onclick:function(){this.exportChart({type:"image/svg+xml"})}}]}}};f.post=function(b,a,d){var c,b=l("form",k({method:"post", +action:b,enctype:"multipart/form-data"},d),{display:"none"},j.body);for(c in a)l("input",{type:"hidden",name:c,value:a[c]},null,b);b.submit();o(b)};p(A.prototype,{getSVG:function(b){var a=this,d,c,z,h,g=k(a.options,b);if(!j.createElementNS)j.createElementNS=function(a,b){return j.createElement(b)};b=l("div",null,{position:"absolute",top:"-9999em",width:a.chartWidth+"px",height:a.chartHeight+"px"},j.body);c=a.renderTo.style.width;h=a.renderTo.style.height;c=g.exporting.sourceWidth||g.chart.width|| +/px$/.test(c)&&parseInt(c,10)||600;h=g.exporting.sourceHeight||g.chart.height||/px$/.test(h)&&parseInt(h,10)||400;p(g.chart,{animation:!1,renderTo:b,forExport:!0,width:c,height:h});g.exporting.enabled=!1;g.series=[];r(a.series,function(a){z=k(a.options,{animation:!1,showCheckbox:!1,visible:a.visible});z.isInternal||g.series.push(z)});d=new f.Chart(g,a.callback);r(["xAxis","yAxis"],function(b){r(a[b],function(a,c){var g=d[b][c],f=a.getExtremes(),h=f.userMin,f=f.userMax;g&&(h!==void 0||f!==void 0)&& +g.setExtremes(h,f,!0,!1)})});c=d.container.innerHTML;g=null;d.destroy();o(b);c=c.replace(/zIndex="[^"]+"/g,"").replace(/isShadow="[^"]+"/g,"").replace(/symbolName="[^"]+"/g,"").replace(/jQuery[0-9]+="[^"]+"/g,"").replace(/url\([^#]+#/g,"url(#").replace(/.*?$/,"").replace(/ /g," ").replace(/­/g,"­").replace(//g,'xlink:href="$1"/>').replace(/id=([^" >]+)/g,'id="$1"').replace(/class=([^" >]+)/g,'class="$1"').replace(/ transform /g," ").replace(/:(path|rect)/g,"$1").replace(/style="([^"]+)"/g,function(a){return a.toLowerCase()});return c=c.replace(/(url\(#highcharts-[0-9]+)"/g,"$1").replace(/"/g,"'")},exportChart:function(b,a){var b=b||{},d=this.options.exporting,d=this.getSVG(k({chart:{borderRadius:0}},d.chartOptions,a,{exporting:{sourceWidth:b.sourceWidth|| +d.sourceWidth,sourceHeight:b.sourceHeight||d.sourceHeight}})),b=k(this.options.exporting,b);f.post(b.url,{filename:b.filename||"chart",type:b.type,width:b.width||0,scale:b.scale||2,svg:d},b.formAttributes)},print:function(){var b=this,a=b.container,d=[],c=a.parentNode,f=j.body,h=f.childNodes;if(!b.isPrinting)b.isPrinting=!0,r(h,function(a,b){if(a.nodeType===1)d[b]=a.style.display,a.style.display="none"}),f.appendChild(a),C.focus(),C.print(),setTimeout(function(){c.appendChild(a);r(h,function(a,b){if(a.nodeType=== +1)a.style.display=d[b]});b.isPrinting=!1},1E3)},contextMenu:function(b,a,d,c,f,h,g){var e=this,k=e.options.navigation,q=k.menuItemStyle,m=e.chartWidth,n=e.chartHeight,j="cache-"+b,i=e[j],u=D(f,h),w,x,o,s=function(a){e.pointer.inClass(a.target,b)||x()};if(!i)e[j]=i=l("div",{className:b},{position:"absolute",zIndex:1E3,padding:u+"px"},e.container),w=l("div",null,p({MozBoxShadow:"3px 3px 10px #888",WebkitBoxShadow:"3px 3px 10px #888",boxShadow:"3px 3px 10px #888"},k.menuStyle),i),x=function(){v(i,{display:"none"}); +g&&g.setState(0);e.openMenu=!1},t(i,"mouseleave",function(){o=setTimeout(x,500)}),t(i,"mouseenter",function(){clearTimeout(o)}),t(document,"mouseup",s),t(e,"destroy",function(){B(document,"mouseup",s)}),r(a,function(a){if(a){var b=a.separator?l("hr",null,null,w):l("div",{onmouseover:function(){v(this,k.menuItemHoverStyle)},onmouseout:function(){v(this,q)},onclick:function(){x();a.onclick.apply(e,arguments)},innerHTML:a.text||e.options.lang[a.textKey]},p({cursor:"pointer"},q),w);e.exportDivElements.push(b)}}), +e.exportDivElements.push(w,i),e.exportMenuWidth=i.offsetWidth,e.exportMenuHeight=i.offsetHeight;a={display:"block"};d+e.exportMenuWidth>m?a.right=m-d-f-u+"px":a.left=d-u+"px";c+h+e.exportMenuHeight>n&&g.alignOptions.verticalAlign!=="top"?a.bottom=n-c-u+"px":a.top=c+h-u+"px";v(i,a);e.openMenu=!0},addButton:function(b){var a=this,d=a.renderer,c=k(a.options.navigation.buttonOptions,b),j=c.onclick,h=c.menuItems,g,e,l={stroke:c.symbolStroke,fill:c.symbolFill},q=c.symbolSize||12;if(!a.btnCount)a.btnCount= +0;if(!a.exportDivElements)a.exportDivElements=[],a.exportSVGElements=[];if(c.enabled!==!1){var m=c.theme,n=m.states,o=n&&n.hover,n=n&&n.select,i;delete m.states;j?i=function(){j.apply(a,arguments)}:h&&(i=function(){a.contextMenu(e.menuClassName,h,e.translateX,e.translateY,e.width,e.height,e);e.setState(2)});c.text&&c.symbol?m.paddingLeft=f.pick(m.paddingLeft,25):c.text||p(m,{width:c.width,height:c.height,padding:0});e=d.button(c.text,0,0,i,m,o,n).attr({title:a.options.lang[c._titleKey],"stroke-linecap":"round"}); +e.menuClassName=b.menuClassName||"highcharts-menu-"+a.btnCount++;c.symbol&&(g=d.symbol(c.symbol,c.symbolX-q/2,c.symbolY-q/2,q,q).attr(p(l,{"stroke-width":c.symbolStrokeWidth||1,zIndex:1})).add(e));e.add().align(p(c,{width:e.width,x:f.pick(c.x,y)}),!0,"spacingBox");y+=(e.width+c.buttonSpacing)*(c.align==="right"?-1:1);a.exportSVGElements.push(e,g)}},destroyExport:function(b){var b=b.target,a,d;for(a=0;a=e&&c<=h&&!j&&k!==""&&(k=b.split(f),m(k,function(b,a){a>=i&&a<=g&&(d[a-i]||(d[a-i]=[]),d[a-i][o]=b)}),o+=1)}),this.dataFound())},parseTable:function(){var a=this.options,b=a.table,c=this.columns,d=a.startRow||0,e=a.endRow||Number.MAX_VALUE,h=a.startColumn||0,i=a.endColumn||Number.MAX_VALUE;b&&(typeof b==="string"&&(b=document.getElementById(b)),m(b.getElementsByTagName("tr"),function(a, +b){b>=d&&b<=e&&m(a.children,function(a,e){if((a.tagName==="TD"||a.tagName==="TH")&&e>=h&&e<=i)c[e-h]||(c[e-h]=[]),c[e-h][b-d]=a.innerHTML})}),this.dataFound())},parseGoogleSpreadsheet:function(){var a=this,b=this.options,c=b.googleSpreadsheetKey,d=this.columns,e=b.startRow||0,h=b.endRow||Number.MAX_VALUE,i=b.startColumn||0,g=b.endColumn||Number.MAX_VALUE,f,k;c&&jQuery.ajax({dataType:"json",url:"https://spreadsheets.google.com/feeds/cells/"+c+"/"+(b.googleSpreadsheetWorksheet||"od6")+"/public/values?alt=json-in-script&callback=?", +error:b.error,success:function(b){var b=b.feed.entry,c,j=b.length,m=0,n=0,l;for(l=0;l=i&&l<=g)d[l-i]=[],d[l-i].length=Math.min(n,h-e);for(l=0;l=i&&k<=g&&f>=e&&f<=h)d[k-i][f-e]=c.content.$t;a.dataFound()}})},findHeaderRow:function(){m(this.columns,function(){});this.headerRow=0},trim:function(a){return typeof a==="string"?a.replace(/^\s+|\s+$/g,""):a},parseTypes:function(){for(var a= +this.columns,b=a.length,c,d,e,h;b--;)for(c=a[b].length;c--;)d=a[b][c],e=parseFloat(d),h=this.trim(d),h==e?(a[b][c]=e,e>31536E6?a[b].isDatetime=!0:a[b].isNumeric=!0):(d=this.parseDate(d),b===0&&typeof d==="number"&&!isNaN(d)?(a[b][c]=d,a[b].isDatetime=!0):a[b][c]=h===""?null:h)},dateFormats:{"YYYY-mm-dd":{regex:"^([0-9]{4})-([0-9]{2})-([0-9]{2})$",parser:function(a){return Date.UTC(+a[1],a[2]-1,+a[3])}}},parseDate:function(a){var b=this.options.parseDate,c,d,e;b&&(c=b(a));if(typeof a==="string")for(d in this.dateFormats)b= +this.dateFormats[d],(e=a.match(b.regex))&&(c=b.parser(e));return c},rowsToColumns:function(a){var b,c,d,e,h;if(a){h=[];c=a.length;for(b=0;b1&&(b=a.shift(),this.headerRow===0&&b.shift(),b.isDatetime?c="datetime":b.isNumeric|| +(c="category"));for(g=0;g1&&i[f].push(a[g+1][f]!==void 0?a[g+1][f]:null),e>2&&i[f].push(a[g+2][f]!==void 0?a[g+2][f]:null),e>3&&i[f].push(a[g+3][f]!==void 0?a[g+3][f]:null),e>4&&i[f].push(a[g+4][f]!==void 0?a[g+4][f]:null);h[k]={name:a[g].name,data:i};g+= +e}d.complete({xAxis:{type:c},series:h})}}});j.Data=n;j.data=function(a,b){return new n(a,b)};j.wrap(j.Chart.prototype,"init",function(a,b,c){var d=this;b&&b.data?j.data(j.extend(b.data,{complete:function(e){b.hasOwnProperty("series")&&(typeof b.series==="object"?m(b.series,function(a,c){b.series[c]=j.merge(a,e.series[c])}):delete b.series);b=j.merge(e,b);a.call(d,b,c)}}),b):a.call(d,b,c)})})(Highcharts); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-more.js b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-more.js old mode 100644 new mode 100755 index 655f9002a2b..3cc51350fa8 --- a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-more.js +++ b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-more.js @@ -1,35 +1,51 @@ -/* - Highcharts JS v2.3.3 (2012-10-04) - - (c) 2009-2011 Torstein Hønsi - - License: www.highcharts.com/license -*/ -(function(h,u){function z(a,b,c){this.init.call(this,a,b,c)}function A(a,b,c){a.call(this,b,c);if(this.chart.polar)this.closeSegment=function(a){var b=this.xAxis.center;a.push("L",b[0],b[1])},this.closedStacks=!0}function B(a,b){var c=this.chart,d=this.options.animation,e=this.group,g=this.markerGroup,f=this.xAxis.center,j=c.plotLeft,n=c.plotTop;if(c.polar){if(c.renderer.isSVG)if(d===!0&&(d={}),b){if(e.attrSetters.scaleX=e.attrSetters.scaleY=function(a,b){this[b]=a;this.scaleX!==u&&this.scaleY!== -u&&this.element.setAttribute("transform","translate("+this.translateX+","+this.translateY+") scale("+this.scaleX+","+this.scaleY+")");return!1},c={translateX:f[0]+j,translateY:f[1]+n,scaleX:0,scaleY:0},e.attr(c),g)g.attrSetters=e.attrSetters,g.attr(c)}else c={translateX:j,translateY:n,scaleX:1,scaleY:1},e.animate(c,d),g&&g.animate(c,d),this.animate=null}else a.call(this,b)}var p=h.each,v=h.extend,o=h.merge,D=h.map,m=h.pick,w=h.pInt,k=h.getOptions().plotOptions,i=h.seriesTypes,E=h.extendClass,l=h.wrap, -r=h.Axis,G=h.Tick,y=h.Series,q=i.column.prototype,s=function(){};v(z.prototype,{init:function(a,b,c){var d=this,e=d.defaultOptions;d.chart=b;if(b.angular)e.background={};d.options=a=o(e,a);(a=a.background)&&p([].concat(h.splat(a)).reverse(),function(a){var b=a.backgroundColor,a=o(d.defaultBackgroundOptions,a);if(b)a.backgroundColor=b;a.color=a.backgroundColor;c.options.plotBands.unshift(a)})},defaultOptions:{center:["50%","50%"],size:"85%",startAngle:0},defaultBackgroundOptions:{shape:"circle",borderWidth:1, -borderColor:"silver",backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,"#FFF"],[1,"#DDD"]]},from:Number.MIN_VALUE,innerRadius:0,to:Number.MAX_VALUE,outerRadius:"105%"}});var x=r.prototype,r=G.prototype,H={getOffset:s,redraw:function(){this.isDirty=!1},render:function(){this.isDirty=!1},setScale:s,setCategories:s,setTitle:s},F={isRadial:!0,defaultRadialGaugeOptions:{labels:{align:"center",x:0,y:null},minorGridLineWidth:0,minorTickInterval:"auto",minorTickLength:10,minorTickPosition:"inside", -minorTickWidth:1,plotBands:[],tickLength:10,tickPosition:"inside",tickWidth:2,title:{rotation:0},zIndex:2},defaultRadialXOptions:{gridLineWidth:1,labels:{align:null,distance:15,x:0,y:null},maxPadding:0,minPadding:0,plotBands:[],showLastLabel:!1,tickLength:0},defaultRadialYOptions:{gridLineInterpolation:"circle",labels:{align:"right",x:-3,y:-2},plotBands:[],showLastLabel:!1,title:{x:4,text:null,rotation:90}},setOptions:function(a){this.options=o(this.defaultOptions,this.defaultRadialOptions,a)},getOffset:function(){x.getOffset.call(this); -this.chart.axisOffset[this.side]=0;this.center=this.pane.center=i.pie.prototype.getCenter.call(this.pane)},getLinePath:function(a,b){var c=this.center,b=m(b,c[2]/2-this.offset);return this.chart.renderer.symbols.arc(this.left+c[0],this.top+c[1],b,b,{start:this.startAngleRad,end:this.endAngleRad,open:!0,innerR:0})},setAxisTranslation:function(){x.setAxisTranslation.call(this);if(this.center&&(this.transA=this.isCircular?(this.endAngleRad-this.startAngleRad)/(this.max-this.min||1):this.center[2]/2/ -(this.max-this.min||1),this.isXAxis))this.minPixelPadding=this.transA*this.minPointOffset+(this.reversed?(this.endAngleRad-this.startAngleRad)/4:0)},beforeSetTickPositions:function(){this.autoConnect&&(this.max+=this.categories&&1||this.pointRange||this.closestPointRange)},setAxisSize:function(){x.setAxisSize.call(this);if(this.center)this.len=this.width=this.height=this.isCircular?this.center[2]*(this.endAngleRad-this.startAngleRad)/2:this.center[2]/2},getPosition:function(a,b){if(!this.isCircular)b= -this.translate(a),a=this.min;return this.postTranslate(this.translate(a),m(b,this.center[2]/2)-this.offset)},postTranslate:function(a,b){var c=this.chart,d=this.center,a=this.startAngleRad+a;return{x:c.plotLeft+d[0]+Math.cos(a)*b,y:c.plotTop+d[1]+Math.sin(a)*b}},getPlotBandPath:function(a,b,c){var d=this.center,e=this.startAngleRad,g=d[2]/2,f=[m(c.outerRadius,"100%"),c.innerRadius,m(c.thickness,10)],j=/%$/,n,C=this.isCircular;this.options.gridLineInterpolation==="polygon"?d=this.getPlotLinePath(a).concat(this.getPlotLinePath(b, -!0)):(C||(f[0]=this.translate(a),f[1]=this.translate(b)),f=D(f,function(a){j.test(a)&&(a=w(a,10)*g/100);return a}),c.shape==="circle"||!C?(a=-Math.PI/2,b=Math.PI*1.5,n=!0):(a=e+this.translate(a),b=e+this.translate(b)),d=this.chart.renderer.symbols.arc(this.left+d[0],this.top+d[1],f[0],f[0],{start:a,end:b,innerR:m(f[1],f[0]-f[2]),open:n}));return d},getPlotLinePath:function(a,b){var c=this.center,d=this.chart,e=this.getPosition(a),g,f,j;this.isCircular?j=["M",c[0]+d.plotLeft,c[1]+d.plotTop,"L",e.x, -e.y]:this.options.gridLineInterpolation==="circle"?(a=this.translate(a))&&(j=this.getLinePath(0,a)):(g=d.xAxis[0],j=[],a=this.translate(a),c=g.tickPositions,g.autoConnect&&(c=c.concat([c[0]])),b&&(c=[].concat(c).reverse()),p(c,function(b,c){f=g.getPosition(b,a);j.push(c?"L":"M",f.x,f.y)}));return j},getTitlePosition:function(){var a=this.center,b=this.chart,c=this.options.title;return{x:b.plotLeft+a[0]+(c.x||0),y:b.plotTop+a[1]-{high:0.5,middle:0.25,low:0}[c.align]*a[2]+(c.y||0)}}};l(x,"init",function(a, -b,c){var d=this,e=b.angular,g=b.polar,f=c.isX,j=e&&f,n;if(e){if(v(this,j?H:F),n=!f)this.defaultRadialOptions=this.defaultRadialGaugeOptions}else if(g)v(this,F),this.defaultRadialOptions=(n=f)?this.defaultRadialXOptions:o(this.defaultYAxisOptions,this.defaultRadialYOptions);a.call(this,b,c);if(!j&&(e||g)){a=this.options;if(!b.panes)b.panes=D(h.splat(b.options.pane),function(a){return new z(a,b,d)});this.pane=e=b.panes[c.pane||0];g=e.options;b.inverted=!1;b.options.chart.zoomType=null;this.startAngleRad= -e=(g.startAngle-90)*Math.PI/180;this.endAngleRad=g=(m(g.endAngle,g.startAngle+360)-90)*Math.PI/180;this.offset=a.offset||0;if((this.isCircular=n)&&c.max===u&&g-e===2*Math.PI)this.autoConnect=!0}});l(r,"getPosition",function(a,b,c,d,e){var g=this.axis;return g.getPosition?g.getPosition(c):a.call(this,b,c,d,e)});l(r,"getLabelPosition",function(a,b,c,d,e,g,f,j,n){var h=this.axis,i=g.y,l=g.align,k=(h.translate(this.pos)+h.startAngleRad+Math.PI/2)/Math.PI*180;h.isRadial?(a=h.getPosition(this.pos,h.center[2]/ -2+m(g.distance,-25)),g.rotation==="auto"?d.attr({rotation:k}):i===null&&(i=w(d.styles.lineHeight)*0.9-d.getBBox().height/2),l===null&&(l=h.isCircular?k>20&&k<160?"left":k>200&&k<340?"right":"center":"center",d.attr({align:l})),a.x+=g.x,a.y+=i):a=a.call(this,b,c,d,e,g,f,j,n);return a});l(r,"getMarkPath",function(a,b,c,d,e,g,f){var j=this.axis;j.isRadial?(a=j.getPosition(this.pos,j.center[2]/2+d),b=["M",b,c,"L",a.x,a.y]):b=a.call(this,b,c,d,e,g,f);return b});k.arearange=o(k.area,{lineWidth:1,marker:null, -threshold:null,tooltip:{pointFormat:'{series.name}: {point.low} - {point.high}
'},trackByArea:!0,dataLabels:{verticalAlign:null,xLow:0,xHigh:0,yLow:0,yHigh:0},shadow:!1});r=h.extendClass(h.Point,{applyOptions:function(a,b){var c=this.series,d=c.pointArrayMap,e=0,g=0,f=d.length;if(typeof a==="object"&&typeof a.length!=="number")v(this,a),this.options=a;else if(a.length){if(a.length>f){if(typeof a[0]==="string")this.name=a[0];else if(typeof a[0]=== -"number")this.x=a[0];e++}for(;gMath.PI/2&&(k-=Math.PI),j=e+Math.cos(k)*b,h=g+Math.sin(k)*b,f=e+Math.cos(Math.PI+k)*i,k=g+Math.sin(Math.PI+k)*i,c.rightContX=f,c.rightContY=k;d?(c=["C",a.rightContX||a.plotX,a.rightContY||a.plotY,j||e,h||g,e,g],a.rightContX=a.rightContY=null):c=["M",e,g]}else c=a.call(this,b,c,d);return c});l(t,"translate",function(a){a.call(this);if(this.chart.polar&&!this.preventPostTranslate)for(var a=this.points,b=a.length;b--;)this.toXY(a[b])});l(t,"getSegmentPath",function(a,b){var c= -this.points;if(this.chart.polar&&this.options.connectEnds!==!1&&b[b.length-1]===c[c.length-1]&&c[0].y!==null)this.connectEnds=!0,b=[].concat(b,[c[0]]);return a.call(this,b)});l(t,"animate",B);l(q,"animate",B);l(t,"setTooltipPoints",function(a,b){this.chart.polar&&v(this.xAxis,{tooltipLen:360,tooltipPosName:"deg"});return a.call(this,b)});l(q,"translate",function(a){var b=this.xAxis,c=this.yAxis.len,d=b.center,e=b.startAngleRad,g=this.chart.renderer,f,h;this.preventPostTranslate=!0;a.call(this);if(b.isRadial){b= -this.points;for(h=b.length;h--;)f=b[h],a=f.barX+e,f.shapeType="path",f.shapeArgs={d:g.symbols.arc(d[0],d[1],c-f.plotY,null,{start:a,end:a+f.pointWidth,innerR:c-m(f.yBottom,c)})},this.toXY(f)}});l(q,"alignDataLabel",function(a,b,c,d,e,g){if(this.chart.polar){a=b.rectPlotX/Math.PI*180;if(d.align===null)d.align=a>20&&a<160?"left":a>200&&a<340?"right":"center";if(d.verticalAlign===null)d.verticalAlign=a<45||a>315?"bottom":a>135&&a<225?"top":"middle";t.alignDataLabel.call(this,b,c,d,e,g)}else a.call(this, -b,c,d,e,g)});l(k,"getIndex",function(a,b){var c,d=this.chart,e;d.polar?(e=d.xAxis[0].center,c=b.chartX-e[0]-d.plotLeft,d=b.chartY-e[1]-d.plotTop,c=180-Math.round(Math.atan2(c,d)/Math.PI*180)):c=a.call(this,b);return c});l(k,"getMouseCoordinates",function(a,b){var c=this.chart,d={xAxis:[],yAxis:[]};c.polar?p(c.axes,function(a){var g=a.isXAxis,f=a.center,h=b.chartX-f[0]-c.plotLeft,f=b.chartY-f[1]-c.plotTop;d[g?"xAxis":"yAxis"].push({axis:a,value:a.translate(g?Math.PI-Math.atan2(h,f):Math.sqrt(Math.pow(h, -2)+Math.pow(f,2)),!0)})}):d=a.call(this,b);return d})})(Highcharts); +/* + Highcharts JS v4.0.1 (2014-04-24) + + (c) 2009-2014 Torstein Honsi + + License: www.highcharts.com/license +*/ +(function(m,C){function K(a,b,c){this.init.call(this,a,b,c)}var O=m.arrayMin,P=m.arrayMax,s=m.each,F=m.extend,o=m.merge,Q=m.map,q=m.pick,x=m.pInt,p=m.getOptions().plotOptions,h=m.seriesTypes,u=m.extendClass,L=m.splat,r=m.wrap,M=m.Axis,y=m.Tick,H=m.Point,R=m.Pointer,S=m.CenteredSeriesMixin,z=m.TrackerMixin,t=m.Series,v=Math,D=v.round,A=v.floor,T=v.max,U=m.Color,w=function(){};F(K.prototype,{init:function(a,b,c){var d=this,e=d.defaultOptions;d.chart=b;if(b.angular)e.background={};d.options=a=o(e,a); +(a=a.background)&&s([].concat(L(a)).reverse(),function(a){var g=a.backgroundColor,a=o(d.defaultBackgroundOptions,a);if(g)a.backgroundColor=g;a.color=a.backgroundColor;c.options.plotBands.unshift(a)})},defaultOptions:{center:["50%","50%"],size:"85%",startAngle:0},defaultBackgroundOptions:{shape:"circle",borderWidth:1,borderColor:"silver",backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,"#FFF"],[1,"#DDD"]]},from:Number.MIN_VALUE,innerRadius:0,to:Number.MAX_VALUE,outerRadius:"105%"}}); +var G=M.prototype,y=y.prototype,V={getOffset:w,redraw:function(){this.isDirty=!1},render:function(){this.isDirty=!1},setScale:w,setCategories:w,setTitle:w},N={isRadial:!0,defaultRadialGaugeOptions:{labels:{align:"center",x:0,y:null},minorGridLineWidth:0,minorTickInterval:"auto",minorTickLength:10,minorTickPosition:"inside",minorTickWidth:1,tickLength:10,tickPosition:"inside",tickWidth:2,title:{rotation:0},zIndex:2},defaultRadialXOptions:{gridLineWidth:1,labels:{align:null,distance:15,x:0,y:null}, +maxPadding:0,minPadding:0,showLastLabel:!1,tickLength:0},defaultRadialYOptions:{gridLineInterpolation:"circle",labels:{align:"right",x:-3,y:-2},showLastLabel:!1,title:{x:4,text:null,rotation:90}},setOptions:function(a){a=this.options=o(this.defaultOptions,this.defaultRadialOptions,a);if(!a.plotBands)a.plotBands=[]},getOffset:function(){G.getOffset.call(this);this.chart.axisOffset[this.side]=0;this.center=this.pane.center=S.getCenter.call(this.pane)},getLinePath:function(a,b){var c=this.center,b=q(b, +c[2]/2-this.offset);return this.chart.renderer.symbols.arc(this.left+c[0],this.top+c[1],b,b,{start:this.startAngleRad,end:this.endAngleRad,open:!0,innerR:0})},setAxisTranslation:function(){G.setAxisTranslation.call(this);if(this.center)this.transA=this.isCircular?(this.endAngleRad-this.startAngleRad)/(this.max-this.min||1):this.center[2]/2/(this.max-this.min||1),this.minPixelPadding=this.isXAxis?this.transA*this.minPointOffset:0},beforeSetTickPositions:function(){this.autoConnect&&(this.max+=this.categories&& +1||this.pointRange||this.closestPointRange||0)},setAxisSize:function(){G.setAxisSize.call(this);if(this.isRadial){this.center=this.pane.center=m.CenteredSeriesMixin.getCenter.call(this.pane);if(this.isCircular)this.sector=this.endAngleRad-this.startAngleRad;this.len=this.width=this.height=this.center[2]*q(this.sector,1)/2}},getPosition:function(a,b){return this.postTranslate(this.isCircular?this.translate(a):0,q(this.isCircular?b:this.translate(a),this.center[2]/2)-this.offset)},postTranslate:function(a, +b){var c=this.chart,d=this.center,a=this.startAngleRad+a;return{x:c.plotLeft+d[0]+Math.cos(a)*b,y:c.plotTop+d[1]+Math.sin(a)*b}},getPlotBandPath:function(a,b,c){var d=this.center,e=this.startAngleRad,f=d[2]/2,g=[q(c.outerRadius,"100%"),c.innerRadius,q(c.thickness,10)],k=/%$/,l,n=this.isCircular;this.options.gridLineInterpolation==="polygon"?d=this.getPlotLinePath(a).concat(this.getPlotLinePath(b,!0)):(n||(g[0]=this.translate(a),g[1]=this.translate(b)),g=Q(g,function(a){k.test(a)&&(a=x(a,10)*f/100); +return a}),c.shape==="circle"||!n?(a=-Math.PI/2,b=Math.PI*1.5,l=!0):(a=e+this.translate(a),b=e+this.translate(b)),d=this.chart.renderer.symbols.arc(this.left+d[0],this.top+d[1],g[0],g[0],{start:a,end:b,innerR:q(g[1],g[0]-g[2]),open:l}));return d},getPlotLinePath:function(a,b){var c=this,d=c.center,e=c.chart,f=c.getPosition(a),g,k,l;c.isCircular?l=["M",d[0]+e.plotLeft,d[1]+e.plotTop,"L",f.x,f.y]:c.options.gridLineInterpolation==="circle"?(a=c.translate(a))&&(l=c.getLinePath(0,a)):(s(e.xAxis,function(a){a.pane=== +c.pane&&(g=a)}),l=[],a=c.translate(a),d=g.tickPositions,g.autoConnect&&(d=d.concat([d[0]])),b&&(d=[].concat(d).reverse()),s(d,function(f,c){k=g.getPosition(f,a);l.push(c?"L":"M",k.x,k.y)}));return l},getTitlePosition:function(){var a=this.center,b=this.chart,c=this.options.title;return{x:b.plotLeft+a[0]+(c.x||0),y:b.plotTop+a[1]-{high:0.5,middle:0.25,low:0}[c.align]*a[2]+(c.y||0)}}};r(G,"init",function(a,b,c){var i;var d=b.angular,e=b.polar,f=c.isX,g=d&&f,k,l;l=b.options;var n=c.pane||0;if(d){if(F(this, +g?V:N),k=!f)this.defaultRadialOptions=this.defaultRadialGaugeOptions}else if(e)F(this,N),this.defaultRadialOptions=(k=f)?this.defaultRadialXOptions:o(this.defaultYAxisOptions,this.defaultRadialYOptions);a.call(this,b,c);if(!g&&(d||e)){a=this.options;if(!b.panes)b.panes=[];this.pane=(i=b.panes[n]=b.panes[n]||new K(L(l.pane)[n],b,this),n=i);n=n.options;b.inverted=!1;l.chart.zoomType=null;this.startAngleRad=b=(n.startAngle-90)*Math.PI/180;this.endAngleRad=l=(q(n.endAngle,n.startAngle+360)-90)*Math.PI/ +180;this.offset=a.offset||0;if((this.isCircular=k)&&c.max===C&&l-b===2*Math.PI)this.autoConnect=!0}});r(y,"getPosition",function(a,b,c,d,e){var f=this.axis;return f.getPosition?f.getPosition(c):a.call(this,b,c,d,e)});r(y,"getLabelPosition",function(a,b,c,d,e,f,g,k,l){var n=this.axis,j=f.y,i=f.align,h=(n.translate(this.pos)+n.startAngleRad+Math.PI/2)/Math.PI*180%360;n.isRadial?(a=n.getPosition(this.pos,n.center[2]/2+q(f.distance,-25)),f.rotation==="auto"?d.attr({rotation:h}):j===null&&(j=n.chart.renderer.fontMetrics(d.styles.fontSize).b- +d.getBBox().height/2),i===null&&(i=n.isCircular?h>20&&h<160?"left":h>200&&h<340?"right":"center":"center",d.attr({align:i})),a.x+=f.x,a.y+=j):a=a.call(this,b,c,d,e,f,g,k,l);return a});r(y,"getMarkPath",function(a,b,c,d,e,f,g){var k=this.axis;k.isRadial?(a=k.getPosition(this.pos,k.center[2]/2+d),b=["M",b,c,"L",a.x,a.y]):b=a.call(this,b,c,d,e,f,g);return b});p.arearange=o(p.area,{lineWidth:1,marker:null,threshold:null,tooltip:{pointFormat:'● {series.name}: {point.low} - {point.high}
'}, +trackByArea:!0,dataLabels:{verticalAlign:null,xLow:0,xHigh:0,yLow:0,yHigh:0},states:{hover:{halo:!1}}});h.arearange=u(h.area,{type:"arearange",pointArrayMap:["low","high"],toYData:function(a){return[a.low,a.high]},pointValKey:"low",getSegments:function(){var a=this;s(a.points,function(b){if(!a.options.connectNulls&&(b.low===null||b.high===null))b.y=null;else if(b.low===null&&b.high!==null)b.y=b.high});t.prototype.getSegments.call(this)},translate:function(){var a=this.yAxis;h.area.prototype.translate.apply(this); +s(this.points,function(b){var c=b.low,d=b.high,e=b.plotY;d===null&&c===null?b.y=null:c===null?(b.plotLow=b.plotY=null,b.plotHigh=a.translate(d,0,1,0,1)):d===null?(b.plotLow=e,b.plotHigh=null):(b.plotLow=e,b.plotHigh=a.translate(d,0,1,0,1))})},getSegmentPath:function(a){var b,c=[],d=a.length,e=t.prototype.getSegmentPath,f,g;g=this.options;var k=g.step;for(b=HighchartsAdapter.grep(a,function(a){return a.plotLow!==null});d--;)f=a[d],f.plotHigh!==null&&c.push({plotX:f.plotX,plotY:f.plotHigh});a=e.call(this, +b);if(k)k===!0&&(k="left"),g.step={left:"right",center:"center",right:"left"}[k];c=e.call(this,c);g.step=k;g=[].concat(a,c);c[0]="L";this.areaPath=this.areaPath.concat(a,c);return g},drawDataLabels:function(){var a=this.data,b=a.length,c,d=[],e=t.prototype,f=this.options.dataLabels,g,k=this.chart.inverted;if(f.enabled||this._hasPointLabels){for(c=b;c--;)g=a[c],g.y=g.high,g._plotY=g.plotY,g.plotY=g.plotHigh,d[c]=g.dataLabel,g.dataLabel=g.dataLabelUpper,g.below=!1,k?(f.align="left",f.x=f.xHigh):f.y= +f.yHigh;e.drawDataLabels&&e.drawDataLabels.apply(this,arguments);for(c=b;c--;)g=a[c],g.dataLabelUpper=g.dataLabel,g.dataLabel=d[c],g.y=g.low,g.plotY=g._plotY,g.below=!0,k?(f.align="right",f.x=f.xLow):f.y=f.yLow;e.drawDataLabels&&e.drawDataLabels.apply(this,arguments)}},alignDataLabel:function(){h.column.prototype.alignDataLabel.apply(this,arguments)},getSymbol:h.column.prototype.getSymbol,drawPoints:w});p.areasplinerange=o(p.arearange);h.areasplinerange=u(h.arearange,{type:"areasplinerange",getPointSpline:h.spline.prototype.getPointSpline}); +(function(){var a=h.column.prototype;p.columnrange=o(p.column,p.arearange,{lineWidth:1,pointRange:null});h.columnrange=u(h.arearange,{type:"columnrange",translate:function(){var b=this,c=b.yAxis,d;a.translate.apply(b);s(b.points,function(a){var f=a.shapeArgs,g=b.options.minPointLength,k;a.tooltipPos=null;a.plotHigh=d=c.translate(a.high,0,1,0,1);a.plotLow=a.plotY;k=d;a=a.plotY-d;a● {series.name}
Maximum: {point.high}
Upper quartile: {point.q3}
Median: {point.median}
Lower quartile: {point.q1}
Minimum: {point.low}
'}, +whiskerLength:"50%",whiskerWidth:2});h.boxplot=u(h.column,{type:"boxplot",pointArrayMap:["low","q1","median","q3","high"],toYData:function(a){return[a.low,a.q1,a.median,a.q3,a.high]},pointValKey:"high",pointAttrToOptions:{fill:"fillColor",stroke:"color","stroke-width":"lineWidth"},drawDataLabels:w,translate:function(){var a=this.yAxis,b=this.pointArrayMap;h.column.prototype.translate.apply(this);s(this.points,function(c){s(b,function(b){c[b]!==null&&(c[b+"Plot"]=a.translate(c[b],0,1,0,1))})})},drawPoints:function(){var a= +this,b=a.points,c=a.options,d=a.chart.renderer,e,f,g,k,l,n,j,i,h,m,p,I,r,o,J,u,w,t,v,x,z,y,E=a.doQuartiles!==!1,B=parseInt(a.options.whiskerLength,10)/100;s(b,function(b){h=b.graphic;z=b.shapeArgs;p={};o={};u={};y=b.color||a.color;if(b.plotY!==C)if(e=b.pointAttr[b.selected?"selected":""],w=z.width,t=A(z.x),v=t+w,x=D(w/2),f=A(E?b.q1Plot:b.lowPlot),g=A(E?b.q3Plot:b.lowPlot),k=A(b.highPlot),l=A(b.lowPlot),p.stroke=b.stemColor||c.stemColor||y,p["stroke-width"]=q(b.stemWidth,c.stemWidth,c.lineWidth),p.dashstyle= +b.stemDashStyle||c.stemDashStyle,o.stroke=b.whiskerColor||c.whiskerColor||y,o["stroke-width"]=q(b.whiskerWidth,c.whiskerWidth,c.lineWidth),u.stroke=b.medianColor||c.medianColor||y,u["stroke-width"]=q(b.medianWidth,c.medianWidth,c.lineWidth),u["stroke-linecap"]="round",j=p["stroke-width"]%2/2,i=t+x+j,m=["M",i,g,"L",i,k,"M",i,f,"L",i,l],E&&(j=e["stroke-width"]%2/2,i=A(i)+j,f=A(f)+j,g=A(g)+j,t+=j,v+=j,I=["M",t,g,"L",t,f,"L",v,f,"L",v,g,"L",t,g,"z"]),B&&(j=o["stroke-width"]%2/2,k+=j,l+=j,r=["M",i-x*B, +k,"L",i+x*B,k,"M",i-x*B,l,"L",i+x*B,l]),j=u["stroke-width"]%2/2,n=D(b.medianPlot)+j,J=["M",t,n,"L",v,n],h)b.stem.animate({d:m}),B&&b.whiskers.animate({d:r}),E&&b.box.animate({d:I}),b.medianShape.animate({d:J});else{b.graphic=h=d.g().add(a.group);b.stem=d.path(m).attr(p).add(h);if(B)b.whiskers=d.path(r).attr(o).add(h);if(E)b.box=d.path(I).attr(e).add(h);b.medianShape=d.path(J).attr(u).add(h)}})}});p.errorbar=o(p.boxplot,{color:"#000000",grouping:!1,linkedTo:":previous",tooltip:{pointFormat:'● {series.name}: {point.low} - {point.high}
'}, +whiskerWidth:null});h.errorbar=u(h.boxplot,{type:"errorbar",pointArrayMap:["low","high"],toYData:function(a){return[a.low,a.high]},pointValKey:"high",doQuartiles:!1,drawDataLabels:h.arearange?h.arearange.prototype.drawDataLabels:w,getColumnMetrics:function(){return this.linkedParent&&this.linkedParent.columnMetrics||h.column.prototype.getColumnMetrics.call(this)}});p.waterfall=o(p.column,{lineWidth:1,lineColor:"#333",dashStyle:"dot",borderColor:"#333"});h.waterfall=u(h.column,{type:"waterfall",upColorProp:"fill", +pointArrayMap:["low","y"],pointValKey:"y",init:function(a,b){b.stacking=!0;h.column.prototype.init.call(this,a,b)},translate:function(){var a=this.yAxis,b,c,d,e,f,g,k,l,n;b=this.options.threshold;h.column.prototype.translate.apply(this);l=b;d=this.points;for(c=0,b=d.length;c0&&!a.color)a.pointAttr=d,a.color=c})},getGraphPath:function(){var a=this.data,b=a.length, +c=D(this.options.lineWidth+this.borderWidth)%2/2,d=[],e,f,g;for(g=1;g0?(k[f]-a)/(b- +a):0.5,n&&g>=0&&(g=Math.sqrt(g)),l.push(v.ceil(c+g*(d-c))/2);this.radii=l},animate:function(a){var b=this.options.animation;if(!a)s(this.points,function(a){var d=a.graphic,a=a.shapeArgs;d&&a&&(d.attr("r",1),d.animate({r:a.r},b))}),this.animate=null},translate:function(){var a,b=this.data,c,d,e=this.radii;h.scatter.prototype.translate.call(this);for(a=b.length;a--;)c=b[a],d=e?e[a]:0,c.negative=c.z<(this.options.zThreshold||0),d>=this.minPxSize/2?(c.shapeType="circle",c.shapeArgs={x:c.plotX,y:c.plotY, +r:d},c.dlBox={x:c.plotX-d,y:c.plotY-d,width:2*d,height:2*d}):c.shapeArgs=c.plotY=c.dlBox=C},drawLegendSymbol:function(a,b){var c=x(a.itemStyle.fontSize)/2;b.legendSymbol=this.chart.renderer.circle(c,a.baseline-c,c).attr({zIndex:3}).add(b.legendGroup);b.legendSymbol.isMarker=!0},drawPoints:h.column.prototype.drawPoints,alignDataLabel:h.column.prototype.alignDataLabel});M.prototype.beforePadding=function(){var a=this,b=this.len,c=this.chart,d=0,e=b,f=this.isXAxis,g=f?"xData":"yData",k=this.min,l={}, +n=v.min(c.plotWidth,c.plotHeight),j=Number.MAX_VALUE,i=-Number.MAX_VALUE,h=this.max-k,m=b/h,p=[];this.tickPositions&&(s(this.series,function(b){var g=b.options;if(b.bubblePadding&&(b.visible||!c.options.chart.ignoreHiddenSeries))if(a.allowZoomOutside=!0,p.push(b),f)s(["minSize","maxSize"],function(a){var b=g[a],f=/%$/.test(b),b=x(b);l[a]=f?n*b/100:b}),b.minPxSize=l.minSize,b=b.zData,b.length&&(j=v.min(j,v.max(O(b),g.displayNegative===!1?g.zThreshold:-Number.MAX_VALUE)),i=v.max(i,P(b)))}),s(p,function(a){var b= +a[g],c=b.length,n;f&&a.getRadii(j,i,l.minSize,l.maxSize);if(h>0)for(;c--;)typeof b[c]==="number"&&(n=a.radii[c],d=Math.min((b[c]-k)*m-n,d),e=Math.max((b[c]-k)*m+n,e))}),p.length&&h>0&&q(this.options.min,this.userMin)===C&&q(this.options.max,this.userMax)===C&&(e-=b,m*=(b+d-e)/b,this.min+=d/m,this.max+=e/m))};(function(){function a(a,b,c){a.call(this,b,c);if(this.chart.polar)this.closeSegment=function(a){var b=this.xAxis.center;a.push("L",b[0],b[1])},this.closedStacks=!0}function b(a,b){var c=this.chart, +d=this.options.animation,e=this.group,j=this.markerGroup,i=this.xAxis.center,h=c.plotLeft,m=c.plotTop;if(c.polar){if(c.renderer.isSVG)d===!0&&(d={}),b?(c={translateX:i[0]+h,translateY:i[1]+m,scaleX:0.001,scaleY:0.001},e.attr(c),j&&j.attr(c)):(c={translateX:h,translateY:m,scaleX:1,scaleY:1},e.animate(c,d),j&&j.animate(c,d),this.animate=null)}else a.call(this,b)}var c=t.prototype,d=R.prototype,e;c.toXY=function(a){var b,c=this.chart,d=a.plotX;b=a.plotY;a.rectPlotX=d;a.rectPlotY=b;d=(d/Math.PI*180+this.xAxis.pane.options.startAngle)% +360;d<0&&(d+=360);a.clientX=d;b=this.xAxis.postTranslate(a.plotX,this.yAxis.len-b);a.plotX=a.polarPlotX=b.x-c.plotLeft;a.plotY=a.polarPlotY=b.y-c.plotTop};c.orderTooltipPoints=function(a){if(this.chart.polar&&(a.sort(function(a,b){return a.clientX-b.clientX}),a[0]))a[0].wrappedClientX=a[0].clientX+360,a.push(a[0])};h.area&&r(h.area.prototype,"init",a);h.areaspline&&r(h.areaspline.prototype,"init",a);h.spline&&r(h.spline.prototype,"getPointSpline",function(a,b,c,d){var e,j,i,h,m,p,o;if(this.chart.polar){e= +c.plotX;j=c.plotY;a=b[d-1];i=b[d+1];this.connectEnds&&(a||(a=b[b.length-2]),i||(i=b[1]));if(a&&i)h=a.plotX,m=a.plotY,b=i.plotX,p=i.plotY,h=(1.5*e+h)/2.5,m=(1.5*j+m)/2.5,i=(1.5*e+b)/2.5,o=(1.5*j+p)/2.5,b=Math.sqrt(Math.pow(h-e,2)+Math.pow(m-j,2)),p=Math.sqrt(Math.pow(i-e,2)+Math.pow(o-j,2)),h=Math.atan2(m-j,h-e),m=Math.atan2(o-j,i-e),o=Math.PI/2+(h+m)/2,Math.abs(h-o)>Math.PI/2&&(o-=Math.PI),h=e+Math.cos(o)*b,m=j+Math.sin(o)*b,i=e+Math.cos(Math.PI+o)*p,o=j+Math.sin(Math.PI+o)*p,c.rightContX=i,c.rightContY= +o;d?(c=["C",a.rightContX||a.plotX,a.rightContY||a.plotY,h||e,m||j,e,j],a.rightContX=a.rightContY=null):c=["M",e,j]}else c=a.call(this,b,c,d);return c});r(c,"translate",function(a){a.call(this);if(this.chart.polar&&!this.preventPostTranslate)for(var a=this.points,b=a.length;b--;)this.toXY(a[b])});r(c,"getSegmentPath",function(a,b){var c=this.points;if(this.chart.polar&&this.options.connectEnds!==!1&&b[b.length-1]===c[c.length-1]&&c[0].y!==null)this.connectEnds=!0,b=[].concat(b,[c[0]]);return a.call(this, +b)});r(c,"animate",b);r(c,"setTooltipPoints",function(a,b){this.chart.polar&&F(this.xAxis,{tooltipLen:360});return a.call(this,b)});if(h.column)e=h.column.prototype,r(e,"animate",b),r(e,"translate",function(a){var b=this.xAxis,c=this.yAxis.len,d=b.center,e=b.startAngleRad,h=this.chart.renderer,i,m;this.preventPostTranslate=!0;a.call(this);if(b.isRadial){b=this.points;for(m=b.length;m--;)i=b[m],a=i.barX+e,i.shapeType="path",i.shapeArgs={d:h.symbols.arc(d[0],d[1],c-i.plotY,null,{start:a,end:a+i.pointWidth, +innerR:c-q(i.yBottom,c)})},this.toXY(i),i.tooltipPos=[i.plotX,i.plotY],i.ttBelow=i.plotY>d[1]}}),r(e,"alignDataLabel",function(a,b,d,e,h,j){if(this.chart.polar){a=b.rectPlotX/Math.PI*180;if(e.align===null)e.align=a>20&&a<160?"left":a>200&&a<340?"right":"center";if(e.verticalAlign===null)e.verticalAlign=a<45||a>315?"bottom":a>135&&a<225?"top":"middle";c.alignDataLabel.call(this,b,d,e,h,j)}else a.call(this,b,d,e,h,j)});r(d,"getIndex",function(a,b){var c,d=this.chart,e;d.polar?(e=d.xAxis[0].center,c= +b.chartX-e[0]-d.plotLeft,d=b.chartY-e[1]-d.plotTop,c=180-Math.round(Math.atan2(c,d)/Math.PI*180)):c=a.call(this,b);return c});r(d,"getCoordinates",function(a,b){var c=this.chart,d={xAxis:[],yAxis:[]};c.polar?s(c.axes,function(a){var e=a.isXAxis,f=a.center,h=b.chartX-f[0]-c.plotLeft,f=b.chartY-f[1]-c.plotTop;d[e?"xAxis":"yAxis"].push({axis:a,value:a.translate(e?Math.PI-Math.atan2(h,f):Math.sqrt(Math.pow(h,2)+Math.pow(f,2)),!0)})}):d=a.call(this,b);return d})})()})(Highcharts); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-more.src.js b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-more.src.js old mode 100644 new mode 100755 index 87accb6c61e..3db8ab419e3 --- a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-more.src.js +++ b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts-more.src.js @@ -1,1581 +1,2546 @@ -// ==ClosureCompiler== -// @compilation_level SIMPLE_OPTIMIZATIONS - -/** - * @license Highcharts JS v2.3.5 (2012-12-19) - * - * (c) 2009-2011 Torstein Hønsi - * - * License: www.highcharts.com/license - */ - -// JSLint options: -/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */ - -(function (Highcharts, UNDEFINED) { -var each = Highcharts.each, - extend = Highcharts.extend, - merge = Highcharts.merge, - map = Highcharts.map, - pick = Highcharts.pick, - pInt = Highcharts.pInt, - defaultPlotOptions = Highcharts.getOptions().plotOptions, - seriesTypes = Highcharts.seriesTypes, - extendClass = Highcharts.extendClass, - splat = Highcharts.splat, - wrap = Highcharts.wrap, - Axis = Highcharts.Axis, - Tick = Highcharts.Tick, - Series = Highcharts.Series, - colProto = seriesTypes.column.prototype, - noop = function () {};/** - * The Pane object allows options that are common to a set of X and Y axes. - * - * In the future, this can be extended to basic Highcharts and Highstock. - */ -function Pane(options, chart, firstAxis) { - this.init.call(this, options, chart, firstAxis); -} - -// Extend the Pane prototype -extend(Pane.prototype, { - - /** - * Initiate the Pane object - */ - init: function (options, chart, firstAxis) { - var pane = this, - backgroundOption, - defaultOptions = pane.defaultOptions; - - pane.chart = chart; - - // Set options - if (chart.angular) { // gauges - defaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions - } - pane.options = options = merge(defaultOptions, options); - - backgroundOption = options.background; - - // To avoid having weighty logic to place, update and remove the backgrounds, - // push them to the first axis' plot bands and borrow the existing logic there. - if (backgroundOption) { - each([].concat(splat(backgroundOption)).reverse(), function (config) { - var backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients) - config = merge(pane.defaultBackgroundOptions, config); - if (backgroundColor) { - config.backgroundColor = backgroundColor; - } - config.color = config.backgroundColor; // due to naming in plotBands - firstAxis.options.plotBands.unshift(config); - }); - } - }, - - /** - * The default options object - */ - defaultOptions: { - // background: {conditional}, - center: ['50%', '50%'], - size: '85%', - startAngle: 0 - //endAngle: startAngle + 360 - }, - - /** - * The default background options - */ - defaultBackgroundOptions: { - shape: 'circle', - borderWidth: 1, - borderColor: 'silver', - backgroundColor: { - linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, - stops: [ - [0, '#FFF'], - [1, '#DDD'] - ] - }, - from: Number.MIN_VALUE, // corrected to axis min - innerRadius: 0, - to: Number.MAX_VALUE, // corrected to axis max - outerRadius: '105%' - } - -}); -var axisProto = Axis.prototype, - tickProto = Tick.prototype; - -/** - * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges - */ -var hiddenAxisMixin = { - getOffset: noop, - redraw: function () { - this.isDirty = false; // prevent setting Y axis dirty - }, - render: function () { - this.isDirty = false; // prevent setting Y axis dirty - }, - setScale: noop, - setCategories: noop, - setTitle: noop -}; - -/** - * Augmented methods for the value axis - */ -/*jslint unparam: true*/ -var radialAxisMixin = { - isRadial: true, - - /** - * The default options extend defaultYAxisOptions - */ - defaultRadialGaugeOptions: { - labels: { - align: 'center', - x: 0, - y: null // auto - }, - minorGridLineWidth: 0, - minorTickInterval: 'auto', - minorTickLength: 10, - minorTickPosition: 'inside', - minorTickWidth: 1, - plotBands: [], - tickLength: 10, - tickPosition: 'inside', - tickWidth: 2, - title: { - rotation: 0 - }, - zIndex: 2 // behind dials, points in the series group - }, - - // Circular axis around the perimeter of a polar chart - defaultRadialXOptions: { - gridLineWidth: 1, // spokes - labels: { - align: null, // auto - distance: 15, - x: 0, - y: null // auto - }, - maxPadding: 0, - minPadding: 0, - plotBands: [], - showLastLabel: false, - tickLength: 0 - }, - - // Radial axis, like a spoke in a polar chart - defaultRadialYOptions: { - gridLineInterpolation: 'circle', - labels: { - align: 'right', - x: -3, - y: -2 - }, - plotBands: [], - showLastLabel: false, - title: { - x: 4, - text: null, - rotation: 90 - } - }, - - /** - * Merge and set options - */ - setOptions: function (userOptions) { - - this.options = merge( - this.defaultOptions, - this.defaultRadialOptions, - userOptions - ); - - }, - - /** - * Wrap the getOffset method to return zero offset for title or labels in a radial - * axis - */ - getOffset: function () { - // Call the Axis prototype method (the method we're in now is on the instance) - axisProto.getOffset.call(this); - - // Title or label offsets are not counted - this.chart.axisOffset[this.side] = 0; - - // Set the center array - this.center = this.pane.center = seriesTypes.pie.prototype.getCenter.call(this.pane); - }, - - - /** - * Get the path for the axis line. This method is also referenced in the getPlotLinePath - * method. - */ - getLinePath: function (lineWidth, radius) { - var center = this.center; - radius = pick(radius, center[2] / 2 - this.offset); - - return this.chart.renderer.symbols.arc( - this.left + center[0], - this.top + center[1], - radius, - radius, - { - start: this.startAngleRad, - end: this.endAngleRad, - open: true, - innerR: 0 - } - ); - }, - - /** - * Override setAxisTranslation by setting the translation to the difference - * in rotation. This allows the translate method to return angle for - * any given value. - */ - setAxisTranslation: function () { - - // Call uber method - axisProto.setAxisTranslation.call(this); - - // Set transA and minPixelPadding - if (this.center) { // it's not defined the first time - if (this.isCircular) { - - this.transA = (this.endAngleRad - this.startAngleRad) / - ((this.max - this.min) || 1); - - - } else { - this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1); - } - - if (this.isXAxis) { - this.minPixelPadding = this.transA * this.minPointOffset + - (this.reversed ? (this.endAngleRad - this.startAngleRad) / 4 : 0); // ??? - } - } - }, - - /** - * In case of auto connect, add one closestPointRange to the max value right before - * tickPositions are computed, so that ticks will extend passed the real max. - */ - beforeSetTickPositions: function () { - if (this.autoConnect) { - this.max += (this.categories && 1) || this.pointRange || this.closestPointRange; // #1197 - } - }, - - /** - * Override the setAxisSize method to use the arc's circumference as length. This - * allows tickPixelInterval to apply to pixel lengths along the perimeter - */ - setAxisSize: function () { - - axisProto.setAxisSize.call(this); - - if (this.center) { // it's not defined the first time - this.len = this.width = this.height = this.isCircular ? - this.center[2] * (this.endAngleRad - this.startAngleRad) / 2 : - this.center[2] / 2; - } - }, - - /** - * Returns the x, y coordinate of a point given by a value and a pixel distance - * from center - */ - getPosition: function (value, length) { - if (!this.isCircular) { - length = this.translate(value); - value = this.min; - } - - return this.postTranslate( - this.translate(value), - pick(length, this.center[2] / 2) - this.offset - ); - }, - - /** - * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates. - */ - postTranslate: function (angle, radius) { - - var chart = this.chart, - center = this.center; - - angle = this.startAngleRad + angle; - - return { - x: chart.plotLeft + center[0] + Math.cos(angle) * radius, - y: chart.plotTop + center[1] + Math.sin(angle) * radius - }; - - }, - - /** - * Find the path for plot bands along the radial axis - */ - getPlotBandPath: function (from, to, options) { - var center = this.center, - startAngleRad = this.startAngleRad, - fullRadius = center[2] / 2, - radii = [ - pick(options.outerRadius, '100%'), - options.innerRadius, - pick(options.thickness, 10) - ], - percentRegex = /%$/, - start, - end, - open, - isCircular = this.isCircular, // X axis in a polar chart - ret; - - // Polygonal plot bands - if (this.options.gridLineInterpolation === 'polygon') { - ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true)); - - // Circular grid bands - } else { - - // Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from - if (!isCircular) { - radii[0] = this.translate(from); - radii[1] = this.translate(to); - } - - // Convert percentages to pixel values - radii = map(radii, function (radius) { - if (percentRegex.test(radius)) { - radius = (pInt(radius, 10) * fullRadius) / 100; - } - return radius; - }); - - // Handle full circle - if (options.shape === 'circle' || !isCircular) { - start = -Math.PI / 2; - end = Math.PI * 1.5; - open = true; - } else { - start = startAngleRad + this.translate(from); - end = startAngleRad + this.translate(to); - } - - - ret = this.chart.renderer.symbols.arc( - this.left + center[0], - this.top + center[1], - radii[0], - radii[0], - { - start: start, - end: end, - innerR: pick(radii[1], radii[0] - radii[2]), - open: open - } - ); - } - - return ret; - }, - - /** - * Find the path for plot lines perpendicular to the radial axis. - */ - getPlotLinePath: function (value, reverse) { - var axis = this, - center = axis.center, - chart = axis.chart, - end = axis.getPosition(value), - xAxis, - xy, - tickPositions, - ret; - - // Spokes - if (axis.isCircular) { - ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y]; - - // Concentric circles - } else if (axis.options.gridLineInterpolation === 'circle') { - value = axis.translate(value); - if (value) { // a value of 0 is in the center - ret = axis.getLinePath(0, value); - } - // Concentric polygons - } else { - xAxis = chart.xAxis[0]; - ret = []; - value = axis.translate(value); - tickPositions = xAxis.tickPositions; - if (xAxis.autoConnect) { - tickPositions = tickPositions.concat([tickPositions[0]]); - } - // Reverse the positions for concatenation of polygonal plot bands - if (reverse) { - tickPositions = [].concat(tickPositions).reverse(); - } - - each(tickPositions, function (pos, i) { - xy = xAxis.getPosition(pos, value); - ret.push(i ? 'L' : 'M', xy.x, xy.y); - }); - - } - return ret; - }, - - /** - * Find the position for the axis title, by default inside the gauge - */ - getTitlePosition: function () { - var center = this.center, - chart = this.chart, - titleOptions = this.options.title; - - return { - x: chart.plotLeft + center[0] + (titleOptions.x || 0), - y: chart.plotTop + center[1] - ({ high: 0.5, middle: 0.25, low: 0 }[titleOptions.align] * - center[2]) + (titleOptions.y || 0) - }; - } - -}; -/*jslint unparam: false*/ - -/** - * Override axisProto.init to mix in special axis instance functions and function overrides - */ -wrap(axisProto, 'init', function (proceed, chart, userOptions) { - var axis = this, - angular = chart.angular, - polar = chart.polar, - isX = userOptions.isX, - isHidden = angular && isX, - isCircular, - startAngleRad, - endAngleRad, - options, - chartOptions = chart.options, - paneIndex = userOptions.pane || 0, - pane, - paneOptions; - - // Before prototype.init - if (angular) { - extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin); - isCircular = !isX; - if (isCircular) { - this.defaultRadialOptions = this.defaultRadialGaugeOptions; - } - - } else if (polar) { - //extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin); - extend(this, radialAxisMixin); - isCircular = isX; - this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions); - - } - - // Run prototype.init - proceed.call(this, chart, userOptions); - - if (!isHidden && (angular || polar)) { - options = this.options; - - // Create the pane and set the pane options. - if (!chart.panes) { - chart.panes = []; - } - this.pane = chart.panes[paneIndex] = pane = new Pane( - splat(chartOptions.pane)[paneIndex], - chart, - axis - ); - paneOptions = pane.options; - - - // Disable certain features on angular and polar axes - chart.inverted = false; - chartOptions.chart.zoomType = null; - - // Start and end angle options are - // given in degrees relative to top, while internal computations are - // in radians relative to right (like SVG). - this.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180; - this.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180; - this.offset = options.offset || 0; - - this.isCircular = isCircular; - - // Automatically connect grid lines? - if (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) { - this.autoConnect = true; - } - } - -}); - -/** - * Add special cases within the Tick class' methods for radial axes. - */ -wrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) { - var axis = this.axis; - - return axis.getPosition ? - axis.getPosition(pos) : - proceed.call(this, horiz, pos, tickmarkOffset, old); -}); - -/** - * Wrap the getLabelPosition function to find the center position of the label - * based on the distance option - */ -wrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { - var axis = this.axis, - optionsY = labelOptions.y, - ret, - align = labelOptions.align, - angle = (axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180; - - if (axis.isRadial) { - ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25)); - - // Automatically rotated - if (labelOptions.rotation === 'auto') { - label.attr({ - rotation: angle - }); - - // Vertically centered - } else if (optionsY === null) { - optionsY = pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2; - - } - - // Automatic alignment - if (align === null) { - if (axis.isCircular) { - if (angle > 20 && angle < 160) { - align = 'left'; // right hemisphere - } else if (angle > 200 && angle < 340) { - align = 'right'; // left hemisphere - } else { - align = 'center'; // top or bottom - } - } else { - align = 'center'; - } - label.attr({ - align: align - }); - } - - ret.x += labelOptions.x; - ret.y += optionsY; - - } else { - ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step); - } - return ret; -}); - -/** - * Wrap the getMarkPath function to return the path of the radial marker - */ -wrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) { - var axis = this.axis, - endPoint, - ret; - - if (axis.isRadial) { - endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength); - ret = [ - 'M', - x, - y, - 'L', - endPoint.x, - endPoint.y - ]; - } else { - ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer); - } - return ret; -});/* - * The AreaRangeSeries class - * - */ - -/** - * Extend the default options with map options - */ -defaultPlotOptions.arearange = merge(defaultPlotOptions.area, { - lineWidth: 1, - marker: null, - threshold: null, - tooltip: { - pointFormat: '{series.name}: {point.low} - {point.high}
' - }, - trackByArea: true, - dataLabels: { - verticalAlign: null, - xLow: 0, - xHigh: 0, - yLow: 0, - yHigh: 0 - }, - shadow: false -}); - -/** - * Extend the point object - */ -var RangePoint = Highcharts.extendClass(Highcharts.Point, { - /** - * Apply the options containing the x and low/high data and possible some extra properties. - * This is called on point init or from point.update. Extends base Point by adding - * multiple y-like values. - * - * @param {Object} options - */ - applyOptions: function (options, x) { - var point = this, - series = point.series, - pointArrayMap = series.pointArrayMap, - i = 0, - j = 0, - valueCount = pointArrayMap.length; - - - // object input - if (typeof options === 'object' && typeof options.length !== 'number') { - - // copy options directly to point - extend(point, options); - - point.options = options; - - } else if (options.length) { // array - // with leading x value - if (options.length > valueCount) { - if (typeof options[0] === 'string') { - point.name = options[0]; - } else if (typeof options[0] === 'number') { - point.x = options[0]; - } - i++; - } - while (j < valueCount) { - point[pointArrayMap[j++]] = options[i++]; - } - } - - // Handle null and make low alias y - /*if (point.high === null) { - point.low = null; - }*/ - point.y = point[series.pointValKey]; - - // If no x is set by now, get auto incremented value. All points must have an - // x value, however the y value can be null to create a gap in the series - if (point.x === UNDEFINED && series) { - point.x = x === UNDEFINED ? series.autoIncrement() : x; - } - - return point; - }, - - /** - * Return a plain array for speedy calculation - */ - toYData: function () { - return [this.low, this.high]; - } -}); - -/** - * Add the series type - */ -seriesTypes.arearange = Highcharts.extendClass(seriesTypes.area, { - type: 'arearange', - pointArrayMap: ['low', 'high'], - pointClass: RangePoint, - pointValKey: 'low', - - /** - * Translate data points from raw values x and y to plotX and plotY - */ - translate: function () { - var series = this, - yAxis = series.yAxis; - - seriesTypes.area.prototype.translate.apply(series); - - // Set plotLow and plotHigh - each(series.points, function (point) { - - if (point.y !== null) { - point.plotLow = point.plotY; - point.plotHigh = yAxis.translate(point.high, 0, 1, 0, 1); - } - }); - }, - - /** - * Extend the line series' getSegmentPath method by applying the segment - * path to both lower and higher values of the range - */ - getSegmentPath: function (segment) { - - var highSegment = [], - i = segment.length, - baseGetSegmentPath = Series.prototype.getSegmentPath, - point, - linePath, - lowerPath, - options = this.options, - step = options.step, - higherPath; - - // Make a segment with plotX and plotY for the top values - while (i--) { - point = segment[i]; - highSegment.push({ - plotX: point.plotX, - plotY: point.plotHigh - }); - } - - // Get the paths - lowerPath = baseGetSegmentPath.call(this, segment); - if (step) { - if (step === true) { - step = 'left'; - } - options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath - } - higherPath = baseGetSegmentPath.call(this, highSegment); - options.step = step; - - // Create a line on both top and bottom of the range - linePath = [].concat(lowerPath, higherPath); - - // For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo' - higherPath[0] = 'L'; // this probably doesn't work for spline - this.areaPath = this.areaPath.concat(lowerPath, higherPath); - - return linePath; - }, - - /** - * Extend the basic drawDataLabels method by running it for both lower and higher - * values. - */ - drawDataLabels: function () { - - var data = this.data, - length = data.length, - i, - originalDataLabels = [], - seriesProto = Series.prototype, - dataLabelOptions = this.options.dataLabels, - point, - inverted = this.chart.inverted; - - if (dataLabelOptions.enabled || this._hasPointLabels) { - - // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels - i = length; - while (i--) { - point = data[i]; - - // Set preliminary values - point.y = point.high; - point.plotY = point.plotHigh; - - // Store original data labels and set preliminary label objects to be picked up - // in the uber method - originalDataLabels[i] = point.dataLabel; - point.dataLabel = point.dataLabelUpper; - - // Set the default offset - point.below = false; - if (inverted) { - dataLabelOptions.align = 'left'; - dataLabelOptions.x = dataLabelOptions.xHigh; - } else { - dataLabelOptions.y = dataLabelOptions.yHigh; - } - } - seriesProto.drawDataLabels.apply(this, arguments); // #1209 - - // Step 2: reorganize and handle data labels for the lower values - i = length; - while (i--) { - point = data[i]; - - // Move the generated labels from step 1, and reassign the original data labels - point.dataLabelUpper = point.dataLabel; - point.dataLabel = originalDataLabels[i]; - - // Reset values - point.y = point.low; - point.plotY = point.plotLow; - - // Set the default offset - point.below = true; - if (inverted) { - dataLabelOptions.align = 'right'; - dataLabelOptions.x = dataLabelOptions.xLow; - } else { - dataLabelOptions.y = dataLabelOptions.yLow; - } - } - seriesProto.drawDataLabels.apply(this, arguments); - } - - }, - - alignDataLabel: seriesTypes.column.prototype.alignDataLabel, - - getSymbol: seriesTypes.column.prototype.getSymbol, - - drawPoints: noop -});/** - * The AreaSplineRangeSeries class - */ - -defaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange); - -/** - * AreaSplineRangeSeries object - */ -seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, { - type: 'areasplinerange', - getPointSpline: seriesTypes.spline.prototype.getPointSpline -});/** - * The ColumnRangeSeries class - */ -defaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, { - lineWidth: 1, - pointRange: null -}); - -/** - * ColumnRangeSeries object - */ -seriesTypes.columnrange = extendClass(seriesTypes.arearange, { - type: 'columnrange', - /** - * Translate data points from raw values x and y to plotX and plotY - */ - translate: function () { - var series = this, - yAxis = series.yAxis, - plotHigh; - - colProto.translate.apply(series); - - // Set plotLow and plotHigh - each(series.points, function (point) { - var shapeArgs = point.shapeArgs; - - point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1); - point.plotLow = point.plotY; - - // adjust shape - shapeArgs.y = plotHigh; - shapeArgs.height = point.plotY - plotHigh; - - point.trackerArgs = shapeArgs; - }); - }, - drawGraph: noop, - pointAttrToOptions: colProto.pointAttrToOptions, - drawPoints: colProto.drawPoints, - drawTracker: colProto.drawTracker, - animate: colProto.animate -});/* - * The GaugeSeries class - */ - - - -/** - * Extend the default options - */ -defaultPlotOptions.gauge = merge(defaultPlotOptions.line, { - dataLabels: { - enabled: true, - y: 15, - borderWidth: 1, - borderColor: 'silver', - borderRadius: 3, - style: { - fontWeight: 'bold' - }, - verticalAlign: 'top', - zIndex: 2 - }, - dial: { - // radius: '80%', - // backgroundColor: 'black', - // borderColor: 'silver', - // borderWidth: 0, - // baseWidth: 3, - // topWidth: 1, - // baseLength: '70%' // of radius - // rearLength: '10%' - }, - pivot: { - //radius: 5, - //borderWidth: 0 - //borderColor: 'silver', - //backgroundColor: 'black' - }, - tooltip: { - headerFormat: '' - }, - showInLegend: false -}); - -/** - * Extend the point object - */ -var GaugePoint = Highcharts.extendClass(Highcharts.Point, { - /** - * Don't do any hover colors or anything - */ - setState: function (state) { - this.state = state; - } -}); - - -/** - * Add the series type - */ -var GaugeSeries = { - type: 'gauge', - pointClass: GaugePoint, - - // chart.angular will be set to true when a gauge series is present, and this will - // be used on the axes - angular: true, - - /* * - * Extend the bindAxes method by adding radial features to the axes - * / - _bindAxes: function () { - Series.prototype.bindAxes.call(this); - - extend(this.xAxis, gaugeXAxisMixin); - extend(this.yAxis, radialAxisMixin); - this.yAxis.onBind(); - },*/ - - /** - * Calculate paths etc - */ - translate: function () { - - var series = this, - yAxis = series.yAxis, - center = yAxis.center; - - series.generatePoints(); - - each(series.points, function (point) { - - var dialOptions = merge(series.options.dial, point.dial), - radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200, - baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100, - rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100, - baseWidth = dialOptions.baseWidth || 3, - topWidth = dialOptions.topWidth || 1; - - point.shapeType = 'path'; - point.shapeArgs = { - d: dialOptions.path || [ - 'M', - -rearLength, -baseWidth / 2, - 'L', - baseLength, -baseWidth / 2, - radius, -topWidth / 2, - radius, topWidth / 2, - baseLength, baseWidth / 2, - -rearLength, baseWidth / 2, - 'z' - ], - translateX: center[0], - translateY: center[1], - rotation: (yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true)) * 180 / Math.PI - }; - - // Positions for data label - point.plotX = center[0]; - point.plotY = center[1]; - }); - }, - - /** - * Draw the points where each point is one needle - */ - drawPoints: function () { - - var series = this, - center = series.yAxis.center, - pivot = series.pivot, - options = series.options, - pivotOptions = options.pivot, - renderer = series.chart.renderer; - - each(series.points, function (point) { - - var graphic = point.graphic, - shapeArgs = point.shapeArgs, - d = shapeArgs.d, - dialOptions = merge(options.dial, point.dial); // #1233 - - if (graphic) { - graphic.animate(shapeArgs); - shapeArgs.d = d; // animate alters it - } else { - point.graphic = renderer[point.shapeType](shapeArgs) - .attr({ - stroke: dialOptions.borderColor || 'none', - 'stroke-width': dialOptions.borderWidth || 0, - fill: dialOptions.backgroundColor || 'black', - rotation: shapeArgs.rotation // required by VML when animation is false - }) - .add(series.group); - } - }); - - // Add or move the pivot - if (pivot) { - pivot.animate({ // #1235 - translateX: center[0], - translateY: center[1] - }); - } else { - series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5)) - .attr({ - 'stroke-width': pivotOptions.borderWidth || 0, - stroke: pivotOptions.borderColor || 'silver', - fill: pivotOptions.backgroundColor || 'black' - }) - .translate(center[0], center[1]) - .add(series.group); - } - }, - - /** - * Animate the arrow up from startAngle - */ - animate: function () { - var series = this; - - each(series.points, function (point) { - var graphic = point.graphic; - - if (graphic) { - // start value - graphic.attr({ - rotation: series.yAxis.startAngleRad * 180 / Math.PI - }); - - // animate - graphic.animate({ - rotation: point.shapeArgs.rotation - }, series.options.animation); - } - }); - - // delete this function to allow it only once - series.animate = null; - }, - - render: function () { - this.group = this.plotGroup( - 'group', - 'series', - this.visible ? 'visible' : 'hidden', - this.options.zIndex, - this.chart.seriesGroup - ); - seriesTypes.pie.prototype.render.call(this); - this.group.clip(this.chart.clipRect); - }, - - setData: seriesTypes.pie.prototype.setData, - drawTracker: seriesTypes.column.prototype.drawTracker -}; -seriesTypes.gauge = Highcharts.extendClass(seriesTypes.line, GaugeSeries);/** - * Extensions for polar charts. Additionally, much of the geometry required for polar charts is - * gathered in RadialAxes.js. - * - */ - -var seriesProto = Series.prototype, - mouseTrackerProto = Highcharts.MouseTracker.prototype; - - - -/** - * Translate a point's plotX and plotY from the internal angle and radius measures to - * true plotX, plotY coordinates - */ -seriesProto.toXY = function (point) { - var xy, - chart = this.chart, - plotX = point.plotX, - plotY = point.plotY; - - // Save rectangular plotX, plotY for later computation - point.rectPlotX = plotX; - point.rectPlotY = plotY; - - // Record the angle in degrees for use in tooltip - point.deg = plotX / Math.PI * 180; - - // Find the polar plotX and plotY - xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY); - point.plotX = point.polarPlotX = xy.x - chart.plotLeft; - point.plotY = point.polarPlotY = xy.y - chart.plotTop; -}; - - -/** - * Add some special init logic to areas and areasplines - */ -function initArea(proceed, chart, options) { - proceed.call(this, chart, options); - if (this.chart.polar) { - - /** - * Overridden method to close a segment path. While in a cartesian plane the area - * goes down to the threshold, in the polar chart it goes to the center. - */ - this.closeSegment = function (path) { - var center = this.xAxis.center; - path.push( - 'L', - center[0], - center[1] - ); - }; - - // Instead of complicated logic to draw an area around the inner area in a stack, - // just draw it behind - this.closedStacks = true; - } -} -wrap(seriesTypes.area.prototype, 'init', initArea); -wrap(seriesTypes.areaspline.prototype, 'init', initArea); - - -/** - * Overridden method for calculating a spline from one point to the next - */ -wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) { - - var ret, - smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc; - denom = smoothing + 1, - plotX, - plotY, - lastPoint, - nextPoint, - lastX, - lastY, - nextX, - nextY, - leftContX, - leftContY, - rightContX, - rightContY, - distanceLeftControlPoint, - distanceRightControlPoint, - leftContAngle, - rightContAngle, - jointAngle; - - - if (this.chart.polar) { - - plotX = point.plotX; - plotY = point.plotY; - lastPoint = segment[i - 1]; - nextPoint = segment[i + 1]; - - // Connect ends - if (this.connectEnds) { - if (!lastPoint) { - lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected - } - if (!nextPoint) { - nextPoint = segment[1]; - } - } - - // find control points - if (lastPoint && nextPoint) { - - lastX = lastPoint.plotX; - lastY = lastPoint.plotY; - nextX = nextPoint.plotX; - nextY = nextPoint.plotY; - leftContX = (smoothing * plotX + lastX) / denom; - leftContY = (smoothing * plotY + lastY) / denom; - rightContX = (smoothing * plotX + nextX) / denom; - rightContY = (smoothing * plotY + nextY) / denom; - distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2)); - distanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2)); - leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX); - rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX); - jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2); - - - // Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle - if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) { - jointAngle -= Math.PI; - } - - // Find the corrected control points for a spline straight through the point - leftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint; - leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint; - rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint; - rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint; - - // Record for drawing in next point - point.rightContX = rightContX; - point.rightContY = rightContY; - - } - - - // moveTo or lineTo - if (!i) { - ret = ['M', plotX, plotY]; - } else { // curve from last point to this - ret = [ - 'C', - lastPoint.rightContX || lastPoint.plotX, - lastPoint.rightContY || lastPoint.plotY, - leftContX || plotX, - leftContY || plotY, - plotX, - plotY - ]; - lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later - } - - - } else { - ret = proceed.call(this, segment, point, i); - } - return ret; -}); - -/** - * Extend translate. The plotX and plotY values are computed as if the polar chart were a - * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from - * center. - */ -wrap(seriesProto, 'translate', function (proceed) { - - // Run uber method - proceed.call(this); - - // Postprocess plot coordinates - if (this.chart.polar && !this.preventPostTranslate) { - var points = this.points, - i = points.length; - while (i--) { - // Translate plotX, plotY from angle and radius to true plot coordinates - this.toXY(points[i]); - } - } -}); - -/** - * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in - * line-like series. - */ -wrap(seriesProto, 'getSegmentPath', function (proceed, segment) { - - var points = this.points; - - // Connect the path - if (this.chart.polar && this.options.connectEnds !== false && - segment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) { - this.connectEnds = true; // re-used in splines - segment = [].concat(segment, [points[0]]); - } - - // Run uber method - return proceed.call(this, segment); - -}); - - -function polarAnimate(proceed, init) { - var chart = this.chart, - animation = this.options.animation, - group = this.group, - markerGroup = this.markerGroup, - center = this.xAxis.center, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop, - attribs; - - // Specific animation for polar charts - if (chart.polar) { - - // Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation - // would be so slow it would't matter. - if (chart.renderer.isSVG) { - - if (animation === true) { - animation = {}; - } - - // Initialize the animation - if (init) { - - // Create an SVG specific attribute setter for scaleX and scaleY - group.attrSetters.scaleX = group.attrSetters.scaleY = function (value, key) { - this[key] = value; - if (this.scaleX !== UNDEFINED && this.scaleY !== UNDEFINED) { - this.element.setAttribute('transform', 'translate(' + this.translateX + ',' + this.translateY + ') scale(' + - this.scaleX + ',' + this.scaleY + ')'); - } - return false; - }; - - // Scale down the group and place it in the center - attribs = { - translateX: center[0] + plotLeft, - translateY: center[1] + plotTop, - scaleX: 0, - scaleY: 0 - }; - - group.attr(attribs); - if (markerGroup) { - markerGroup.attrSetters = group.attrSetters; - markerGroup.attr(attribs); - } - - // Run the animation - } else { - attribs = { - translateX: plotLeft, - translateY: plotTop, - scaleX: 1, - scaleY: 1 - }; - group.animate(attribs, animation); - if (markerGroup) { - markerGroup.animate(attribs, animation); - } - - // Delete this function to allow it only once - this.animate = null; - } - } - - // For non-polar charts, revert to the basic animation - } else { - proceed.call(this, init); - } -} - -// Define the animate method for both regular series and column series and their derivatives -wrap(seriesProto, 'animate', polarAnimate); -wrap(colProto, 'animate', polarAnimate); - - -/** - * Throw in a couple of properties to let setTooltipPoints know we're indexing the points - * in degrees (0-360), not plot pixel width. - */ -wrap(seriesProto, 'setTooltipPoints', function (proceed, renew) { - - if (this.chart.polar) { - extend(this.xAxis, { - tooltipLen: 360, // degrees are the resolution unit of the tooltipPoints array - tooltipPosName: 'deg' - }); - } - - // Run uber method - return proceed.call(this, renew); -}); - - -/** - * Extend the column prototype's translate method - */ -wrap(colProto, 'translate', function (proceed) { - - var xAxis = this.xAxis, - len = this.yAxis.len, - center = xAxis.center, - startAngleRad = xAxis.startAngleRad, - renderer = this.chart.renderer, - start, - points, - point, - i; - - this.preventPostTranslate = true; - - // Run uber method - proceed.call(this); - - // Postprocess plot coordinates - if (xAxis.isRadial) { - points = this.points; - i = points.length; - while (i--) { - point = points[i]; - start = point.barX + startAngleRad; - point.shapeType = 'path'; - point.shapeArgs = { - d: renderer.symbols.arc( - center[0], - center[1], - len - point.plotY, - null, - { - start: start, - end: start + point.pointWidth, - innerR: len - pick(point.yBottom, len) - } - ) - }; - this.toXY(point); // provide correct plotX, plotY for tooltip - } - } -}); - - -/** - * Align column data labels outside the columns. #1199. - */ -wrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) { - - if (this.chart.polar) { - var angle = point.rectPlotX / Math.PI * 180, - align, - verticalAlign; - - // Align nicely outside the perimeter of the columns - if (options.align === null) { - if (angle > 20 && angle < 160) { - align = 'left'; // right hemisphere - } else if (angle > 200 && angle < 340) { - align = 'right'; // left hemisphere - } else { - align = 'center'; // top or bottom - } - options.align = align; - } - if (options.verticalAlign === null) { - if (angle < 45 || angle > 315) { - verticalAlign = 'bottom'; // top part - } else if (angle > 135 && angle < 225) { - verticalAlign = 'top'; // bottom part - } else { - verticalAlign = 'middle'; // left or right - } - options.verticalAlign = verticalAlign; - } - - seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); - } else { - proceed.call(this, point, dataLabel, options, alignTo, isNew); - } - -}); - -/** - * Extend the mouse tracker to return the tooltip position index in terms of - * degrees rather than pixels - */ -wrap(mouseTrackerProto, 'getIndex', function (proceed, e) { - var ret, - chart = this.chart, - center, - x, - y; - - if (chart.polar) { - center = chart.xAxis[0].center; - x = e.chartX - center[0] - chart.plotLeft; - y = e.chartY - center[1] - chart.plotTop; - - ret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180); - - } else { - - // Run uber method - ret = proceed.call(this, e); - } - return ret; -}); - -/** - * Extend getMouseCoordinates to prepare for polar axis values - */ -wrap(mouseTrackerProto, 'getMouseCoordinates', function (proceed, e) { - var chart = this.chart, - ret = { - xAxis: [], - yAxis: [] - }; - - if (chart.polar) { - - each(chart.axes, function (axis) { - var isXAxis = axis.isXAxis, - center = axis.center, - x = e.chartX - center[0] - chart.plotLeft, - y = e.chartY - center[1] - chart.plotTop; - - ret[isXAxis ? 'xAxis' : 'yAxis'].push({ - axis: axis, - value: axis.translate( - isXAxis ? - Math.PI - Math.atan2(x, y) : // angle - Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center - true - ) - }); - }); - - } else { - ret = proceed.call(this, e); - } - - return ret; -}); -}(Highcharts)); +// ==ClosureCompiler== +// @compilation_level SIMPLE_OPTIMIZATIONS + +/** + * @license Highcharts JS v4.0.1 (2014-04-24) + * + * (c) 2009-2014 Torstein Honsi + * + * License: www.highcharts.com/license + */ + +// JSLint options: +/*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */ + +(function (Highcharts, UNDEFINED) { +var arrayMin = Highcharts.arrayMin, + arrayMax = Highcharts.arrayMax, + each = Highcharts.each, + extend = Highcharts.extend, + merge = Highcharts.merge, + map = Highcharts.map, + pick = Highcharts.pick, + pInt = Highcharts.pInt, + defaultPlotOptions = Highcharts.getOptions().plotOptions, + seriesTypes = Highcharts.seriesTypes, + extendClass = Highcharts.extendClass, + splat = Highcharts.splat, + wrap = Highcharts.wrap, + Axis = Highcharts.Axis, + Tick = Highcharts.Tick, + Point = Highcharts.Point, + Pointer = Highcharts.Pointer, + CenteredSeriesMixin = Highcharts.CenteredSeriesMixin, + TrackerMixin = Highcharts.TrackerMixin, + Series = Highcharts.Series, + math = Math, + mathRound = math.round, + mathFloor = math.floor, + mathMax = math.max, + Color = Highcharts.Color, + noop = function () {};/** + * The Pane object allows options that are common to a set of X and Y axes. + * + * In the future, this can be extended to basic Highcharts and Highstock. + */ +function Pane(options, chart, firstAxis) { + this.init.call(this, options, chart, firstAxis); +} + +// Extend the Pane prototype +extend(Pane.prototype, { + + /** + * Initiate the Pane object + */ + init: function (options, chart, firstAxis) { + var pane = this, + backgroundOption, + defaultOptions = pane.defaultOptions; + + pane.chart = chart; + + // Set options + if (chart.angular) { // gauges + defaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions + } + pane.options = options = merge(defaultOptions, options); + + backgroundOption = options.background; + + // To avoid having weighty logic to place, update and remove the backgrounds, + // push them to the first axis' plot bands and borrow the existing logic there. + if (backgroundOption) { + each([].concat(splat(backgroundOption)).reverse(), function (config) { + var backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients) + config = merge(pane.defaultBackgroundOptions, config); + if (backgroundColor) { + config.backgroundColor = backgroundColor; + } + config.color = config.backgroundColor; // due to naming in plotBands + firstAxis.options.plotBands.unshift(config); + }); + } + }, + + /** + * The default options object + */ + defaultOptions: { + // background: {conditional}, + center: ['50%', '50%'], + size: '85%', + startAngle: 0 + //endAngle: startAngle + 360 + }, + + /** + * The default background options + */ + defaultBackgroundOptions: { + shape: 'circle', + borderWidth: 1, + borderColor: 'silver', + backgroundColor: { + linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, + stops: [ + [0, '#FFF'], + [1, '#DDD'] + ] + }, + from: Number.MIN_VALUE, // corrected to axis min + innerRadius: 0, + to: Number.MAX_VALUE, // corrected to axis max + outerRadius: '105%' + } + +}); +var axisProto = Axis.prototype, + tickProto = Tick.prototype; + +/** + * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges + */ +var hiddenAxisMixin = { + getOffset: noop, + redraw: function () { + this.isDirty = false; // prevent setting Y axis dirty + }, + render: function () { + this.isDirty = false; // prevent setting Y axis dirty + }, + setScale: noop, + setCategories: noop, + setTitle: noop +}; + +/** + * Augmented methods for the value axis + */ +/*jslint unparam: true*/ +var radialAxisMixin = { + isRadial: true, + + /** + * The default options extend defaultYAxisOptions + */ + defaultRadialGaugeOptions: { + labels: { + align: 'center', + x: 0, + y: null // auto + }, + minorGridLineWidth: 0, + minorTickInterval: 'auto', + minorTickLength: 10, + minorTickPosition: 'inside', + minorTickWidth: 1, + tickLength: 10, + tickPosition: 'inside', + tickWidth: 2, + title: { + rotation: 0 + }, + zIndex: 2 // behind dials, points in the series group + }, + + // Circular axis around the perimeter of a polar chart + defaultRadialXOptions: { + gridLineWidth: 1, // spokes + labels: { + align: null, // auto + distance: 15, + x: 0, + y: null // auto + }, + maxPadding: 0, + minPadding: 0, + showLastLabel: false, + tickLength: 0 + }, + + // Radial axis, like a spoke in a polar chart + defaultRadialYOptions: { + gridLineInterpolation: 'circle', + labels: { + align: 'right', + x: -3, + y: -2 + }, + showLastLabel: false, + title: { + x: 4, + text: null, + rotation: 90 + } + }, + + /** + * Merge and set options + */ + setOptions: function (userOptions) { + + var options = this.options = merge( + this.defaultOptions, + this.defaultRadialOptions, + userOptions + ); + + // Make sure the plotBands array is instanciated for each Axis (#2649) + if (!options.plotBands) { + options.plotBands = []; + } + + }, + + /** + * Wrap the getOffset method to return zero offset for title or labels in a radial + * axis + */ + getOffset: function () { + // Call the Axis prototype method (the method we're in now is on the instance) + axisProto.getOffset.call(this); + + // Title or label offsets are not counted + this.chart.axisOffset[this.side] = 0; + + // Set the center array + this.center = this.pane.center = CenteredSeriesMixin.getCenter.call(this.pane); + }, + + + /** + * Get the path for the axis line. This method is also referenced in the getPlotLinePath + * method. + */ + getLinePath: function (lineWidth, radius) { + var center = this.center; + radius = pick(radius, center[2] / 2 - this.offset); + + return this.chart.renderer.symbols.arc( + this.left + center[0], + this.top + center[1], + radius, + radius, + { + start: this.startAngleRad, + end: this.endAngleRad, + open: true, + innerR: 0 + } + ); + }, + + /** + * Override setAxisTranslation by setting the translation to the difference + * in rotation. This allows the translate method to return angle for + * any given value. + */ + setAxisTranslation: function () { + + // Call uber method + axisProto.setAxisTranslation.call(this); + + // Set transA and minPixelPadding + if (this.center) { // it's not defined the first time + if (this.isCircular) { + + this.transA = (this.endAngleRad - this.startAngleRad) / + ((this.max - this.min) || 1); + + + } else { + this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1); + } + + if (this.isXAxis) { + this.minPixelPadding = this.transA * this.minPointOffset; + } else { + // This is a workaround for regression #2593, but categories still don't position correctly. + // TODO: Implement true handling of Y axis categories on gauges. + this.minPixelPadding = 0; + } + } + }, + + /** + * In case of auto connect, add one closestPointRange to the max value right before + * tickPositions are computed, so that ticks will extend passed the real max. + */ + beforeSetTickPositions: function () { + if (this.autoConnect) { + this.max += (this.categories && 1) || this.pointRange || this.closestPointRange || 0; // #1197, #2260 + } + }, + + /** + * Override the setAxisSize method to use the arc's circumference as length. This + * allows tickPixelInterval to apply to pixel lengths along the perimeter + */ + setAxisSize: function () { + + axisProto.setAxisSize.call(this); + + if (this.isRadial) { + + // Set the center array + this.center = this.pane.center = Highcharts.CenteredSeriesMixin.getCenter.call(this.pane); + + // The sector is used in Axis.translate to compute the translation of reversed axis points (#2570) + if (this.isCircular) { + this.sector = this.endAngleRad - this.startAngleRad; + } + + // Axis len is used to lay out the ticks + this.len = this.width = this.height = this.center[2] * pick(this.sector, 1) / 2; + + + } + }, + + /** + * Returns the x, y coordinate of a point given by a value and a pixel distance + * from center + */ + getPosition: function (value, length) { + return this.postTranslate( + this.isCircular ? this.translate(value) : 0, // #2848 + pick(this.isCircular ? length : this.translate(value), this.center[2] / 2) - this.offset + ); + }, + + /** + * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates. + */ + postTranslate: function (angle, radius) { + + var chart = this.chart, + center = this.center; + + angle = this.startAngleRad + angle; + + return { + x: chart.plotLeft + center[0] + Math.cos(angle) * radius, + y: chart.plotTop + center[1] + Math.sin(angle) * radius + }; + + }, + + /** + * Find the path for plot bands along the radial axis + */ + getPlotBandPath: function (from, to, options) { + var center = this.center, + startAngleRad = this.startAngleRad, + fullRadius = center[2] / 2, + radii = [ + pick(options.outerRadius, '100%'), + options.innerRadius, + pick(options.thickness, 10) + ], + percentRegex = /%$/, + start, + end, + open, + isCircular = this.isCircular, // X axis in a polar chart + ret; + + // Polygonal plot bands + if (this.options.gridLineInterpolation === 'polygon') { + ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true)); + + // Circular grid bands + } else { + + // Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from + if (!isCircular) { + radii[0] = this.translate(from); + radii[1] = this.translate(to); + } + + // Convert percentages to pixel values + radii = map(radii, function (radius) { + if (percentRegex.test(radius)) { + radius = (pInt(radius, 10) * fullRadius) / 100; + } + return radius; + }); + + // Handle full circle + if (options.shape === 'circle' || !isCircular) { + start = -Math.PI / 2; + end = Math.PI * 1.5; + open = true; + } else { + start = startAngleRad + this.translate(from); + end = startAngleRad + this.translate(to); + } + + + ret = this.chart.renderer.symbols.arc( + this.left + center[0], + this.top + center[1], + radii[0], + radii[0], + { + start: start, + end: end, + innerR: pick(radii[1], radii[0] - radii[2]), + open: open + } + ); + } + + return ret; + }, + + /** + * Find the path for plot lines perpendicular to the radial axis. + */ + getPlotLinePath: function (value, reverse) { + var axis = this, + center = axis.center, + chart = axis.chart, + end = axis.getPosition(value), + xAxis, + xy, + tickPositions, + ret; + + // Spokes + if (axis.isCircular) { + ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y]; + + // Concentric circles + } else if (axis.options.gridLineInterpolation === 'circle') { + value = axis.translate(value); + if (value) { // a value of 0 is in the center + ret = axis.getLinePath(0, value); + } + // Concentric polygons + } else { + // Find the X axis in the same pane + each(chart.xAxis, function (a) { + if (a.pane === axis.pane) { + xAxis = a; + } + }); + ret = []; + value = axis.translate(value); + tickPositions = xAxis.tickPositions; + if (xAxis.autoConnect) { + tickPositions = tickPositions.concat([tickPositions[0]]); + } + // Reverse the positions for concatenation of polygonal plot bands + if (reverse) { + tickPositions = [].concat(tickPositions).reverse(); + } + + each(tickPositions, function (pos, i) { + xy = xAxis.getPosition(pos, value); + ret.push(i ? 'L' : 'M', xy.x, xy.y); + }); + + } + return ret; + }, + + /** + * Find the position for the axis title, by default inside the gauge + */ + getTitlePosition: function () { + var center = this.center, + chart = this.chart, + titleOptions = this.options.title; + + return { + x: chart.plotLeft + center[0] + (titleOptions.x || 0), + y: chart.plotTop + center[1] - ({ high: 0.5, middle: 0.25, low: 0 }[titleOptions.align] * + center[2]) + (titleOptions.y || 0) + }; + } + +}; +/*jslint unparam: false*/ + +/** + * Override axisProto.init to mix in special axis instance functions and function overrides + */ +wrap(axisProto, 'init', function (proceed, chart, userOptions) { + var axis = this, + angular = chart.angular, + polar = chart.polar, + isX = userOptions.isX, + isHidden = angular && isX, + isCircular, + startAngleRad, + endAngleRad, + options, + chartOptions = chart.options, + paneIndex = userOptions.pane || 0, + pane, + paneOptions; + + // Before prototype.init + if (angular) { + extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin); + isCircular = !isX; + if (isCircular) { + this.defaultRadialOptions = this.defaultRadialGaugeOptions; + } + + } else if (polar) { + //extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin); + extend(this, radialAxisMixin); + isCircular = isX; + this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions); + + } + + // Run prototype.init + proceed.call(this, chart, userOptions); + + if (!isHidden && (angular || polar)) { + options = this.options; + + // Create the pane and set the pane options. + if (!chart.panes) { + chart.panes = []; + } + this.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane( + splat(chartOptions.pane)[paneIndex], + chart, + axis + ); + paneOptions = pane.options; + + + // Disable certain features on angular and polar axes + chart.inverted = false; + chartOptions.chart.zoomType = null; + + // Start and end angle options are + // given in degrees relative to top, while internal computations are + // in radians relative to right (like SVG). + this.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180; + this.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180; + this.offset = options.offset || 0; + + this.isCircular = isCircular; + + // Automatically connect grid lines? + if (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) { + this.autoConnect = true; + } + } + +}); + +/** + * Add special cases within the Tick class' methods for radial axes. + */ +wrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) { + var axis = this.axis; + + return axis.getPosition ? + axis.getPosition(pos) : + proceed.call(this, horiz, pos, tickmarkOffset, old); +}); + +/** + * Wrap the getLabelPosition function to find the center position of the label + * based on the distance option + */ +wrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { + var axis = this.axis, + optionsY = labelOptions.y, + ret, + align = labelOptions.align, + angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360; + + if (axis.isRadial) { + ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25)); + + // Automatically rotated + if (labelOptions.rotation === 'auto') { + label.attr({ + rotation: angle + }); + + // Vertically centered + } else if (optionsY === null) { + optionsY = axis.chart.renderer.fontMetrics(label.styles.fontSize).b - label.getBBox().height / 2; + } + + // Automatic alignment + if (align === null) { + if (axis.isCircular) { + if (angle > 20 && angle < 160) { + align = 'left'; // right hemisphere + } else if (angle > 200 && angle < 340) { + align = 'right'; // left hemisphere + } else { + align = 'center'; // top or bottom + } + } else { + align = 'center'; + } + label.attr({ + align: align + }); + } + + ret.x += labelOptions.x; + ret.y += optionsY; + + } else { + ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step); + } + return ret; +}); + +/** + * Wrap the getMarkPath function to return the path of the radial marker + */ +wrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) { + var axis = this.axis, + endPoint, + ret; + + if (axis.isRadial) { + endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength); + ret = [ + 'M', + x, + y, + 'L', + endPoint.x, + endPoint.y + ]; + } else { + ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer); + } + return ret; +});/* + * The AreaRangeSeries class + * + */ + +/** + * Extend the default options with map options + */ +defaultPlotOptions.arearange = merge(defaultPlotOptions.area, { + lineWidth: 1, + marker: null, + threshold: null, + tooltip: { + pointFormat: '\u25CF {series.name}: {point.low} - {point.high}
' + }, + trackByArea: true, + dataLabels: { + verticalAlign: null, + xLow: 0, + xHigh: 0, + yLow: 0, + yHigh: 0 + }, + states: { + hover: { + halo: false + } + } +}); + +/** + * Add the series type + */ +seriesTypes.arearange = extendClass(seriesTypes.area, { + type: 'arearange', + pointArrayMap: ['low', 'high'], + toYData: function (point) { + return [point.low, point.high]; + }, + pointValKey: 'low', + + /** + * Extend getSegments to force null points if the higher value is null. #1703. + */ + getSegments: function () { + var series = this; + + each(series.points, function (point) { + if (!series.options.connectNulls && (point.low === null || point.high === null)) { + point.y = null; + } else if (point.low === null && point.high !== null) { + point.y = point.high; + } + }); + Series.prototype.getSegments.call(this); + }, + + /** + * Translate data points from raw values x and y to plotX and plotY + */ + translate: function () { + var series = this, + yAxis = series.yAxis; + + seriesTypes.area.prototype.translate.apply(series); + + // Set plotLow and plotHigh + each(series.points, function (point) { + + var low = point.low, + high = point.high, + plotY = point.plotY; + + if (high === null && low === null) { + point.y = null; + } else if (low === null) { + point.plotLow = point.plotY = null; + point.plotHigh = yAxis.translate(high, 0, 1, 0, 1); + } else if (high === null) { + point.plotLow = plotY; + point.plotHigh = null; + } else { + point.plotLow = plotY; + point.plotHigh = yAxis.translate(high, 0, 1, 0, 1); + } + }); + }, + + /** + * Extend the line series' getSegmentPath method by applying the segment + * path to both lower and higher values of the range + */ + getSegmentPath: function (segment) { + + var lowSegment, + highSegment = [], + i = segment.length, + baseGetSegmentPath = Series.prototype.getSegmentPath, + point, + linePath, + lowerPath, + options = this.options, + step = options.step, + higherPath; + + // Remove nulls from low segment + lowSegment = HighchartsAdapter.grep(segment, function (point) { + return point.plotLow !== null; + }); + + // Make a segment with plotX and plotY for the top values + while (i--) { + point = segment[i]; + if (point.plotHigh !== null) { + highSegment.push({ + plotX: point.plotX, + plotY: point.plotHigh + }); + } + } + + // Get the paths + lowerPath = baseGetSegmentPath.call(this, lowSegment); + if (step) { + if (step === true) { + step = 'left'; + } + options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath + } + higherPath = baseGetSegmentPath.call(this, highSegment); + options.step = step; + + // Create a line on both top and bottom of the range + linePath = [].concat(lowerPath, higherPath); + + // For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo' + higherPath[0] = 'L'; // this probably doesn't work for spline + this.areaPath = this.areaPath.concat(lowerPath, higherPath); + + return linePath; + }, + + /** + * Extend the basic drawDataLabels method by running it for both lower and higher + * values. + */ + drawDataLabels: function () { + + var data = this.data, + length = data.length, + i, + originalDataLabels = [], + seriesProto = Series.prototype, + dataLabelOptions = this.options.dataLabels, + point, + inverted = this.chart.inverted; + + if (dataLabelOptions.enabled || this._hasPointLabels) { + + // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels + i = length; + while (i--) { + point = data[i]; + + // Set preliminary values + point.y = point.high; + point._plotY = point.plotY; + point.plotY = point.plotHigh; + + // Store original data labels and set preliminary label objects to be picked up + // in the uber method + originalDataLabels[i] = point.dataLabel; + point.dataLabel = point.dataLabelUpper; + + // Set the default offset + point.below = false; + if (inverted) { + dataLabelOptions.align = 'left'; + dataLabelOptions.x = dataLabelOptions.xHigh; + } else { + dataLabelOptions.y = dataLabelOptions.yHigh; + } + } + + if (seriesProto.drawDataLabels) { + seriesProto.drawDataLabels.apply(this, arguments); // #1209 + } + + // Step 2: reorganize and handle data labels for the lower values + i = length; + while (i--) { + point = data[i]; + + // Move the generated labels from step 1, and reassign the original data labels + point.dataLabelUpper = point.dataLabel; + point.dataLabel = originalDataLabels[i]; + + // Reset values + point.y = point.low; + point.plotY = point._plotY; + + // Set the default offset + point.below = true; + if (inverted) { + dataLabelOptions.align = 'right'; + dataLabelOptions.x = dataLabelOptions.xLow; + } else { + dataLabelOptions.y = dataLabelOptions.yLow; + } + } + if (seriesProto.drawDataLabels) { + seriesProto.drawDataLabels.apply(this, arguments); + } + } + + }, + + alignDataLabel: function () { + seriesTypes.column.prototype.alignDataLabel.apply(this, arguments); + }, + + getSymbol: seriesTypes.column.prototype.getSymbol, + + drawPoints: noop +});/** + * The AreaSplineRangeSeries class + */ + +defaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange); + +/** + * AreaSplineRangeSeries object + */ +seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, { + type: 'areasplinerange', + getPointSpline: seriesTypes.spline.prototype.getPointSpline +}); + +(function () { + + var colProto = seriesTypes.column.prototype; + + /** + * The ColumnRangeSeries class + */ + defaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, { + lineWidth: 1, + pointRange: null + }); + + /** + * ColumnRangeSeries object + */ + seriesTypes.columnrange = extendClass(seriesTypes.arearange, { + type: 'columnrange', + /** + * Translate data points from raw values x and y to plotX and plotY + */ + translate: function () { + var series = this, + yAxis = series.yAxis, + plotHigh; + + colProto.translate.apply(series); + + // Set plotLow and plotHigh + each(series.points, function (point) { + var shapeArgs = point.shapeArgs, + minPointLength = series.options.minPointLength, + heightDifference, + height, + y; + + point.tooltipPos = null; // don't inherit from column + point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1); + point.plotLow = point.plotY; + + // adjust shape + y = plotHigh; + height = point.plotY - plotHigh; + + if (height < minPointLength) { + heightDifference = (minPointLength - height); + height += heightDifference; + y -= heightDifference / 2; + } + shapeArgs.height = height; + shapeArgs.y = y; + }); + }, + trackerGroups: ['group', 'dataLabels'], + drawGraph: noop, + pointAttrToOptions: colProto.pointAttrToOptions, + drawPoints: colProto.drawPoints, + drawTracker: colProto.drawTracker, + animate: colProto.animate, + getColumnMetrics: colProto.getColumnMetrics + }); +}()); + +/* + * The GaugeSeries class + */ + + + +/** + * Extend the default options + */ +defaultPlotOptions.gauge = merge(defaultPlotOptions.line, { + dataLabels: { + enabled: true, + defer: false, + y: 15, + borderWidth: 1, + borderColor: 'silver', + borderRadius: 3, + crop: false, + style: { + fontWeight: 'bold' + }, + verticalAlign: 'top', + zIndex: 2 + }, + dial: { + // radius: '80%', + // backgroundColor: 'black', + // borderColor: 'silver', + // borderWidth: 0, + // baseWidth: 3, + // topWidth: 1, + // baseLength: '70%' // of radius + // rearLength: '10%' + }, + pivot: { + //radius: 5, + //borderWidth: 0 + //borderColor: 'silver', + //backgroundColor: 'black' + }, + tooltip: { + headerFormat: '' + }, + showInLegend: false +}); + +/** + * Extend the point object + */ +var GaugePoint = extendClass(Point, { + /** + * Don't do any hover colors or anything + */ + setState: function (state) { + this.state = state; + } +}); + + +/** + * Add the series type + */ +var GaugeSeries = { + type: 'gauge', + pointClass: GaugePoint, + + // chart.angular will be set to true when a gauge series is present, and this will + // be used on the axes + angular: true, + drawGraph: noop, + fixedBox: true, + forceDL: true, + trackerGroups: ['group', 'dataLabels'], + + /** + * Calculate paths etc + */ + translate: function () { + + var series = this, + yAxis = series.yAxis, + options = series.options, + center = yAxis.center; + + series.generatePoints(); + + each(series.points, function (point) { + + var dialOptions = merge(options.dial, point.dial), + radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200, + baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100, + rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100, + baseWidth = dialOptions.baseWidth || 3, + topWidth = dialOptions.topWidth || 1, + overshoot = options.overshoot, + rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true); + + // Handle the wrap and overshoot options + if (overshoot && typeof overshoot === 'number') { + overshoot = overshoot / 180 * Math.PI; + rotation = Math.max(yAxis.startAngleRad - overshoot, Math.min(yAxis.endAngleRad + overshoot, rotation)); + + } else if (options.wrap === false) { + rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation)); + } + + rotation = rotation * 180 / Math.PI; + + point.shapeType = 'path'; + point.shapeArgs = { + d: dialOptions.path || [ + 'M', + -rearLength, -baseWidth / 2, + 'L', + baseLength, -baseWidth / 2, + radius, -topWidth / 2, + radius, topWidth / 2, + baseLength, baseWidth / 2, + -rearLength, baseWidth / 2, + 'z' + ], + translateX: center[0], + translateY: center[1], + rotation: rotation + }; + + // Positions for data label + point.plotX = center[0]; + point.plotY = center[1]; + }); + }, + + /** + * Draw the points where each point is one needle + */ + drawPoints: function () { + + var series = this, + center = series.yAxis.center, + pivot = series.pivot, + options = series.options, + pivotOptions = options.pivot, + renderer = series.chart.renderer; + + each(series.points, function (point) { + + var graphic = point.graphic, + shapeArgs = point.shapeArgs, + d = shapeArgs.d, + dialOptions = merge(options.dial, point.dial); // #1233 + + if (graphic) { + graphic.animate(shapeArgs); + shapeArgs.d = d; // animate alters it + } else { + point.graphic = renderer[point.shapeType](shapeArgs) + .attr({ + stroke: dialOptions.borderColor || 'none', + 'stroke-width': dialOptions.borderWidth || 0, + fill: dialOptions.backgroundColor || 'black', + rotation: shapeArgs.rotation // required by VML when animation is false + }) + .add(series.group); + } + }); + + // Add or move the pivot + if (pivot) { + pivot.animate({ // #1235 + translateX: center[0], + translateY: center[1] + }); + } else { + series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5)) + .attr({ + 'stroke-width': pivotOptions.borderWidth || 0, + stroke: pivotOptions.borderColor || 'silver', + fill: pivotOptions.backgroundColor || 'black' + }) + .translate(center[0], center[1]) + .add(series.group); + } + }, + + /** + * Animate the arrow up from startAngle + */ + animate: function (init) { + var series = this; + + if (!init) { + each(series.points, function (point) { + var graphic = point.graphic; + + if (graphic) { + // start value + graphic.attr({ + rotation: series.yAxis.startAngleRad * 180 / Math.PI + }); + + // animate + graphic.animate({ + rotation: point.shapeArgs.rotation + }, series.options.animation); + } + }); + + // delete this function to allow it only once + series.animate = null; + } + }, + + render: function () { + this.group = this.plotGroup( + 'group', + 'series', + this.visible ? 'visible' : 'hidden', + this.options.zIndex, + this.chart.seriesGroup + ); + Series.prototype.render.call(this); + this.group.clip(this.chart.clipRect); + }, + + /** + * Extend the basic setData method by running processData and generatePoints immediately, + * in order to access the points from the legend. + */ + setData: function (data, redraw) { + Series.prototype.setData.call(this, data, false); + this.processData(); + this.generatePoints(); + if (pick(redraw, true)) { + this.chart.redraw(); + } + }, + + /** + * If the tracking module is loaded, add the point tracker + */ + drawTracker: TrackerMixin && TrackerMixin.drawTrackerPoint +}; +seriesTypes.gauge = extendClass(seriesTypes.line, GaugeSeries); + +/* **************************************************************************** + * Start Box plot series code * + *****************************************************************************/ + +// Set default options +defaultPlotOptions.boxplot = merge(defaultPlotOptions.column, { + fillColor: '#FFFFFF', + lineWidth: 1, + //medianColor: null, + medianWidth: 2, + states: { + hover: { + brightness: -0.3 + } + }, + //stemColor: null, + //stemDashStyle: 'solid' + //stemWidth: null, + threshold: null, + tooltip: { + pointFormat: '\u25CF {series.name}
' + + 'Maximum: {point.high}
' + + 'Upper quartile: {point.q3}
' + + 'Median: {point.median}
' + + 'Lower quartile: {point.q1}
' + + 'Minimum: {point.low}
' + + }, + //whiskerColor: null, + whiskerLength: '50%', + whiskerWidth: 2 +}); + +// Create the series object +seriesTypes.boxplot = extendClass(seriesTypes.column, { + type: 'boxplot', + pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this + toYData: function (point) { // return a plain array for speedy calculation + return [point.low, point.q1, point.median, point.q3, point.high]; + }, + pointValKey: 'high', // defines the top of the tracker + + /** + * One-to-one mapping from options to SVG attributes + */ + pointAttrToOptions: { // mapping between SVG attributes and the corresponding options + fill: 'fillColor', + stroke: 'color', + 'stroke-width': 'lineWidth' + }, + + /** + * Disable data labels for box plot + */ + drawDataLabels: noop, + + /** + * Translate data points from raw values x and y to plotX and plotY + */ + translate: function () { + var series = this, + yAxis = series.yAxis, + pointArrayMap = series.pointArrayMap; + + seriesTypes.column.prototype.translate.apply(series); + + // do the translation on each point dimension + each(series.points, function (point) { + each(pointArrayMap, function (key) { + if (point[key] !== null) { + point[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1); + } + }); + }); + }, + + /** + * Draw the data points + */ + drawPoints: function () { + var series = this, //state = series.state, + points = series.points, + options = series.options, + chart = series.chart, + renderer = chart.renderer, + pointAttr, + q1Plot, + q3Plot, + highPlot, + lowPlot, + medianPlot, + crispCorr, + crispX, + graphic, + stemPath, + stemAttr, + boxPath, + whiskersPath, + whiskersAttr, + medianPath, + medianAttr, + width, + left, + right, + halfWidth, + shapeArgs, + color, + doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles + whiskerLength = parseInt(series.options.whiskerLength, 10) / 100; + + + each(points, function (point) { + + graphic = point.graphic; + shapeArgs = point.shapeArgs; // the box + stemAttr = {}; + whiskersAttr = {}; + medianAttr = {}; + color = point.color || series.color; + + if (point.plotY !== UNDEFINED) { + + pointAttr = point.pointAttr[point.selected ? 'selected' : '']; + + // crisp vector coordinates + width = shapeArgs.width; + left = mathFloor(shapeArgs.x); + right = left + width; + halfWidth = mathRound(width / 2); + //crispX = mathRound(left + halfWidth) + crispCorr; + q1Plot = mathFloor(doQuartiles ? point.q1Plot : point.lowPlot);// + crispCorr; + q3Plot = mathFloor(doQuartiles ? point.q3Plot : point.lowPlot);// + crispCorr; + highPlot = mathFloor(point.highPlot);// + crispCorr; + lowPlot = mathFloor(point.lowPlot);// + crispCorr; + + // Stem attributes + stemAttr.stroke = point.stemColor || options.stemColor || color; + stemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth); + stemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle; + + // Whiskers attributes + whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color; + whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth); + + // Median attributes + medianAttr.stroke = point.medianColor || options.medianColor || color; + medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth); + medianAttr['stroke-linecap'] = 'round'; // #1638 + + + // The stem + crispCorr = (stemAttr['stroke-width'] % 2) / 2; + crispX = left + halfWidth + crispCorr; + stemPath = [ + // stem up + 'M', + crispX, q3Plot, + 'L', + crispX, highPlot, + + // stem down + 'M', + crispX, q1Plot, + 'L', + crispX, lowPlot + ]; + + // The box + if (doQuartiles) { + crispCorr = (pointAttr['stroke-width'] % 2) / 2; + crispX = mathFloor(crispX) + crispCorr; + q1Plot = mathFloor(q1Plot) + crispCorr; + q3Plot = mathFloor(q3Plot) + crispCorr; + left += crispCorr; + right += crispCorr; + boxPath = [ + 'M', + left, q3Plot, + 'L', + left, q1Plot, + 'L', + right, q1Plot, + 'L', + right, q3Plot, + 'L', + left, q3Plot, + 'z' + ]; + } + + // The whiskers + if (whiskerLength) { + crispCorr = (whiskersAttr['stroke-width'] % 2) / 2; + highPlot = highPlot + crispCorr; + lowPlot = lowPlot + crispCorr; + whiskersPath = [ + // High whisker + 'M', + crispX - halfWidth * whiskerLength, + highPlot, + 'L', + crispX + halfWidth * whiskerLength, + highPlot, + + // Low whisker + 'M', + crispX - halfWidth * whiskerLength, + lowPlot, + 'L', + crispX + halfWidth * whiskerLength, + lowPlot + ]; + } + + // The median + crispCorr = (medianAttr['stroke-width'] % 2) / 2; + medianPlot = mathRound(point.medianPlot) + crispCorr; + medianPath = [ + 'M', + left, + medianPlot, + 'L', + right, + medianPlot + ]; + + // Create or update the graphics + if (graphic) { // update + + point.stem.animate({ d: stemPath }); + if (whiskerLength) { + point.whiskers.animate({ d: whiskersPath }); + } + if (doQuartiles) { + point.box.animate({ d: boxPath }); + } + point.medianShape.animate({ d: medianPath }); + + } else { // create new + point.graphic = graphic = renderer.g() + .add(series.group); + + point.stem = renderer.path(stemPath) + .attr(stemAttr) + .add(graphic); + + if (whiskerLength) { + point.whiskers = renderer.path(whiskersPath) + .attr(whiskersAttr) + .add(graphic); + } + if (doQuartiles) { + point.box = renderer.path(boxPath) + .attr(pointAttr) + .add(graphic); + } + point.medianShape = renderer.path(medianPath) + .attr(medianAttr) + .add(graphic); + } + } + }); + + } + + +}); + +/* **************************************************************************** + * End Box plot series code * + *****************************************************************************/ +/* **************************************************************************** + * Start error bar series code * + *****************************************************************************/ + +// 1 - set default options +defaultPlotOptions.errorbar = merge(defaultPlotOptions.boxplot, { + color: '#000000', + grouping: false, + linkedTo: ':previous', + tooltip: { + pointFormat: '\u25CF {series.name}: {point.low} - {point.high}
' + }, + whiskerWidth: null +}); + +// 2 - Create the series object +seriesTypes.errorbar = extendClass(seriesTypes.boxplot, { + type: 'errorbar', + pointArrayMap: ['low', 'high'], // array point configs are mapped to this + toYData: function (point) { // return a plain array for speedy calculation + return [point.low, point.high]; + }, + pointValKey: 'high', // defines the top of the tracker + doQuartiles: false, + drawDataLabels: seriesTypes.arearange ? seriesTypes.arearange.prototype.drawDataLabels : noop, + + /** + * Get the width and X offset, either on top of the linked series column + * or standalone + */ + getColumnMetrics: function () { + return (this.linkedParent && this.linkedParent.columnMetrics) || + seriesTypes.column.prototype.getColumnMetrics.call(this); + } +}); + +/* **************************************************************************** + * End error bar series code * + *****************************************************************************/ +/* **************************************************************************** + * Start Waterfall series code * + *****************************************************************************/ + +// 1 - set default options +defaultPlotOptions.waterfall = merge(defaultPlotOptions.column, { + lineWidth: 1, + lineColor: '#333', + dashStyle: 'dot', + borderColor: '#333' +}); + + +// 2 - Create the series object +seriesTypes.waterfall = extendClass(seriesTypes.column, { + type: 'waterfall', + + upColorProp: 'fill', + + pointArrayMap: ['low', 'y'], + + pointValKey: 'y', + + /** + * Init waterfall series, force stacking + */ + init: function (chart, options) { + // force stacking + options.stacking = true; + + seriesTypes.column.prototype.init.call(this, chart, options); + }, + + + /** + * Translate data points from raw values + */ + translate: function () { + var series = this, + options = series.options, + axis = series.yAxis, + len, + i, + points, + point, + shapeArgs, + stack, + y, + previousY, + stackPoint, + threshold = options.threshold; + + // run column series translate + seriesTypes.column.prototype.translate.apply(this); + + previousY = threshold; + points = series.points; + + for (i = 0, len = points.length; i < len; i++) { + // cache current point object + point = points[i]; + shapeArgs = point.shapeArgs; + + // get current stack + stack = series.getStack(i); + stackPoint = stack.points[series.index + ',' + i]; + + // override point value for sums + if (isNaN(point.y)) { + point.y = series.yData[i]; + } + + // up points + y = mathMax(previousY, previousY + point.y) + stackPoint[0]; + shapeArgs.y = axis.translate(y, 0, 1); + + + // sum points + if (point.isSum || point.isIntermediateSum) { + shapeArgs.y = axis.translate(stackPoint[1], 0, 1); + shapeArgs.height = axis.translate(stackPoint[0], 0, 1) - shapeArgs.y; + + // if it's not the sum point, update previous stack end position + } else { + previousY += stack.total; + } + + // negative points + if (shapeArgs.height < 0) { + shapeArgs.y += shapeArgs.height; + shapeArgs.height *= -1; + } + + point.plotY = shapeArgs.y = mathRound(shapeArgs.y) - (series.borderWidth % 2) / 2; + shapeArgs.height = mathRound(shapeArgs.height); + point.yBottom = shapeArgs.y + shapeArgs.height; + } + }, + + /** + * Call default processData then override yData to reflect waterfall's extremes on yAxis + */ + processData: function (force) { + var series = this, + options = series.options, + yData = series.yData, + points = series.points, + point, + dataLength = yData.length, + threshold = options.threshold || 0, + subSum, + sum, + dataMin, + dataMax, + y, + i; + + sum = subSum = dataMin = dataMax = threshold; + + for (i = 0; i < dataLength; i++) { + y = yData[i]; + point = points && points[i] ? points[i] : {}; + + if (y === "sum" || point.isSum) { + yData[i] = sum; + } else if (y === "intermediateSum" || point.isIntermediateSum) { + yData[i] = subSum; + subSum = threshold; + } else { + sum += y; + subSum += y; + } + dataMin = Math.min(sum, dataMin); + dataMax = Math.max(sum, dataMax); + } + + Series.prototype.processData.call(this, force); + + // Record extremes + series.dataMin = dataMin; + series.dataMax = dataMax; + }, + + /** + * Return y value or string if point is sum + */ + toYData: function (pt) { + if (pt.isSum) { + return "sum"; + } else if (pt.isIntermediateSum) { + return "intermediateSum"; + } + + return pt.y; + }, + + /** + * Postprocess mapping between options and SVG attributes + */ + getAttribs: function () { + seriesTypes.column.prototype.getAttribs.apply(this, arguments); + + var series = this, + options = series.options, + stateOptions = options.states, + upColor = options.upColor || series.color, + hoverColor = Highcharts.Color(upColor).brighten(0.1).get(), + seriesDownPointAttr = merge(series.pointAttr), + upColorProp = series.upColorProp; + + seriesDownPointAttr[''][upColorProp] = upColor; + seriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || hoverColor; + seriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor; + + each(series.points, function (point) { + if (point.y > 0 && !point.color) { + point.pointAttr = seriesDownPointAttr; + point.color = upColor; + } + }); + }, + + /** + * Draw columns' connector lines + */ + getGraphPath: function () { + + var data = this.data, + length = data.length, + lineWidth = this.options.lineWidth + this.borderWidth, + normalizer = mathRound(lineWidth) % 2 / 2, + path = [], + M = 'M', + L = 'L', + prevArgs, + pointArgs, + i, + d; + + for (i = 1; i < length; i++) { + pointArgs = data[i].shapeArgs; + prevArgs = data[i - 1].shapeArgs; + + d = [ + M, + prevArgs.x + prevArgs.width, prevArgs.y + normalizer, + L, + pointArgs.x, prevArgs.y + normalizer + ]; + + if (data[i - 1].y < 0) { + d[2] += prevArgs.height; + d[5] += prevArgs.height; + } + + path = path.concat(d); + } + + return path; + }, + + /** + * Extremes are recorded in processData + */ + getExtremes: noop, + + /** + * Return stack for given index + */ + getStack: function (i) { + var axis = this.yAxis, + stacks = axis.stacks, + key = this.stackKey; + + if (this.processedYData[i] < this.options.threshold) { + key = '-' + key; + } + + return stacks[key][i]; + }, + + drawGraph: Series.prototype.drawGraph +}); + +/* **************************************************************************** + * End Waterfall series code * + *****************************************************************************/ +/* **************************************************************************** + * Start Bubble series code * + *****************************************************************************/ + +// 1 - set default options +defaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, { + dataLabels: { + format: '{point.z}', + inside: true, + style: { + color: 'white', + textShadow: '0px 0px 3px black' + }, + verticalAlign: 'middle' + }, + // displayNegative: true, + marker: { + // fillOpacity: 0.5, + lineColor: null, // inherit from series.color + lineWidth: 1 + }, + minSize: 8, + maxSize: '20%', + // negativeColor: null, + // sizeBy: 'area' + states: { + hover: { + halo: { + size: 5 + } + } + }, + tooltip: { + pointFormat: '({point.x}, {point.y}), Size: {point.z}' + }, + turboThreshold: 0, + zThreshold: 0 +}); + +var BubblePoint = extendClass(Point, { + haloPath: function () { + return Point.prototype.haloPath.call(this, this.shapeArgs.r + this.series.options.states.hover.halo.size); + } +}); + +// 2 - Create the series object +seriesTypes.bubble = extendClass(seriesTypes.scatter, { + type: 'bubble', + pointClass: BubblePoint, + pointArrayMap: ['y', 'z'], + parallelArrays: ['x', 'y', 'z'], + trackerGroups: ['group', 'dataLabelsGroup'], + bubblePadding: true, + + /** + * Mapping between SVG attributes and the corresponding options + */ + pointAttrToOptions: { + stroke: 'lineColor', + 'stroke-width': 'lineWidth', + fill: 'fillColor' + }, + + /** + * Apply the fillOpacity to all fill positions + */ + applyOpacity: function (fill) { + var markerOptions = this.options.marker, + fillOpacity = pick(markerOptions.fillOpacity, 0.5); + + // When called from Legend.colorizeItem, the fill isn't predefined + fill = fill || markerOptions.fillColor || this.color; + + if (fillOpacity !== 1) { + fill = Color(fill).setOpacity(fillOpacity).get('rgba'); + } + return fill; + }, + + /** + * Extend the convertAttribs method by applying opacity to the fill + */ + convertAttribs: function () { + var obj = Series.prototype.convertAttribs.apply(this, arguments); + + obj.fill = this.applyOpacity(obj.fill); + + return obj; + }, + + /** + * Get the radius for each point based on the minSize, maxSize and each point's Z value. This + * must be done prior to Series.translate because the axis needs to add padding in + * accordance with the point sizes. + */ + getRadii: function (zMin, zMax, minSize, maxSize) { + var len, + i, + pos, + zData = this.zData, + radii = [], + sizeByArea = this.options.sizeBy !== 'width', + zRange; + + // Set the shape type and arguments to be picked up in drawPoints + for (i = 0, len = zData.length; i < len; i++) { + zRange = zMax - zMin; + pos = zRange > 0 ? // relative size, a number between 0 and 1 + (zData[i] - zMin) / (zMax - zMin) : + 0.5; + if (sizeByArea && pos >= 0) { + pos = Math.sqrt(pos); + } + radii.push(math.ceil(minSize + pos * (maxSize - minSize)) / 2); + } + this.radii = radii; + }, + + /** + * Perform animation on the bubbles + */ + animate: function (init) { + var animation = this.options.animation; + + if (!init) { // run the animation + each(this.points, function (point) { + var graphic = point.graphic, + shapeArgs = point.shapeArgs; + + if (graphic && shapeArgs) { + // start values + graphic.attr('r', 1); + + // animate + graphic.animate({ + r: shapeArgs.r + }, animation); + } + }); + + // delete this function to allow it only once + this.animate = null; + } + }, + + /** + * Extend the base translate method to handle bubble size + */ + translate: function () { + + var i, + data = this.data, + point, + radius, + radii = this.radii; + + // Run the parent method + seriesTypes.scatter.prototype.translate.call(this); + + // Set the shape type and arguments to be picked up in drawPoints + i = data.length; + + while (i--) { + point = data[i]; + radius = radii ? radii[i] : 0; // #1737 + + // Flag for negativeColor to be applied in Series.js + point.negative = point.z < (this.options.zThreshold || 0); + + if (radius >= this.minPxSize / 2) { + // Shape arguments + point.shapeType = 'circle'; + point.shapeArgs = { + x: point.plotX, + y: point.plotY, + r: radius + }; + + // Alignment box for the data label + point.dlBox = { + x: point.plotX - radius, + y: point.plotY - radius, + width: 2 * radius, + height: 2 * radius + }; + } else { // below zThreshold + point.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691 + } + } + }, + + /** + * Get the series' symbol in the legend + * + * @param {Object} legend The legend object + * @param {Object} item The series (this) or point + */ + drawLegendSymbol: function (legend, item) { + var radius = pInt(legend.itemStyle.fontSize) / 2; + + item.legendSymbol = this.chart.renderer.circle( + radius, + legend.baseline - radius, + radius + ).attr({ + zIndex: 3 + }).add(item.legendGroup); + item.legendSymbol.isMarker = true; + + }, + + drawPoints: seriesTypes.column.prototype.drawPoints, + alignDataLabel: seriesTypes.column.prototype.alignDataLabel +}); + +/** + * Add logic to pad each axis with the amount of pixels + * necessary to avoid the bubbles to overflow. + */ +Axis.prototype.beforePadding = function () { + var axis = this, + axisLength = this.len, + chart = this.chart, + pxMin = 0, + pxMax = axisLength, + isXAxis = this.isXAxis, + dataKey = isXAxis ? 'xData' : 'yData', + min = this.min, + extremes = {}, + smallestSize = math.min(chart.plotWidth, chart.plotHeight), + zMin = Number.MAX_VALUE, + zMax = -Number.MAX_VALUE, + range = this.max - min, + transA = axisLength / range, + activeSeries = []; + + // Handle padding on the second pass, or on redraw + if (this.tickPositions) { + each(this.series, function (series) { + + var seriesOptions = series.options, + zData; + + if (series.bubblePadding && (series.visible || !chart.options.chart.ignoreHiddenSeries)) { + + // Correction for #1673 + axis.allowZoomOutside = true; + + // Cache it + activeSeries.push(series); + + if (isXAxis) { // because X axis is evaluated first + + // For each series, translate the size extremes to pixel values + each(['minSize', 'maxSize'], function (prop) { + var length = seriesOptions[prop], + isPercent = /%$/.test(length); + + length = pInt(length); + extremes[prop] = isPercent ? + smallestSize * length / 100 : + length; + + }); + series.minPxSize = extremes.minSize; + + // Find the min and max Z + zData = series.zData; + if (zData.length) { // #1735 + zMin = math.min( + zMin, + math.max( + arrayMin(zData), + seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE + ) + ); + zMax = math.max(zMax, arrayMax(zData)); + } + } + } + }); + + each(activeSeries, function (series) { + + var data = series[dataKey], + i = data.length, + radius; + + if (isXAxis) { + series.getRadii(zMin, zMax, extremes.minSize, extremes.maxSize); + } + + if (range > 0) { + while (i--) { + if (typeof data[i] === 'number') { + radius = series.radii[i]; + pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin); + pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax); + } + } + } + }); + + if (activeSeries.length && range > 0 && pick(this.options.min, this.userMin) === UNDEFINED && pick(this.options.max, this.userMax) === UNDEFINED) { + pxMax -= axisLength; + transA *= (axisLength + pxMin - pxMax) / axisLength; + this.min += pxMin / transA; + this.max += pxMax / transA; + } + } +}; + +/* **************************************************************************** + * End Bubble series code * + *****************************************************************************/ + +(function () { + + /** + * Extensions for polar charts. Additionally, much of the geometry required for polar charts is + * gathered in RadialAxes.js. + * + */ + + var seriesProto = Series.prototype, + pointerProto = Pointer.prototype, + colProto; + + /** + * Translate a point's plotX and plotY from the internal angle and radius measures to + * true plotX, plotY coordinates + */ + seriesProto.toXY = function (point) { + var xy, + chart = this.chart, + plotX = point.plotX, + plotY = point.plotY, + clientX; + + // Save rectangular plotX, plotY for later computation + point.rectPlotX = plotX; + point.rectPlotY = plotY; + + // Record the angle in degrees for use in tooltip + clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360; + if (clientX < 0) { // #2665 + clientX += 360; + } + point.clientX = clientX; + + + // Find the polar plotX and plotY + xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY); + point.plotX = point.polarPlotX = xy.x - chart.plotLeft; + point.plotY = point.polarPlotY = xy.y - chart.plotTop; + }; + + /** + * Order the tooltip points to get the mouse capture ranges correct. #1915. + */ + seriesProto.orderTooltipPoints = function (points) { + if (this.chart.polar) { + points.sort(function (a, b) { + return a.clientX - b.clientX; + }); + + // Wrap mouse tracking around to capture movement on the segment to the left + // of the north point (#1469, #2093). + if (points[0]) { + points[0].wrappedClientX = points[0].clientX + 360; + points.push(points[0]); + } + } + }; + + + /** + * Add some special init logic to areas and areasplines + */ + function initArea(proceed, chart, options) { + proceed.call(this, chart, options); + if (this.chart.polar) { + + /** + * Overridden method to close a segment path. While in a cartesian plane the area + * goes down to the threshold, in the polar chart it goes to the center. + */ + this.closeSegment = function (path) { + var center = this.xAxis.center; + path.push( + 'L', + center[0], + center[1] + ); + }; + + // Instead of complicated logic to draw an area around the inner area in a stack, + // just draw it behind + this.closedStacks = true; + } + } + + if (seriesTypes.area) { + wrap(seriesTypes.area.prototype, 'init', initArea); + } + if (seriesTypes.areaspline) { + wrap(seriesTypes.areaspline.prototype, 'init', initArea); + } + + if (seriesTypes.spline) { + /** + * Overridden method for calculating a spline from one point to the next + */ + wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) { + + var ret, + smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc; + denom = smoothing + 1, + plotX, + plotY, + lastPoint, + nextPoint, + lastX, + lastY, + nextX, + nextY, + leftContX, + leftContY, + rightContX, + rightContY, + distanceLeftControlPoint, + distanceRightControlPoint, + leftContAngle, + rightContAngle, + jointAngle; + + + if (this.chart.polar) { + + plotX = point.plotX; + plotY = point.plotY; + lastPoint = segment[i - 1]; + nextPoint = segment[i + 1]; + + // Connect ends + if (this.connectEnds) { + if (!lastPoint) { + lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected + } + if (!nextPoint) { + nextPoint = segment[1]; + } + } + + // find control points + if (lastPoint && nextPoint) { + + lastX = lastPoint.plotX; + lastY = lastPoint.plotY; + nextX = nextPoint.plotX; + nextY = nextPoint.plotY; + leftContX = (smoothing * plotX + lastX) / denom; + leftContY = (smoothing * plotY + lastY) / denom; + rightContX = (smoothing * plotX + nextX) / denom; + rightContY = (smoothing * plotY + nextY) / denom; + distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2)); + distanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2)); + leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX); + rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX); + jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2); + + + // Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle + if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) { + jointAngle -= Math.PI; + } + + // Find the corrected control points for a spline straight through the point + leftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint; + leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint; + rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint; + rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint; + + // Record for drawing in next point + point.rightContX = rightContX; + point.rightContY = rightContY; + + } + + + // moveTo or lineTo + if (!i) { + ret = ['M', plotX, plotY]; + } else { // curve from last point to this + ret = [ + 'C', + lastPoint.rightContX || lastPoint.plotX, + lastPoint.rightContY || lastPoint.plotY, + leftContX || plotX, + leftContY || plotY, + plotX, + plotY + ]; + lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later + } + + + } else { + ret = proceed.call(this, segment, point, i); + } + return ret; + }); + } + + /** + * Extend translate. The plotX and plotY values are computed as if the polar chart were a + * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from + * center. + */ + wrap(seriesProto, 'translate', function (proceed) { + + // Run uber method + proceed.call(this); + + // Postprocess plot coordinates + if (this.chart.polar && !this.preventPostTranslate) { + var points = this.points, + i = points.length; + while (i--) { + // Translate plotX, plotY from angle and radius to true plot coordinates + this.toXY(points[i]); + } + } + }); + + /** + * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in + * line-like series. + */ + wrap(seriesProto, 'getSegmentPath', function (proceed, segment) { + + var points = this.points; + + // Connect the path + if (this.chart.polar && this.options.connectEnds !== false && + segment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) { + this.connectEnds = true; // re-used in splines + segment = [].concat(segment, [points[0]]); + } + + // Run uber method + return proceed.call(this, segment); + + }); + + + function polarAnimate(proceed, init) { + var chart = this.chart, + animation = this.options.animation, + group = this.group, + markerGroup = this.markerGroup, + center = this.xAxis.center, + plotLeft = chart.plotLeft, + plotTop = chart.plotTop, + attribs; + + // Specific animation for polar charts + if (chart.polar) { + + // Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation + // would be so slow it would't matter. + if (chart.renderer.isSVG) { + + if (animation === true) { + animation = {}; + } + + // Initialize the animation + if (init) { + + // Scale down the group and place it in the center + attribs = { + translateX: center[0] + plotLeft, + translateY: center[1] + plotTop, + scaleX: 0.001, // #1499 + scaleY: 0.001 + }; + + group.attr(attribs); + if (markerGroup) { + //markerGroup.attrSetters = group.attrSetters; + markerGroup.attr(attribs); + } + + // Run the animation + } else { + attribs = { + translateX: plotLeft, + translateY: plotTop, + scaleX: 1, + scaleY: 1 + }; + group.animate(attribs, animation); + if (markerGroup) { + markerGroup.animate(attribs, animation); + } + + // Delete this function to allow it only once + this.animate = null; + } + } + + // For non-polar charts, revert to the basic animation + } else { + proceed.call(this, init); + } + } + + // Define the animate method for regular series + wrap(seriesProto, 'animate', polarAnimate); + + /** + * Throw in a couple of properties to let setTooltipPoints know we're indexing the points + * in degrees (0-360), not plot pixel width. + */ + wrap(seriesProto, 'setTooltipPoints', function (proceed, renew) { + + if (this.chart.polar) { + extend(this.xAxis, { + tooltipLen: 360 // degrees are the resolution unit of the tooltipPoints array + }); + } + // Run uber method + return proceed.call(this, renew); + }); + + + if (seriesTypes.column) { + + colProto = seriesTypes.column.prototype; + /** + * Define the animate method for columnseries + */ + wrap(colProto, 'animate', polarAnimate); + + + /** + * Extend the column prototype's translate method + */ + wrap(colProto, 'translate', function (proceed) { + + var xAxis = this.xAxis, + len = this.yAxis.len, + center = xAxis.center, + startAngleRad = xAxis.startAngleRad, + renderer = this.chart.renderer, + start, + points, + point, + i; + + this.preventPostTranslate = true; + + // Run uber method + proceed.call(this); + + // Postprocess plot coordinates + if (xAxis.isRadial) { + points = this.points; + i = points.length; + while (i--) { + point = points[i]; + start = point.barX + startAngleRad; + point.shapeType = 'path'; + point.shapeArgs = { + d: renderer.symbols.arc( + center[0], + center[1], + len - point.plotY, + null, + { + start: start, + end: start + point.pointWidth, + innerR: len - pick(point.yBottom, len) + } + ) + }; + // Provide correct plotX, plotY for tooltip + this.toXY(point); + point.tooltipPos = [point.plotX, point.plotY]; + point.ttBelow = point.plotY > center[1]; + } + } + }); + + + /** + * Align column data labels outside the columns. #1199. + */ + wrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) { + + if (this.chart.polar) { + var angle = point.rectPlotX / Math.PI * 180, + align, + verticalAlign; + + // Align nicely outside the perimeter of the columns + if (options.align === null) { + if (angle > 20 && angle < 160) { + align = 'left'; // right hemisphere + } else if (angle > 200 && angle < 340) { + align = 'right'; // left hemisphere + } else { + align = 'center'; // top or bottom + } + options.align = align; + } + if (options.verticalAlign === null) { + if (angle < 45 || angle > 315) { + verticalAlign = 'bottom'; // top part + } else if (angle > 135 && angle < 225) { + verticalAlign = 'top'; // bottom part + } else { + verticalAlign = 'middle'; // left or right + } + options.verticalAlign = verticalAlign; + } + + seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); + } else { + proceed.call(this, point, dataLabel, options, alignTo, isNew); + } + + }); + } + + + /** + * Extend the mouse tracker to return the tooltip position index in terms of + * degrees rather than pixels + */ + wrap(pointerProto, 'getIndex', function (proceed, e) { + var ret, + chart = this.chart, + center, + x, + y; + + if (chart.polar) { + center = chart.xAxis[0].center; + x = e.chartX - center[0] - chart.plotLeft; + y = e.chartY - center[1] - chart.plotTop; + + ret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180); + + } else { + + // Run uber method + ret = proceed.call(this, e); + } + return ret; + }); + + /** + * Extend getCoordinates to prepare for polar axis values + */ + wrap(pointerProto, 'getCoordinates', function (proceed, e) { + var chart = this.chart, + ret = { + xAxis: [], + yAxis: [] + }; + + if (chart.polar) { + + each(chart.axes, function (axis) { + var isXAxis = axis.isXAxis, + center = axis.center, + x = e.chartX - center[0] - chart.plotLeft, + y = e.chartY - center[1] - chart.plotTop; + + ret[isXAxis ? 'xAxis' : 'yAxis'].push({ + axis: axis, + value: axis.translate( + isXAxis ? + Math.PI - Math.atan2(x, y) : // angle + Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center + true + ) + }); + }); + + } else { + ret = proceed.call(this, e); + } + + return ret; + }); + +}()); + +}(Highcharts)); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts.js b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts.js old mode 100644 new mode 100755 index 52b64e54482..06e464031b9 --- a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts.js +++ b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts.js @@ -1,246 +1,305 @@ -/* - Highcharts JS v2.3.3 (2012-10-04) - - (c) 2009-2011 Torstein Hønsi - - License: www.highcharts.com/license -*/ -(function(){function r(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function ka(){for(var a=0,b=arguments,c=b.length,d={};a-1?b.split(".")[1].length:0):a=isNaN(b=L(b))?2:b;var b=a,c=c===void 0?e.decimalPoint:c,d=d===void 0?e.thousandsSep:d,e=f<0?"-":"",a=String(w(f=L(+f||0).toFixed(b))),g=a.length>3?a.length%3:0;return e+(g?a.substr(0,g)+d:"")+a.substr(g).replace(/(\d{3})(?=\d)/g, -"$1"+d)+(b?c+L(f-a).toFixed(b).slice(2):"")}function sa(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function hb(a,b,c,d){var e,c=o(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d=B[ib]&&(i.setMilliseconds(0),i.setSeconds(b>=B[Ua]?0:j*T(i.getSeconds()/j)));if(b>=B[Ua])i[Ab](b>=B[Ja]?0:j*T(i[jb]()/j));if(b>=B[Ja])i[Bb](b>=B[oa]?0:j*T(i[kb]()/ -j));if(b>=B[oa])i[lb](b>=B[Ka]?1:j*T(i[La]()/j));b>=B[Ka]&&(i[Cb](b>=B[ta]?0:j*T(i[Wa]()/j)),h=i[Xa]());b>=B[ta]&&(h-=h%j,i[Db](h));if(b===B[Va])i[lb](i[La]()-i[mb]()+o(d,1));d=1;h=i[Xa]();for(var k=i.getTime(),l=i[Wa](),m=i[La](),i=g?0:(864E5+i.getTimezoneOffset()*6E4)%864E5;kc&&(c=a[b]);return c}function Ea(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Na(a){Za||(Za=Q(ia));a&&Za.appendChild(a);Za.innerHTML=""}function $a(a, -b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw c;else K.console&&console.log(c)}function ea(a){return parseFloat(a.toPrecision(14))}function ua(a,b){Oa=o(a,b.animation)}function Gb(){var a=M.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";Ya=a?Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,o(c,1),o(g,0),o(h,0),o(i,0))).getTime()};jb=b+"Minutes";kb=b+"Hours";mb=b+"Day";La=b+"Date";Wa=b+"Month";Xa=b+"FullYear";Ab=c+"Minutes";Bb=c+"Hours";lb=c+"Date";Cb=c+"Month"; -Db=c+"FullYear"}function va(){}function Pa(a,b,c){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;c||this.addLabel()}function nb(a,b){this.axis=a;if(b)this.options=b,this.id=b.id;return this}function Hb(a,b,c,d,e,f){var g=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.stack=e;this.percent=f==="percent";this.alignOptions={align:b.align||(g?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(g?"middle":c?"bottom":"top"),y:o(b.y,g?4:c?14:-6),x:o(b.x,g?c?-6:6: -0)};this.textAlign=b.textAlign||(g?c?"right":"left":"center")}function ob(){this.init.apply(this,arguments)}function pb(a,b){var c=b.borderWidth,d=b.style,e=w(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape,null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).hide().add();U||this.label.shadow(b.shadow);this.shared=b.shared} -function qb(a,b){var c=U?"":b.chart.zoomType;this.zoomX=/x/.test(c);this.zoomY=/y/.test(c);this.options=b;this.chart=a;this.init(a,b.tooltip)}function rb(a){this.init(a)}function sb(a,b){var c,d=a.series;a.series=null;c=A(M,a);c.series=a.series=d;var d=c.chart,e=d.margin,e=X(e)?e:[e,e,e,e];this.optionsMarginTop=o(d.marginTop,e[0]);this.optionsMarginRight=o(d.marginRight,e[1]);this.optionsMarginBottom=o(d.marginBottom,e[2]);this.optionsMarginLeft=o(d.marginLeft,e[3]);this.runChartClick=(e=d.events)&& -!!e.click;this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;this.init(e)}var x,C=document,K=window,J=Math,s=J.round,T=J.floor,wa=J.ceil,y=J.max,O=J.min,L=J.abs,V=J.cos,Y=J.sin,xa=J.PI,ab=xa*2/360,Fa=navigator.userAgent,Ib=K.opera,Ca=/msie/i.test(Fa)&&!Ib,Qa=C.documentMode===8,tb=/AppleWebKit/.test(Fa),bb=/Firefox/.test(Fa),pa="http://www.w3.org/2000/svg",Z=!!C.createElementNS&&!!C.createElementNS(pa,"svg").createSVGRect,Pb=bb&&parseInt(Fa.split("Firefox/")[1], -10)<4,U=!Z&&!Ca&&!!C.createElement("canvas").getContext,Ra,fa=C.documentElement.ontouchstart!==x,Jb={},ub=0,Za,M,cb,Oa,vb,B,ya=function(){},ia="div",R="none",wb="rgba(192,192,192,"+(Z?1.0E-6:0.0020)+")",zb="millisecond",ib="second",Ua="minute",Ja="hour",oa="day",Va="week",Ka="month",ta="year",Ya,jb,kb,mb,La,Wa,Xa,Ab,Bb,lb,Cb,Db,$={};K.Highcharts={};cb=function(a,b,c){if(!t(b)||isNaN(b))return"Invalid date";var a=o(a,"%Y-%m-%d %H:%M:%S"),d=new Date(b),e,f=d[kb](),g=d[mb](),h=d[La](),i=d[Wa](),j=d[Xa](), -k=M.lang,l=k.weekdays,b={a:l[g].substr(0,3),A:l[g],d:sa(h),e:h,b:k.shortMonths[i],B:k.months[i],m:sa(i+1),y:j.toString().substr(2,2),Y:j,H:sa(f),I:sa(f%12||12),l:f%12||12,M:sa(d[jb]()),p:f<12?"AM":"PM",P:f<12?"am":"pm",S:sa(d.getSeconds()),L:sa(s(b%1E3),3)};for(e in b)a=a.replace("%"+e,b[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};Eb.prototype={wrapColor:function(a){if(this.color>=a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};B=ka(zb,1,ib,1E3,Ua,6E4,Ja,36E5, -oa,864E5,Va,6048E5,Ka,2592E6,ta,31556952E3);vb={init:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c),h,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length{point.key}
',pointFormat:'{series.name}: {point.y}
',shadow:!0,shared:U,snap:fa?25:10,style:{color:"#333333",fontSize:"12px",padding:"5px",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5}, -style:{cursor:"pointer",color:"#909090",fontSize:"10px"}}};var W=M.plotOptions,ga=W.line;Gb();var qa=function(a){var b=[],c;(function(a){(c=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(a))?b=[w(c[1]),w(c[2]),w(c[3]),parseFloat(c[4],10)]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))&&(b=[w(c[1],16),w(c[2],16),w(c[3],16),1])})(a);return{get:function(c){return b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"? -b[3]:"rgba("+b.join(",")+")":a},brighten:function(a){if(Aa(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=w(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},setOpacity:function(a){b[3]=a;return this}}};va.prototype={init:function(a,b){this.element=b==="span"?Q(b):C.createElementNS(pa,b);this.renderer=a;this.attrSetters={}},animate:function(a,b,c){b=o(b,Oa,!0);eb(this);if(b){b=A(b);if(c)b.complete=c;xb(this,a,b)}else this.attr(a),c&&c()},attr:function(a,b){var c,d,e,f,g=this.element,h=g.nodeName.toLowerCase(), -i=this.renderer,j,k=this.attrSetters,l=this.shadows,m,p,u=this;la(a)&&t(b)&&(c=a,a={},a[c]=b);if(la(a))c=a,h==="circle"?c={x:"cx",y:"cy"}[c]||c:c==="strokeWidth"&&(c="stroke-width"),u=z(g,c)||this[c]||0,c!=="d"&&c!=="visibility"&&(u=parseFloat(u));else for(c in a)if(j=!1,d=a[c],e=k[c]&&k[c].call(this,d,c),e!==!1){e!==x&&(d=e);if(c==="d")d&&d.join&&(d=d.join(" ")),/(NaN| {2}|^$)/.test(d)&&(d="M 0 0");else if(c==="x"&&h==="text"){for(e=0;em&&/[ \-]/.test(b.innerText)&&(H(b,{width:m+"px",display:"block",whiteSpace:"normal"}),k=m);m=a.fontMetrics(b.style.fontSize).b;q=u<0&&-k;D=p<0&&-l;da=u*p<0;q+=p*m*(da?1-h:h);D-=u*m*(j?da?h:1-h:1);i&&(q-=k*h*(u<0?-1:1),j&&(D-=l*h*(p<0?-1:1)),H(b,{textAlign:g}));this.xCorr=q;this.yCorr=D}H(b,{left:e+q+"px",top:f+D+"px"});this.cTT=aa}}else this.alignOnAdd= -!0},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.inverted,d=this.rotation,e=[];c&&(a+=this.attr("width"),b+=this.attr("height"));(a||b)&&e.push("translate("+a+","+b+")");c?e.push("rotate(90) scale(-1,1)"):d&&e.push("rotate("+d+" "+(this.x||0)+" "+(this.y||0)+")");e.length&&z(this.element,"transform",e.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){a?(this.alignOptions=a,this.alignByTranslate=b,c|| -this.renderer.alignedObjects.push(this)):(a=this.alignOptions,b=this.alignByTranslate);var c=o(c,this.renderer),d=a.align,e=a.verticalAlign,f=(c.x||0)+(a.x||0),g=(c.y||0)+(a.y||0),h={};if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=s(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=s(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a= -this.bBox,b=this.renderer,c,d=this.rotation,e=this.element,f=this.styles,g=d*ab;if(!a){if(e.namespaceURI===pa||b.forExport){try{a=e.getBBox?r({},e.getBBox()):{width:e.offsetWidth,height:e.offsetHeight}}catch(h){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG&&(b=a.width,c=a.height,d))a.width=L(c*Y(g))+L(b*V(g)),a.height=L(c*V(g))+L(b*Y(g));if(Ca&&f&&f.fontSize==="11px"&&c===22.700000762939453)a.height=14;this.bBox=a}return a},show:function(){return this.attr({visibility:"visible"})}, -hide:function(){return this.attr({visibility:"hidden"})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=d.childNodes,f=this.element,g=z(f,"zIndex"),h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(g)c.handleZ=!0,g=w(g);if(c.handleZ)for(c=0;cg||!t(g)&&t(b))){d.insertBefore(f,a);h=!0;break}h||d.appendChild(f);this.added=!0;E(this,"add");return this},safeRemoveChild:function(a){var b= -a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d,e;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=null;eb(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(e=0;e/g,'').replace(/<(i|em)>/g,'').replace(/
/g,"").split(//g),d=b.childNodes,e=/style="([^"]+)"/,f=/href="([^"]+)"/,g=z(b,"x"),h=a.styles,i=h&&h.width&&w(h.width),j=h&&h.lineHeight,k,h=d.length,l=[];h--;)b.removeChild(d[h]);i&&!a.added&&this.box.appendChild(b); -c[c.length-1]===""&&c.pop();n(c,function(c,d){var h,da=0,q,c=c.replace(//g,"|||");h=c.split("|||");n(h,function(c){if(c!==""||h.length===1){var m={},o=C.createElementNS(pa,"tspan"),n;e.test(c)&&(n=c.match(e)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),z(o,"style",n));f.test(c)&&(z(o,"onclick",'location.href="'+c.match(f)[1]+'"'),H(o,{cursor:"pointer"}));c=(c.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");o.appendChild(C.createTextNode(c)); -da?m.dx=3:m.x=g;if(!da){if(d){!Z&&a.renderer.forExport&&H(o,{display:"block"});q=K.getComputedStyle&&w(K.getComputedStyle(k,null).getPropertyValue("line-height"));if(!q||isNaN(q)){var F;if(!(F=j))if(!(F=k.offsetHeight))l[d]=b.getBBox?b.getBBox().height:a.renderer.fontMetrics(b.style.fontSize).h,F=s(l[d]-(l[d-1]||0))||18;q=F}z(o,"dy",q)}k=o}z(o,m);b.appendChild(o);da++;if(i)for(var c=c.replace(/([^\^])-/g,"$1- ").split(" "),t=[];c.length||t.length;)delete a.bBox,F=a.getBBox().width,m=F>i,!m||c.length=== -1?(c=t,t=[],c.length&&(o=C.createElementNS(pa,"tspan"),z(o,{dy:j||16,x:g}),n&&z(o,"style",n),b.appendChild(o),F>i&&(i=F))):(o.removeChild(o.firstChild),t.unshift(c.pop())),c.length&&o.appendChild(C.createTextNode(c.join(" ").replace(/- /g,"-")))}})})},button:function(a,b,c,d,e,f,g){var h=this.label(a,b,c),i=0,j,k,l,m,p,a={x1:0,y1:0,x2:0,y2:1},e=A(ka("stroke-width",1,"stroke","#999","fill",ka("linearGradient",a,"stops",[[0,"#FFF"],[1,"#DDD"]]),"r",3,"padding",3,"style",ka("color","black")),e);l=e.style; -delete e.style;f=A(e,ka("stroke","#68A","fill",ka("linearGradient",a,"stops",[[0,"#FFF"],[1,"#ACF"]])),f);m=f.style;delete f.style;g=A(e,ka("stroke","#68A","fill",ka("linearGradient",a,"stops",[[0,"#9BD"],[1,"#CDF"]])),g);p=g.style;delete g.style;I(h.element,"mouseenter",function(){h.attr(f).css(m)});I(h.element,"mouseleave",function(){j=[e,f,g][i];k=[l,m,p][i];h.attr(j).css(k)});h.setState=function(a){(i=a)?a===2&&h.attr(g).css(p):h.attr(e).css(l)};return h.on("click",function(){d.call(h)}).attr(e).css(r({cursor:"default"}, -l))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=s(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=s(a[2])+b%2/2);return a},path:function(a){var b={fill:R};Ha(a)?b.d=a:X(a)&&r(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=X(a)?a:{x:a,y:b,r:c};return this.createElement("circle").attr(a)},arc:function(a,b,c,d,e,f){if(X(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;return this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0})},rect:function(a,b,c,d,e,f){e=X(a)? -a.r:e;e=this.createElement("rect").attr({rx:e,ry:e,fill:R});return e.attr(X(a)?a:e.crisp(f,a,b,y(c,0),y(d,0)))},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[o(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return t(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:R};arguments.length>1&&r(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f); -f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(s(b),s(c),d,e,f),i=/^url\((.*?)\)$/,j,k;h?(g=this.path(h),r(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&r(g,f)):i.test(a)&&(k=function(a,b){a.attr({width:b[0],height:b[1]});a.alignByTranslate||a.translate(-s(b[0]/2),-s(b[1]/2))},j=a.match(i)[1],a=Jb[j],g=this.image(j).attr({x:b,y:c}),a?k(g,a):(g.attr({width:0, -height:0}),Q("img",{onload:function(){k(g,Jb[j]=[this.width,this.height])},src:j})));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+ -c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-1.0E-6,d=e.innerR,h=e.open,i=V(f),j=Y(f),k=V(g),g=Y(g),e=e.end-f');if(b)c=b===ia||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=Q(c);this.renderer=a;this.attrSetters={}},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();E(this,"add"); -return this},updateTransform:va.prototype.htmlUpdateTransform,attr:function(a,b){var c,d,e,f=this.element||{},g=f.style,h=f.nodeName,i=this.renderer,j=this.symbolName,k,l=this.shadows,m,p=this.attrSetters,u=this;la(a)&&t(b)&&(c=a,a={},a[c]=b);if(la(a))c=a,u=c==="strokeWidth"||c==="stroke-width"?this.strokeweight:this[c];else for(c in a)if(d=a[c],m=!1,e=p[c]&&p[c].call(this,d,c),e!==!1&&d!==null){e!==x&&(d=e);if(j&&/^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(c))k||(this.symbolAttr(a), -k=!0),m=!0;else if(c==="d"){d=d||[];this.d=d.join(" ");e=d.length;for(m=[];e--;)m[e]=Aa(d[e])?s(d[e]*10)-5:d[e]==="Z"?"x":d[e];d=m.join(" ")||"x";f.path=d;if(l)for(e=l.length;e--;)l[e].path=l[e].cutOff?this.cutOffPath(d,l[e].cutOff):d;m=!0}else if(c==="visibility"){if(l)for(e=l.length;e--;)l[e].style[c]=d;h==="DIV"&&(d=d==="hidden"?"-999em":0,c="top");g[c]=d;m=!0}else if(c==="zIndex")d&&(g[c]=d),m=!0;else if(c==="width"||c==="height")d=y(0,d),this[c]=d,this.updateClipping?(this[c]=d,this.updateClipping()): -g[c]=d,m=!0;else if(c==="x"||c==="y")this[c]=d,g[{x:"left",y:"top"}[c]]=d;else if(c==="class")f.className=d;else if(c==="stroke")d=i.color(d,f,c),c="strokecolor";else if(c==="stroke-width"||c==="strokeWidth")f.stroked=d?!0:!1,c="strokeweight",this[c]=d,Aa(d)&&(d+="px");else if(c==="dashstyle")(f.getElementsByTagName("stroke")[0]||Q(i.prepVML([""]),null,null,f))[c]=d||"solid",this.dashstyle=d,m=!0;else if(c==="fill")h==="SPAN"?g.color=d:(f.filled=d!==R?!0:!1,d=i.color(d,f,c,this),c="fillcolor"); -else if(h==="shape"&&c==="rotation")this[c]=d,f.style.left=-s(Y(d*ab)+1)+"px",f.style.top=s(V(d*ab))+"px";else if(c==="translateX"||c==="translateY"||c==="rotation")this[c]=d,this.updateTransform(),m=!0;else if(c==="text")this.bBox=null,f.innerHTML=d,m=!0;m||(Qa?f[c]=d:z(f,c,d))}return u},clip:function(a){var b=this,c,d=b.element,e=d.parentNode;a?(c=a.members,c.push(b),b.destroyClip=function(){Ba(c,b)},e&&e.className==="highcharts-tracker"&&!Qa&&H(d,{visibility:"hidden"}),a=a.getCSS(b)):(b.destroyClip&& -b.destroyClip(),a={clip:Qa?"inherit":"rect(auto)"});return b.css(a)},css:va.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Na(a)},destroy:function(){this.destroyClip&&this.destroyClip();return va.prototype.destroy.apply(this)},empty:function(){for(var a=this.element.childNodes,b=a.length,c;b--;)c=a[b],c.parentNode.removeChild(c)},on:function(a,b){this.element["on"+a]=function(){var a=K.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length; -if(c===9||c===11)a[c-4]=a[c-2]=w(a[c-2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,p,u;k&&typeof k.value!=="string"&&(k="x");m=k;if(a){p=o(a.width,3);u=(a.opacity||0.15)/p;for(e=1;e<=3;e++){l=p*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=[''];h=Q(g.prepVML(j),null,{left:w(i.left)+o(a.offsetX,1),top:w(i.top)+ -o(a.offsetY,1)});if(c)h.cutOff=l+1;j=[''];Q(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this}};ja=ca(va,ja);var ha={Element:ja,isIE8:Fa.indexOf("MSIE 8.0")>-1,init:function(a,b,c){var d,e;this.alignedObjects=[];d=this.createElement(ia);e=d.element;e.style.position="relative";a.appendChild(d.element);this.box=e;this.boxWrapper=d;this.setSize(b,c,!1);if(!C.namespaces.hcv)C.namespaces.add("hcv", -"urn:schemas-microsoft-com:vml"),C.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=X(a);return r(e,{members:[],left:f?a.x:a,top:f?a.y:b,width:f?a.width:c,height:f?a.height:d,getCSS:function(a){var b=a.inverted,c=this.top,d=this.left,e=d+this.width,f=c+this.height,c={clip:"rect("+s(b?d:c)+"px,"+s(b?f:e)+"px,"+ -s(b?e:f)+"px,"+s(b?c:d)+"px)"};!b&&Qa&&a.element.nodeName!=="IMG"&&r(c,{width:e+"px",height:f+"px"});return c},updateClipping:function(){n(e.members,function(a){a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=R;a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,m=a.linearGradient||a.radialGradient,p,u,o,q,D,t="",a=a.stops,s,v=[],F=function(){h=['']; -Q(e.prepVML(h),null,null,b)};p=a[0];s=a[a.length-1];p[0]>0&&a.unshift([0,p[1]]);s[0]<1&&a.push([1,s[1]]);n(a,function(a,b){g.test(a[1])?(f=qa(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);v.push(a[0]*100+"% "+k);b?(o=l,q=k):(u=l,D=k)});if(c==="fill")if(i==="gradient")c=m.x1||m[0]||0,a=m.y1||m[1]||0,p=m.x2||m[2]||0,m=m.y2||m[3]||0,t='angle="'+(90-J.atan((m-a)/(p-c))*180/xa)+'"',F();else{var j=m.r,r=j*2,P=j*2,y=m.cx,x=m.cy,A=b.radialReference,w,j=function(){A&&(w=d.getBBox(),y+=(A[0]-w.x)/w.width- -0.5,x+=(A[1]-w.y)/w.height-0.5,r*=A[2]/w.width,P*=A[2]/w.height);t='src="'+M.global.VMLRadialGradientURL+'" size="'+r+","+P+'" origin="0.5,0.5" position="'+y+","+x+'" color2="'+D+'" ';F()};d.added?j():I(d,"add",j);j=q}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=qa(a),h=["<",c,' opacity="',f.get("a"),'"/>'],Q(this.prepVML(h),null,null,b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1;j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>", -' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","1&&f.attr({x:b,y:c,width:d,height:e});return f},rect:function(a,b,c,d,e,f){if(X(a))b=a.y,c=a.width,d=a.height,f=a.strokeWidth,a=a.x;var g=this.symbol("rect");g.r=e;return g.attr(g.crisp(f,a,b,y(c,0),y(d,0)))},invertChild:function(a,b){var c=b.style;H(a,{flip:"x",left:w(c.width)-1,top:w(c.height)-1,rotation:-90})}, -symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=V(f),d=Y(f),i=V(g),j=Y(g),k=e.innerR,l=0.08/h,m=k&&0.1/k||0;if(g-f===0)return["x"];else 2*xa-g+fj&&(c=!1)):h+k>m&&(h=m-k,d&&h+l0&&b.height>0){f=A({align:c&&k&&"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g)a.label=g=r.text(f.text,0,0).attr({align:f.textAlign||f.align,rotation:f.rotation,zIndex:D}).css(f.style).add();b=[u[1],u[4],o(u[6],u[1])];u=[u[2],u[5],o(u[7],u[2])];c=Ma(b);k=Ma(u);g.align(f,!1,{x:c,y:k,width:Da(b)-c,height:Da(u)-k});g.show()}else g&&g.hide();return a},destroy:function(){Ba(this.axis.plotLinesAndBands,this);Ea(this,this.axis)}}; -Hb.prototype={destroy:function(){Ea(this,this.axis)},setTotal:function(a){this.cum=this.total=a},render:function(a){var b=this.options.formatter.call(this);this.label?this.label.attr({text:b,visibility:"hidden"}):this.label=this.axis.chart.renderer.text(b,0,0).css(this.options.style).attr({align:this.textAlign,rotation:this.options.rotation,visibility:"hidden"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,g=c.translate(this.percent?100:this.total,0,0,0, -1),c=c.translate(0),c=L(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e.attr({visibility:this.options.crop===!1||d.isInsidePlot(f.x,f.y)?Z?"inherit":"visible":"hidden"})}};ob.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0", -labels:G,lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:5,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#6D869F",fontWeight:"bold"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0, -labels:{align:"right",x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Y-values"},stackLabels:{enabled:!1,formatter:function(){return this.total},style:G.style}},defaultLeftAxisOptions:{labels:{align:"right",x:-8,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{align:"left",x:8,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{align:"center",x:0,y:14},title:{rotation:0}},defaultTopAxisOptions:{labels:{align:"center", -x:0,y:-5},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.xOrY=(this.isXAxis=c)?"x":"y";this.opposite=b.opposite;this.side=this.horiz?this.opposite?0:2:this.opposite?1:3;this.setOptions(b);var d=this.options,e=d.type,f=e==="datetime";this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.staggerLines=this.horiz&&d.labels.staggerLines;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.categories=d.categories;this.isLog= -e==="logarithmic";this.isLinked=t(d.linkedTo);this.isDatetimeAxis=f;this.tickmarkOffset=d.categories&&d.tickmarkPlacement==="between"?0.5:0;this.ticks={};this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.min=this.max=null;var g,d=this.options.events;a.axes.push(this);a[c?"xAxis":"yAxis"].push(this);this.series=[];if(a.inverted&&c&&this.reversed===x)this.reversed= -!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;this.addPlotLine=this.addPlotBand=this.addPlotBandOrLine;for(g in d)I(this,g,d[g]);if(this.isLog)this.val2lin=ma,this.lin2val=ba},setOptions:function(a){this.options=A(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],A(M[this.isXAxis?"xAxis":"yAxis"],a))},defaultLabelFormatter:function(){var a= -this.axis,b=this.value,c=this.dateTimeLabelFormat,d=M.lang.numericSymbols,e=d&&d.length,f,g=a.isLog?b:a.tickInterval;if(a.categories)f=b;else if(c)f=cb(c,b);else if(e&&g>=1E3)for(;e--&&f===x;)a=Math.pow(1E3,e+1),g>=a&&d[e]!==null&&(f=Ia(b/a,-1)+d[e]);f===x&&(f=b>=1E3?Ia(b,0):Ia(b,-1));return f},getSeriesExtremes:function(){var a=this,b=a.chart,c=a.stacks,d=[],e=[],f;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=null;n(a.series,function(g){if(g.visible||!b.options.chart.ignoreHiddenSeries){var h=g.options, -i,j,k,l,m,p,u,n,q,D=h.threshold,s,r=[],v=0;a.hasVisibleSeries=!0;if(a.isLog&&D<=0)D=h.threshold=null;if(a.isXAxis){if(h=g.xData,h.length)a.dataMin=O(o(a.dataMin,h[0]),Ma(h)),a.dataMax=y(o(a.dataMax,h[0]),Da(h))}else{var F,w,P,A=g.cropped,z=g.xAxis.getExtremes(),C=!!g.modifyValue;i=h.stacking;a.usePercentage=i==="percent";if(i)m=h.stack,l=g.type+o(m,""),p="-"+l,g.stackKey=l,j=d[l]||[],d[l]=j,k=e[p]||[],e[p]=k;if(a.usePercentage)a.dataMin=0,a.dataMax=99;h=g.processedXData;u=g.processedYData;s=u.length; -for(f=0;f=z.min&&(h[f-1]||n)<=z.max))if(n=q.length)for(;n--;)q[n]!==null&&(r[v++]=q[n]);else r[v++]=q;if(!a.usePercentage&&r.length)a.dataMin=O(o(a.dataMin,r[0]),Ma(r)),a.dataMax=y(o(a.dataMax,r[0]),Da(r));if(t(D))if(a.dataMin>=D)a.dataMin=D,a.ignoreMinPadding=!0;else if(a.dataMax< -D)a.dataMax=D,a.ignoreMaxPadding=!0}}})},translate:function(a,b,c,d,e,f){var g=this.len,h=1,i=0,j=d?this.oldTransA:this.transA,d=d?this.oldMin:this.min,e=this.options.ordinal||this.isLog&&e;if(!j)j=this.transA;c&&(h*=-1,i=g);this.reversed&&(h*=-1,i-=h*g);b?(this.reversed&&(a=g-a),a=a/j+d,e&&(a=this.lin2val(a))):(e&&(a=this.val2lin(a)),a=h*(a-d)*j+i+h*this.minPixelPadding+(f?j*this.pointRange/2:0));return a},getPlotLinePath:function(a,b,c){var d=this.chart,e=this.left,f=this.top,g,h,i,a=this.translate(a, -null,null,c),j=c&&d.oldChartHeight||d.chartHeight,k=c&&d.oldChartWidth||d.chartWidth,l;g=this.transB;c=h=s(a+g);g=i=s(j-a-g);if(isNaN(a))l=!0;else if(this.horiz){if(g=f,i=j-this.bottom,ce+this.width)l=!0}else if(c=e,h=k-this.right,gf+this.height)l=!0;return l?null:d.renderer.crispLine(["M",c,g,"L",h,i],b||0)},getPlotBandPath:function(a,b){var c=this.getPlotLinePath(b),d=this.getPlotLinePath(a);d&&c?d.push(c[4],c[5],c[1],c[2]):d=null;return d},getLinearTickPositions:function(a,b,c){for(var d, -b=ea(T(b/a)*a),c=ea(wa(c/a)*a),e=[];b<=c;){e.push(b);b=ea(b+a);if(b===d)break;d=b}return e},getLogTickPositions:function(a,b,c,d){var e=this.options,f=this.len,g=[];if(!d)this._minorAutoInterval=null;if(a>=0.5)a=s(a),g=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=T(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];fb&&g.push(k),k>c&&(l=!0),k=j}else if(b=ba(b),c=ba(c),a=e[d?"minorTickInterval":"tickInterval"], -a=o(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=hb(a,null,J.pow(10,T(J.log(a)/J.LN10))),g=Sa(this.getLinearTickPositions(a,b,c),ma),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return g},getMinorTickPositions:function(){var a=this.tickPositions,b=this.minorTickInterval,c=[],d,e;if(this.isLog){e=a.length;for(d=1;d=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===x&&!this.isLog)t(a.min)||t(a.max)?this.minRange=null:(n(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]-i[g-1],f===x||h0||!b.ignoreMaxPadding))b.max+=c*j}b.tickInterval=b.min===b.max||b.min===void 0||b.max===void 0?1:h&&!l&&p===b.linkedParent.options.tickPixelInterval?b.linkedParent.tickInterval:o(l,u?1:(b.max-b.min)*p/(b.len||1));g&&!a&&n(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(a);b.beforeSetTickPositions&&b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval); -if(!l&&b.tickIntervale&&i.shift(),d.endOnTick?b.max=f:b.max+hb[d]&&this.options.alignTicks!==!1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this.xOrY,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&& -!this.categories&&!this.isLinked&&this.options.alignTicks!==!1){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(ea||a===null?a=c:b=a.min&&b<=a.max)j[b]||(j[b]=new Pa(a,b)),D&&j[b].isNew&&j[b].render(c,!0),j[b].isActive=!0,j[b].render(c)}),p&&n(g,function(b,c){if(c%2===0&&b1||L(b-f.y)>1))clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(){if(!this.isHidden){var a=this.chart.hoverPoints;this.label.hide();a&&n(a,function(a){a.setState()});this.chart.hoverPoints=null;this.isHidden=!0}},hideCrosshairs:function(){n(this.crosshairs,function(a){a&&a.hide()})},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f= -0,g=0,h,a=na(a);c=a[0].tooltipPos;c||(n(a,function(a){h=a.series.yAxis;f+=a.plotX;g+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&h?h.top-d.plotTop:0)}),f/=a.length,g/=a.length,c=[e?d.plotWidth-g:f,this.shared&&!e&&a.length>1&&b?b.chartY-d.plotTop:e?d.plotHeight-f:g]);return Sa(c,s)},getPosition:function(a,b,c){var d=this.chart,e=d.plotLeft,f=d.plotTop,g=d.plotWidth,h=d.plotHeight,i=o(this.options.distance,12),j=c.plotX,c=c.plotY,d=j+e+(d.inverted?i:-a-i),k=c-b+f+15,l;d<7&&(d=e+y(j,0)+i);d+a> -e+g&&(d-=d+a-(e+g),k=c-b+f-i,l=!0);k=k&&c<=k+b&&(k=c+f+i));k+b>f+h&&(k=y(f,f+h-b-i));return{x:d,y:k}},refresh:function(a,b){function c(){var a=this.points||na(this),b=a[0].series,c;c=[b.tooltipHeaderFormatter(a[0].key)];n(a,function(a){b=a.series;c.push(b.tooltipFormatter&&b.tooltipFormatter(a)||a.point.tooltipFormatter(b.tooltipOptions.pointFormat))});c.push(f.footerFormat||"");return c.join("")}var d=this.chart,e=this.label,f=this.options,g,h,i,j={},k,l=[];k=f.formatter||c;var j= -d.hoverPoints,m,p=f.crosshairs;i=this.shared;h=this.getAnchor(a,b);g=h[0];h=h[1];i&&(!a.series||!a.series.noSharedTooltip)?(d.hoverPoints=a,j&&n(j,function(a){a.setState()}),n(a,function(a){a.setState("hover");l.push(a.getLabelConfig())}),j={x:a[0].category,y:a[0].y},j.points=l,a=a[0]):j=a.getLabelConfig();k=k.call(j);j=a.series;i=i||!j.isCartesian||j.tooltipOutsidePlot||d.isInsidePlot(g,h);k===!1||!i?this.hide():(this.isHidden&&e.show(),e.attr({text:k}),m=f.borderColor||a.color||j.color||"#606060", -e.attr({stroke:m}),e=(f.positioner||this.getPosition).call(this,e.width,e.height,{plotX:g,plotY:h}),this.move(s(e.x),s(e.y),g+d.plotLeft,h+d.plotTop),this.isHidden=!1);if(p){p=na(p);for(e=p.length;e--;)if(i=a.series[e?"yAxis":"xAxis"],p[e]&&i)if(i=i.getPlotLinePath(e?o(a.stackY,a.y):a.x,1),this.crosshairs[e])this.crosshairs[e].attr({d:i,visibility:"visible"});else{j={"stroke-width":p[e].width||1,stroke:p[e].color||"#C0C0C0",zIndex:p[e].zIndex||2};if(p[e].dashStyle)j.dashstyle=p[e].dashStyle;this.crosshairs[e]= -d.renderer.path(i).attr(j).add()}}E(d,"tooltipRefresh",{text:k,x:g+d.plotLeft,y:h+d.plotTop,borderColor:m})}};qb.prototype={normalizeMouseEvent:function(a){var b,c,d,a=a||K.event;if(!a.target)a.target=a.srcElement;a=Lb(a);d=a.touches?a.touches.item(0):a;this.chartPosition=b=Sb(this.chart.container);d.pageX===x?(c=a.x,b=a.y):(c=d.pageX-b.left,b=d.pageY-b.top);return r(a,{chartX:s(c),chartY:s(b)})},getMouseCoordinates:function(a){var b={xAxis:[],yAxis:[]},c=this.chart;n(c.axes,function(d){var e=d.isXAxis; -b[e?"xAxis":"yAxis"].push({axis:d,value:d.translate(((c.inverted?!e:e)?a.chartX-c.plotLeft:d.top+d.len-a.chartY)-d.minPixelPadding,!0)})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},onmousemove:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f=b.hoverPoint,g=b.hoverSeries,h,i,j=b.chartWidth,k=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!g||!g.noSharedTooltip)){e=[];h=c.length;for(i=0;ij&&e.splice(h,1);if(e.length&&e[0].plotX!==this.hoverX)d.refresh(e,a),this.hoverX=e[0].plotX}if(g&&g.tracker&&(b=g.tooltipPoints[k])&&b!==f)b.onMouseOver()},resetTracker:function(a){var b=this.chart,c=b.hoverSeries,d=b.hoverPoint,e=b.tooltip,b=e&&e.shared?b.hoverPoints:d;(a=a&&e&&b)&& -na(b)[0].plotX===x&&(a=!1);if(a)e.refresh(b);else{if(d)d.onMouseOut();if(c)c.onMouseOut();e&&(e.hide(),e.hideCrosshairs());this.hoverX=null}},setDOMEvents:function(){function a(){if(b.selectionMarker){var f={xAxis:[],yAxis:[]},g=b.selectionMarker.getBBox(),h=g.x-c.plotLeft,l=g.y-c.plotTop,m;e&&(n(c.axes,function(a){if(a.options.zoomEnabled!==!1){var b=a.isXAxis,d=c.inverted?!b:b,e=a.translate(d?h:c.plotHeight-l-g.height,!0,0,0,1),d=a.translate((d?h+g.width:c.plotHeight-l)-2*a.minPixelPadding,!0,0, -0,1);!isNaN(e)&&!isNaN(d)&&(f[b?"xAxis":"yAxis"].push({axis:a,min:O(e,d),max:y(e,d)}),m=!0)}}),m&&E(c,"selection",f,function(a){c.zoom(a)}));b.selectionMarker=b.selectionMarker.destroy()}if(c)H(d,{cursor:"auto"}),c.cancelClick=e,c.mouseIsDown=e=!1;S(C,fa?"touchend":"mouseup",a)}var b=this,c=b.chart,d=c.container,e,f=b.zoomX&&!c.inverted||b.zoomY&&c.inverted,g=b.zoomY&&!c.inverted||b.zoomX&&c.inverted;b.hideTooltipOnMouseMove=function(a){a=Lb(a);b.chartPosition&&c.hoverSeries&&c.hoverSeries.isCartesian&& -!c.isInsidePlot(a.pageX-b.chartPosition.left-c.plotLeft,a.pageY-b.chartPosition.top-c.plotTop)&&b.resetTracker()};b.hideTooltipOnMouseLeave=function(){b.resetTracker();b.chartPosition=null};d.onmousedown=function(d){d=b.normalizeMouseEvent(d);!fa&&d.preventDefault&&d.preventDefault();c.mouseIsDown=!0;c.cancelClick=!1;c.mouseDownX=b.mouseDownX=d.chartX;b.mouseDownY=d.chartY;I(C,fa?"touchend":"mouseup",a)};var h=function(a){if(!a||!(a.touches&&a.touches.length>1)){a=b.normalizeMouseEvent(a);if(!fa)a.returnValue= -!1;var d=a.chartX,h=a.chartY,l=!c.isInsidePlot(d-c.plotLeft,h-c.plotTop);fa&&a.type==="touchstart"&&(z(a.target,"isTracker")?c.runTrackerClick||a.preventDefault():!c.runChartClick&&!l&&a.preventDefault());if(l)dc.plotLeft+c.plotWidth&&(d=c.plotLeft+c.plotWidth),hc.plotTop+c.plotHeight&&(h=c.plotTop+c.plotHeight);if(c.mouseIsDown&&a.type!=="touchstart"&&(e=Math.sqrt(Math.pow(b.mouseDownX-d,2)+Math.pow(b.mouseDownY-h,2)),e>10)){var m=c.isInsidePlot(b.mouseDownX- -c.plotLeft,b.mouseDownY-c.plotTop);if(c.hasCartesianSeries&&(b.zoomX||b.zoomY)&&m&&!b.selectionMarker)b.selectionMarker=c.renderer.rect(c.plotLeft,c.plotTop,f?1:c.plotWidth,g?1:c.plotHeight,0).attr({fill:b.options.chart.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();if(b.selectionMarker&&f){var p=d-b.mouseDownX;b.selectionMarker.attr({width:L(p),x:(p>0?0:p)+b.mouseDownX})}b.selectionMarker&&g&&(h-=b.mouseDownY,b.selectionMarker.attr({height:L(h),y:(h>0?0:h)+b.mouseDownY}));m&&!b.selectionMarker&& -b.options.chart.panning&&c.pan(d)}if(!l)b.onmousemove(a);return l||!c.hasCartesianSeries}};d.onmousemove=h;I(d,"mouseleave",b.hideTooltipOnMouseLeave);I(C,"mousemove",b.hideTooltipOnMouseMove);d.ontouchstart=function(a){if(b.zoomX||b.zoomY)d.onmousedown(a);h(a)};d.ontouchmove=h;d.ontouchend=function(){e&&b.resetTracker()};d.onclick=function(a){var d=c.hoverPoint,e,f,a=b.normalizeMouseEvent(a);a.cancelBubble=!0;if(!c.cancelClick)d&&(z(a.target,"isTracker")||z(a.target.parentNode,"isTracker"))?(e=d.plotX, -f=d.plotY,r(d,{pageX:b.chartPosition.left+c.plotLeft+(c.inverted?c.plotWidth-f:e),pageY:b.chartPosition.top+c.plotTop+(c.inverted?c.plotHeight-e:f)}),E(d.series,"click",r(a,{point:d})),d.firePointEvent("click",a)):(r(a,b.getMouseCoordinates(a)),c.isInsidePlot(a.chartX-c.plotLeft,a.chartY-c.plotTop)&&E(c,"click",a))}},destroy:function(){var a=this.chart,b=a.container;if(a.trackerGroup)a.trackerGroup=a.trackerGroup.destroy();S(b,"mouseleave",this.hideTooltipOnMouseLeave);S(C,"mousemove",this.hideTooltipOnMouseMove); -b.onclick=b.onmousedown=b.onmousemove=b.ontouchstart=b.ontouchend=b.ontouchmove=null;clearInterval(this.tooltipTimeout)},init:function(a,b){if(!a.trackerGroup)a.trackerGroup=a.renderer.g("tracker").attr({zIndex:9}).add();if(b.enabled)a.tooltip=new pb(a,b);this.setDOMEvents()}};rb.prototype={init:function(a){var b=this,c=b.options=a.options.legend;if(c.enabled){var d=c.itemStyle,e=o(c.padding,8),f=c.itemMarginTop||0;b.baseline=w(d.fontSize)+3+f;b.itemStyle=d;b.itemHiddenStyle=A(d,c.itemHiddenStyle); -b.itemMarginTop=f;b.padding=e;b.initialItemX=e;b.initialItemY=e-5;b.maxItemWidth=0;b.chart=a;b.itemHeight=0;b.lastLineHeight=0;b.render();I(b.chart,"endResize",function(){b.positionCheckboxes()})}},colorizeItem:function(a,b){var c=this.options,d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.color:g,g=a.options&&a.options.marker,i={stroke:h,fill:h},j;d&&d.css({fill:c});e&&e.attr({stroke:h});if(f){if(g)for(j in g=a.convertAttribs(g),g)d=g[j], -d!==x&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;n(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&a[b].destroy()});b&&Na(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(){var a= -this;n(a.allItems,function(b){var c=b.checkbox,d=a.group.alignAttr;c&&H(c,{left:d.translateX+b.legendItemWidth+c.x-20+"px",top:d.translateY+c.y+3+"px"})})},renderItem:function(a){var u;var b=this,c=b.chart,d=c.renderer,e=b.options,f=e.layout==="horizontal",g=e.symbolWidth,h=e.symbolPadding,i=b.itemStyle,j=b.itemHiddenStyle,k=b.padding,l=!e.rtl,m=e.width,p=e.itemMarginBottom||0,o=b.itemMarginTop,n=b.initialItemX,q=a.legendItem,t=a.series||a,r=t.options,s=r.showCheckbox;if(!q&&(a.legendGroup=d.g("legend-item").attr({zIndex:1}).add(b.scrollGroup), -t.drawLegendSymbol(b,a),a.legendItem=q=d.text(e.labelFormatter.call(a),l?g+h:-h,b.baseline,e.useHTML).css(A(a.visible?i:j)).attr({align:l?"left":"right",zIndex:2}).add(a.legendGroup),a.legendGroup.on("mouseover",function(){a.setState("hover");q.css(b.options.itemHoverStyle)}).on("mouseout",function(){q.css(a.visible?i:j);a.setState()}).on("click",function(b){var c=function(){a.setVisible()},b={browserEvent:b};a.firePointEvent?a.firePointEvent("legendItemClick",b,c):E(a,"legendItemClick",b,c)}),b.colorizeItem(a, -a.visible),r&&s))a.checkbox=Q("input",{type:"checkbox",checked:a.selected,defaultChecked:a.selected},e.itemCheckboxStyle,c.container),I(a.checkbox,"click",function(b){E(a,"checkboxClick",{checked:b.target.checked},function(){a.select()})});d=q.getBBox();u=a.legendItemWidth=e.itemWidth||g+h+d.width+k+(s?20:0),e=u;b.itemHeight=g=d.height;if(f&&b.itemX-n+e>(m||c.chartWidth-2*k-n))b.itemX=n,b.itemY+=o+b.lastLineHeight+p,b.lastLineHeight=0;b.maxItemWidth=y(b.maxItemWidth,e);b.lastItemY=o+b.itemY+p;b.lastLineHeight= -y(g,b.lastLineHeight);a._legendItemPos=[b.itemX,b.itemY];f?b.itemX+=e:(b.itemY+=o+g+p,b.lastLineHeight=g);b.offsetWidth=m||y(f?b.itemX-n:e,b.offsetWidth)},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup), -a.clipRect=c.clipRect(0,0,9999,b.chartHeight),a.contentGroup.clip(a.clipRect);e=[];n(b.series,function(a){var b=a.options;b.showInLegend&&(e=e.concat(a.legendItems||(b.legendType==="point"?a.data:a)))});Fb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;n(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight;h=a.handleOverflow(h);if(l||m){g+=k;h+=k;if(i){if(g> -0&&h>0)i[i.isNew?"attr":"animate"](i.crisp(null,null,null,g,h)),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,"stroke-width":l||0,fill:m||R}).add(d).shadow(j.shadow),i.isNew=!0;i[f?"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;n(e,function(b){a.positionItem(b)});f&&d.align(r({width:g,height:h},j),!0,b.spacingBox);b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+ -(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h=this.clipRect,i=e.navigation,j=o(i.animation,!0),k=i.arrowSize||12,l=this.nav;e.layout==="horizontal"&&(f/=2);g&&(f=O(f,g));if(a>f){this.clipHeight=c=f-20;this.pageCount=wa(a/c);this.currentPage=o(this.currentPage,1);this.fullHeight=a;h.attr({height:c});if(!l)this.nav=l=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,k,k).on("click",function(){b.scroll(-1,j)}).add(l),this.pager=d.text("",15,10).css(i.style).add(l), -this.down=d.symbol("triangle-down",0,0,k,k).on("click",function(){b.scroll(1,j)}).add(l);b.scroll(0);a=f}else l&&(h.attr({height:c.chartHeight}),l.hide(),this.scrollGroup.attr({translateY:1}));return a},scroll:function(a,b){var c=this.pageCount,d=this.currentPage+a,e=this.clipHeight,f=this.options.navigation,g=f.activeColor,f=f.inactiveColor,h=this.pager,i=this.padding;d>c&&(d=c);if(d>0)b!==x&&ua(b,this.chart),this.nav.attr({translateX:i,translateY:e+7,visibility:"visible"}),this.up.attr({fill:d=== -1?f:g}).css({cursor:d===1?"default":"pointer"}),h.attr({text:d+"/"+this.pageCount}),this.down.attr({x:18+this.pager.getBBox().width,fill:d===c?f:g}).css({cursor:d===c?"default":"pointer"}),this.scrollGroup.animate({translateY:-O(e*(d-1),this.fullHeight-e+i)+1}),h.attr({text:d+"/"+c}),this.currentPage=d}};sb.prototype={initSeries:function(a){var b=this.options.chart,b=new $[a.type||b.type||b.defaultSeriesType];b.init(this,a);return b},addSeries:function(a,b,c){var d,e=this;a&&(ua(c,e),b=o(b,!0),E(e, -"addSeries",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;b&&e.redraw()}));return d},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&n(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.tracker,e=this.legend,f=this.isDirtyLegend,g,h=this.isDirtyBox,i=c.length,j=i,k=this.renderer,l=k.isHidden(), -m=[];ua(a,this);for(l&&this.cloneRenderTo();j--;)if(a=c[j],a.isDirty&&a.options.stacking){g=!0;break}if(g)for(j=i;j--;)if(a=c[j],a.options.stacking)a.isDirty=!0;n(c,function(a){a.isDirty&&a.options.legendType==="point"&&(f=!0)});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;if(this.hasCartesianSeries){if(!this.isResizing)this.maxTicks=null,n(b,function(a){a.setScale()});this.adjustTickAmounts();this.getMargins();n(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,m.push(function(){E(a, -"afterSetExtremes",a.getExtremes())});if(a.isDirty||h||g)a.redraw(),h=!0})}h&&this.drawChartBox();n(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.resetTracker&&d.resetTracker(!0);k.draw();E(this,"redraw");l&&this.cloneRenderTo(!0);n(m,function(a){a.call()})},showLoading:function(a){var b=this.options,c=this.loadingDiv,d=b.loading;if(!c)this.loadingDiv=c=Q(ia,{className:"highcharts-loading"},r(d.style,{left:this.plotLeft+"px",top:this.plotTop+"px",width:this.plotWidth+ -"px",height:this.plotHeight+"px",zIndex:10,display:R}),this.container),this.loadingSpan=Q("span",null,d.labelStyle,c);this.loadingSpan.innerHTML=a||b.lang.loading;if(!this.loadingShown)H(c,{opacity:0,display:""}),xb(c,{opacity:d.style.opacity},{duration:d.showDuration||0}),this.loadingShown=!0},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&xb(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){H(b,{display:R})}});this.loadingShown=!1},get:function(a){var b=this.axes, -c=this.series,d,e;for(d=0;dO(e.dataMin,e.min)&&c19?this.containerHeight: -400)},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Na(b),delete this.renderToClone):(c&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),H(b,{position:"absolute",top:"-9999px",display:"block"}),C.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+ub++;if(la(a))this.renderTo=a=C.getElementById(a);a||$a(13,!0);a.innerHTML=""; -a.offsetWidth||this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=Q(ia,{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},r({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0},b.style),this.renderToClone||a);this.renderer=b.forExport?new ra(a,c,d,!0):new Ra(a,c,d);U&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.options.chart,b=a.spacingTop,c=a.spacingRight, -d=a.spacingBottom,a=a.spacingLeft,e,f=this.legend,g=this.optionsMarginTop,h=this.optionsMarginLeft,i=this.optionsMarginRight,j=this.optionsMarginBottom,k=this.chartTitleOptions,l=this.chartSubtitleOptions,m=this.options.legend,p=o(m.margin,10),u=m.x,r=m.y,q=m.align,s=m.verticalAlign;this.resetMargins();e=this.axisOffset;if((this.title||this.subtitle)&&!t(this.optionsMarginTop))if(l=y(this.title&&!k.floating&&!k.verticalAlign&&k.y||0,this.subtitle&&!l.floating&&!l.verticalAlign&&l.y||0))this.plotTop= -y(this.plotTop,l+o(k.margin,15)+b);if(f.display&&!m.floating)if(q==="right"){if(!t(i))this.marginRight=y(this.marginRight,f.legendWidth-u+p+c)}else if(q==="left"){if(!t(h))this.plotLeft=y(this.plotLeft,f.legendWidth+u+p+a)}else if(s==="top"){if(!t(g))this.plotTop=y(this.plotTop,f.legendHeight+r+p+b)}else if(s==="bottom"&&!t(j))this.marginBottom=y(this.marginBottom,f.legendHeight-r+p+d);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin); -this.hasCartesianSeries&&n(this.axes,function(a){a.getOffset()});t(h)||(this.plotLeft+=e[3]);t(g)||(this.plotTop+=e[0]);t(j)||(this.marginBottom+=e[2]);t(i)||(this.marginRight+=e[1]);this.setChartSize()},initReflow:function(){function a(a){var g=c.width||db(d,"width"),h=c.height||db(d,"height"),a=a?a.target:K;if(g&&h&&(a===K||a===C)){if(g!==b.containerWidth||h!==b.containerHeight)clearTimeout(e),b.reflowTimeout=e=setTimeout(function(){b.container&&b.resize(g,h,!1)},100);b.containerWidth=g;b.containerHeight= -h}}var b=this,c=b.options.chart,d=b.renderTo,e;I(K,"resize",a);I(b,"destroy",function(){S(K,"resize",a)})},resize:function(a,b,c){var d=this,e,f,g=d.resetZoomButton,h=d.title,i=d.subtitle,j;d.isResizing+=1;j=function(){d&&E(d,"endResize",null,function(){d.isResizing-=1})};ua(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(t(a))d.chartWidth=e=s(a);if(t(b))d.chartHeight=f=s(b);H(d.container,{width:e+"px",height:f+"px"});d.renderer.setSize(e,f,c);d.plotWidth=e-d.plotLeft-d.marginRight; -d.plotHeight=f-d.plotTop-d.marginBottom;d.maxTicks=null;n(d.axes,function(a){a.isDirty=!0;a.setScale()});n(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.getMargins();a=d.spacingBox;h&&h.align(null,null,a);i&&i.align(null,null,a);g&&g.align&&g.align(null,null,d[g.alignTo]);d.redraw(c);d.oldChartHeight=null;E(d,"resize");Oa===!1?j():setTimeout(j,Oa&&Oa.duration||500)},setChartSize:function(){var a=this.inverted,b=this.chartWidth,c=this.chartHeight,d=this.options.chart,e=d.spacingTop, -f=d.spacingRight,g=d.spacingBottom,h=d.spacingLeft,i,j,k,l;this.plotLeft=i=s(this.plotLeft);this.plotTop=j=s(this.plotTop);this.plotWidth=k=s(b-i-this.marginRight);this.plotHeight=l=s(c-j-this.marginBottom);this.plotSizeX=a?l:k;this.plotSizeY=a?k:l;this.plotBorderWidth=a=d.plotBorderWidth||0;this.spacingBox={x:h,y:e,width:b-h-f,height:c-e-g};this.plotBox={x:i,y:j,width:k,height:l};this.clipBox={x:a/2,y:a/2,width:this.plotSizeX-a,height:this.plotSizeY-a};n(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})}, -resetMargins:function(){var a=this.options.chart,b=a.spacingRight,c=a.spacingBottom,d=a.spacingLeft;this.plotTop=o(this.optionsMarginTop,a.spacingTop);this.marginRight=o(this.optionsMarginRight,b);this.marginBottom=o(this.optionsMarginBottom,c);this.plotLeft=o(this.optionsMarginLeft,d);this.axisOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth|| -0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,p,o=this.plotLeft,n=this.plotTop,q=this.plotWidth,r=this.plotHeight,t=this.plotBox,s=this.clipRect,v=this.clipBox;p=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp(null,null,null,c-p,d-p));else{e={fill:j||R};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(p/2,p/2,c-p,d-p,a.borderRadius,i).attr(e).add().shadow(a.shadow)}if(k)f?f.animate(t):this.plotBackground=b.rect(o,n,q,r,0).attr({fill:k}).add().shadow(a.plotShadow); -if(l)h?h.animate(t):this.plotBGImage=b.image(l,o,n,q,r).add();s?s.animate({width:v.width,height:v.height}):this.clipRect=b.clipRect(v);if(m)g?g.animate(g.crisp(null,o,n,q,r)):this.plotBorder=b.rect(o,n,q,r,0,m).attr({stroke:a.plotBorderColor,"stroke-width":m,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;n(["inverted","angular","polar"],function(g){c=$[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&& -e--;)(c=$[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},render:function(){var a=this,b=a.axes,c=a.renderer,d=a.options,e=d.labels,d=d.credits,f;a.setTitle();a.legend=new rb(a);n(b,function(a){a.setScale()});a.getMargins();a.maxTicks=null;n(b,function(a){a.setTickPositions(!0);a.setMaxTicks()});a.adjustTickAmounts();a.getMargins();a.drawChartBox();a.hasCartesianSeries&&n(b,function(a){a.render()});if(!a.seriesGroup)a.seriesGroup=c.g("series-group").attr({zIndex:3}).add();n(a.series,function(a){a.translate(); -a.setTooltipPoints();a.render()});e.items&&n(e.items,function(b){var d=r(e.style,b.style),f=w(d.left)+a.plotLeft,j=w(d.top)+a.plotTop+12;delete d.left;delete d.top;c.text(b.html,f,j).attr({zIndex:2}).css(d).add()});if(d.enabled&&!a.credits)f=d.href,a.credits=c.text(d.text,0,0).on("click",function(){if(f)location.href=f}).attr({align:d.position.align,zIndex:8}).css(d.style).add().align(d.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode; -E(a,"destroy");S(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();n("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,tracker,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",S(d),f&&Na(d);for(e in a)delete a[e]},firstRender:function(){var a=this,b=a.options,c=a.callback;if(!Z&&K==K.top&&C.readyState!=="complete"|| -U&&!K.canvg)U?Mb.push(function(){a.firstRender()},b.global.canvasToolsURL):C.attachEvent("onreadystatechange",function(){C.detachEvent("onreadystatechange",a.firstRender);C.readyState==="complete"&&a.firstRender()});else{a.getContainer();E(a,"init");if(Highcharts.RangeSelector&&b.rangeSelector.enabled)a.rangeSelector=new Highcharts.RangeSelector(a);a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();n(b.series||[],function(b){a.initSeries(b)});if(Highcharts.Scroller&&(b.navigator.enabled|| -b.scrollbar.enabled))a.scroller=new Highcharts.Scroller(a);a.tracker=new qb(a,b);a.render();a.renderer.draw();c&&c.apply(a,[a]);n(a.callbacks,function(b){b.apply(a,[a])});a.cloneRenderTo(!0);E(a,"load")}},init:function(a){var b=this.options.chart,c;b.reflow!==!1&&I(this,"load",this.initReflow);if(a)for(c in a)I(this,c,a[c]);this.xAxis=[];this.yAxis=[];this.animation=U?!1:o(b.animation,!0);this.setSize=this.resize;this.pointCount=0;this.counters=new Eb;this.firstRender()}};sb.prototype.callbacks=[]; -var Ta=function(){};Ta.prototype={init:function(a,b,c){var d=a.chart.counters;this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint)b=a.chart.options.colors,this.color=this.color||b[d.color++],d.wrapColor(b.length);a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=typeof a;this.config=a;if(d==="number"||a===null)this.y=a;else if(typeof a[0]==="number")this.x=a[0],this.y=a[1];else if(d==="object"&&typeof a.length!=="number"){r(this,a);this.options= -a;if(a.dataLabels)c._hasPointLabels=!0;if(a.marker)c._hasPointMarkers=!0}else if(typeof a[0]==="string")this.name=a[0],this.y=a[1];if(this.x===x)this.x=b===x?c.autoIncrement():b},destroy:function(){var a=this.series.chart,b=a.hoverPoints,c;a.pointCount--;if(b&&(this.setState(),Ba(b,this),!b.length))a.hoverPoints=null;if(this===a.hoverPoint)this.onMouseOut();if(this.graphic||this.dataLabel)S(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a= -"graphic,tracker,dataLabel,group,connector,shadowGroup".split(","),b,c=6;c--;)b=a[c],this[b]&&(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},select:function(a,b){var c=this,d=c.series.chart,a=o(a,!c.selected);c.firePointEvent(a?"select":"unselect",{accumulate:b},function(){c.selected=a;c.setState(a&&"select");b||n(d.getSelectedPoints(),function(a){if(a.selected&& -a!==c)a.selected=!1,a.setState(""),a.firePointEvent("unselect")})})},onMouseOver:function(){var a=this.series,b=a.chart,c=b.tooltip,d=b.hoverPoint;if(d&&d!==this)d.onMouseOut();this.firePointEvent("mouseOver");c&&(!c.shared||a.noSharedTooltip)&&c.refresh(this);this.setState("hover");b.hoverPoint=this},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;if(!b||Rb(this,b)===-1)this.firePointEvent("mouseOut"),this.setState(),a.hoverPoint=null},tooltipFormatter:function(a){var b=this.series, -c=b.tooltipOptions,d=a.match(/\{(series|point)\.[a-zA-Z]+\}/g),e=/[{\.}]/,f,g,h,i,j={y:0,open:0,high:0,low:0,close:0,percentage:1,total:1};c.valuePrefix=c.valuePrefix||c.yPrefix;c.valueDecimals=c.valueDecimals||c.yDecimals;c.valueSuffix=c.valueSuffix||c.ySuffix;for(i in d)g=d[i],la(g)&&g!==a&&(h=(" "+g).split(e),f={point:this,series:b}[h[1]],h=h[2],f===this&&j.hasOwnProperty(h)?(f=j[h]?h:"value",f=(c[f+"Prefix"]||"")+Ia(this[h],o(c[f+"Decimals"],-1))+(c[f+"Suffix"]||"")):f=f[h],a=a.replace(g,f)); -return a},update:function(a,b,c){var d=this,e=d.series,f=d.graphic,g,h=e.data,i=h.length,j=e.chart,b=o(b,!0);d.firePointEvent("update",{options:a},function(){d.applyOptions(a);X(a)&&(e.getAttribs(),f&&f.attr(d.pointAttr[e.state]));for(g=0;ga+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart.options,c=b.plotOptions,d=c[this.type],e=a.data;a.data=null;c=A(d,c.series,a);c.data=a.data=e;this.tooltipOptions=A(b.tooltip,c.tooltip);d.marker===null&&delete c.marker;return c},getColor:function(){var a=this.options,b=this.chart.options.colors,c=this.chart.counters;this.color=a.color|| -!a.colorByPoint&&b[c.color++]||"gray";c.wrapColor(b.length)},getSymbol:function(){var a=this.options.marker,b=this.chart,c=b.options.symbols,b=b.counters;this.symbol=a.symbol||c[b.symbol++];if(/^url/.test(this.symbol))a.radius=0;b.wrapSymbol(c.length)},drawLegendSymbol:function(a){var b=this.options,c=b.marker,d=a.options.symbolWidth,e=this.chart.renderer,f=this.legendGroup,a=a.baseline,g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle=b.dashStyle;this.legendLine=e.path(["M", -0,a-4,"L",d,a-4]).attr(g).add(f)}if(c&&c.enabled)b=c.radius,this.legendSymbol=e.symbol(this.symbol,d/2-b,a-4-b,2*b,2*b).add(f)},addPoint:function(a,b,c,d){var e=this.data,f=this.graph,g=this.area,h=this.chart,i=this.xData,j=this.yData,k=f&&f.shift||0,l=this.options.data,m=this.pointClass.prototype;ua(d,h);if(f&&c)f.shift=k+1;if(g){if(c)g.shift=k+1;g.isArea=!0}b=o(b,!0);d={series:this};m.applyOptions.apply(d,[a]);i.push(d.x);j.push(m.toYData?m.toYData.call(d):d.y);l.push(a);c&&(e[0]&&e[0].remove?e[0].remove(!1): -(e.shift(),i.shift(),j.shift(),l.shift()));this.getAttribs();this.isDirtyData=this.isDirty=!0;b&&h.redraw()},setData:function(a,b){var c=this.points,d=this.options,e=this.initialColor,f=this.chart,g=null,h=this.xAxis,i,j=this.pointClass.prototype;this.xIncrement=null;this.pointRange=h&&h.categories?1:d.pointRange;if(t(e))f.counters.color=e;var e=[],k=[],l=a?a.length:[],m=(i=this.pointArrayMap)&&i.length;if(l>(d.turboThreshold||1E3)){for(i=0;g===null&&ik||this.forceCrop))if(a=i.getExtremes(), -i=a.min,k=a.max,b[d-1]k)b=[],c=[];else if(b[0]k){for(a=0;a=i){e=y(0,a-1);break}for(;ak){f=a+1;break}b=b.slice(e,f);c=c.slice(e,f);g=!0}for(a=b.length-1;a>0;a--)if(d=b[a]-b[a-1],d>0&&(h===x||d=0&&c<=d;)i[c++]=g}this.tooltipPoints=i}},tooltipHeaderFormatter:function(a){var b=this.tooltipOptions,c=b.xDateFormat,d=this.xAxis,e=d&&d.options.type==="datetime",f;if(e&&!c)for(f in B)if(B[f]>=d.closestPointRange){c=b.dateTimeLabelFormats[f];break}return b.headerFormat.replace("{point.key}",e&&Aa(a)? -cb(c,a):a).replace("{series.name}",this.name).replace("{series.color}",this.color)},onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&E(this,"mouseOver");this.setState("hover");a.hoverSeries=this},onMouseOut:function(){var a=this.options,b=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&E(this,"mouseOut");c&&!a.stickyTracking&&!c.shared&&c.hide();this.setState();b.hoverSeries=null},animate:function(a){var b= -this,c=b.chart,d=c.renderer,e;e=b.options.animation;var f=c.clipBox,g=c.inverted,h;if(e&&!X(e))e=W[b.type].animation;h="_sharedClip"+e.duration+e.easing;if(a)a=c[h],e=c[h+"m"],a||(c[h]=a=d.clipRect(r(f,{width:0})),c[h+"m"]=e=d.clipRect(-99,g?-c.plotLeft:-c.plotTop,99,g?c.chartWidth:c.chartHeight)),b.group.clip(a),b.markerGroup.clip(e),b.sharedClipKey=h;else{if(a=c[h])a.animate({width:c.plotSizeX},e),c[h+"m"].animate({width:c.plotSizeX+99},e);b.animate=null;b.animationTimeout=setTimeout(function(){b.afterAnimate()}, -e.duration)}},afterAnimate:function(){var a=this.chart,b=this.sharedClipKey,c=this.group;c&&this.options.clip!==!1&&(c.clip(a.clipRect),this.markerGroup.clip());setTimeout(function(){b&&a[b]&&(a[b]=a[b].destroy(),a[b+"m"]=a[b+"m"].destroy())},100)},drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,i,j,k,l=this.options.marker,m,p=this.markerGroup;if(l.enabled||this._hasPointMarkers)for(f=b.length;f--;)if(g=b[f],d=g.plotX,e=g.plotY,k=g.graphic,i=g.marker||{},a=l.enabled&&i.enabled=== -x||i.enabled,m=c.isInsidePlot(d,e,c.inverted),a&&e!==x&&!isNaN(e))if(a=g.pointAttr[g.selected?"select":""],h=a.r,i=o(i.symbol,this.symbol),j=i.indexOf("url")===0,k)k.attr({visibility:m?Z?"inherit":"visible":"hidden"}).animate(r({x:d-h,y:e-h},k.symbolName?{width:2*h,height:2*h}:{}));else{if(m&&(h>0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h).attr(a).add(p)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{}; -for(f in e)g=e[f],h[f]=o(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=W[a.type].marker?a.options.marker:a.options,c=b.states,d=c.hover,e,f=a.color,g={stroke:f,fill:f},h=a.points||[],i=[],j,k=a.pointAttrToOptions,l;a.options.marker?(d.radius=d.radius||b.radius+2,d.lineWidth=d.lineWidth||b.lineWidth+1):d.color=d.color||qa(d.color||f).brighten(d.brightness).get();i[""]=a.convertAttribs(b,g);n(["hover","select"],function(b){i[b]=a.convertAttribs(c[b],i[""])});a.pointAttr=i;for(f= -h.length;f--;){g=h[f];if((b=g.options&&g.options.marker||g.options)&&b.enabled===!1)b.radius=0;e=a.options.colorByPoint;if(g.options)for(l in k)t(b[k[l]])&&(e=!0);if(e){b=b||{};j=[];c=b.states||{};e=c.hover=c.hover||{};if(!a.options.marker)e.color=qa(e.color||g.color).brighten(e.brightness||d.brightness).get();j[""]=a.convertAttribs(r({color:g.color},b),i[""]);j.hover=a.convertAttribs(c.hover,i.hover,j[""]);j.select=a.convertAttribs(c.select,i.select,j[""])}else j=i;g.pointAttr=j}},destroy:function(){var a= -this,b=a.chart,c=/AppleWebKit\/533/.test(Fa),d,e,f=a.data||[],g,h,i;E(a,"destroy");S(a);n(["xAxis","yAxis"],function(b){if(i=a[b])Ba(i.series,a),i.isDirty=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(e=f.length;e--;)(g=f[e])&&g.destroy&&g.destroy();a.points=null;clearTimeout(a.animationTimeout);n("area,graph,dataLabelsGroup,group,markerGroup,tracker,trackerGroup".split(","),function(b){a[b]&&(d=c&&b==="group"?"hide":"destroy",a[b][d]())});if(b.hoverSeries===a)b.hoverSeries=null;Ba(b.series, -a);for(h in a)delete a[h]},drawDataLabels:function(){var a=this,b=a.options.dataLabels,c=a.points,d,e,f,g;if(b.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(b),g=a.plotGroup("dataLabelsGroup","data-labels",a.visible?"visible":"hidden",6),e=b,n(c,function(c){var i,j=c.dataLabel,k,l=!0;d=c.options&&c.options.dataLabels;i=e.enabled||d&&d.enabled;if(j&&!i)c.dataLabel=j.destroy();else if(i){i=b.rotation;b=A(e,d);f=b.formatter.call(c.getLabelConfig(),b);b.style.color=o(b.color,b.style.color, -a.color,"black");if(j)j.attr({text:f}),l=!1;else if(t(f)){j={fill:b.backgroundColor,stroke:b.borderColor,"stroke-width":b.borderWidth,r:b.borderRadius||0,rotation:i,padding:b.padding,zIndex:1};for(k in j)j[k]===x&&delete j[k];j=c.dataLabel=a.chart.renderer[i?"text":"label"](f,0,-999,null,null,null,b.useHTML).attr(j).css(b.style).add(g).shadow(b.shadow)}j&&a.alignDataLabel(c,j,b,null,l)}})},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=o(a.plotX,-999),a=o(a.plotY,-999),i=b.getBBox(), -d=r({x:g?f.plotWidth-a:h,y:s(g?f.plotHeight-h:a),width:0,height:0},d);r(c,{width:i.width,height:i.height});c.rotation?(d={align:c.align,x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2},b[e?"attr":"animate"](d)):(b.align(c,null,d),d=b.alignAttr);b.attr({visibility:c.crop===!1||f.isInsidePlot(d.x,d.y)||f.isInsidePlot(h,a,g)?Z?"inherit":"visible":"hidden"})},getSegmentPath:function(a){var b=this,c=[];n(a,function(d,e){b.getPointSpline?c.push.apply(c,b.getPointSpline(a,d,e)):(c.push(e?"L":"M"),e&&b.options.step&& -c.push(d.plotX,a[e-1].plotY),c.push(d.plotX,d.plotY))});return c},getGraphPath:function(){var a=this,b=[],c,d=[];n(a.segments,function(e){c=a.getSegmentPath(e);e.length>1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this.options,b=this.graph,c=this.group,d=a.lineColor||this.color,e=a.lineWidth,f=a.dashStyle,g=this.getGraphPath();if(b)eb(b),b.animate({d:g});else if(e){b={stroke:d,"stroke-width":e,zIndex:1};if(f)b.dashstyle=f;this.graph=this.chart.renderer.path(g).attr(b).add(c).shadow(a.shadow)}}, -invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};n(["group","trackerGroup","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;I(c,"resize",a);I(b,"destroy",function(){S(c,"resize",a)});a();b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=this.chart,h=this.xAxis,i=this.yAxis;f||(this[a]=f=g.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f.translate(h?h.left:g.plotLeft,i?i.top:g.plotTop);return f},render:function(){var a= -this.chart,b,c=this.options,d=c.animation&&!!this.animate,e=this.visible?"visible":"hidden",f=c.zIndex,g=this.hasRendered,h=a.seriesGroup;b=this.plotGroup("group","series",e,f,h);this.markerGroup=this.plotGroup("markerGroup","markers",e,f,h);d&&this.animate(!0);this.getAttribs();b.inverted=a.inverted;this.drawGraph&&this.drawGraph();this.drawPoints();this.drawDataLabels();this.options.enableMouseTracking!==!1&&this.drawTracker();a.inverted&&this.invertGroups();c.clip!==!1&&!this.sharedClipKey&&!g&& -(b.clip(a.clipRect),this.trackerGroup&&this.trackerGroup.clip(a.clipRect));d?this.animate():g||this.afterAnimate();this.isDirty=this.isDirtyData=!1;this.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:this.xAxis.left,translateY:this.yAxis.top}));this.translate();this.setTooltipPoints(!0);this.render();b&&E(this,"updatedData")},setState:function(a){var b=this.options,c=this.graph, -d=b.states,b=b.lineWidth,a=a||"";if(this.state!==a)this.state=a,d[a]&&d[a].enabled===!1||(a&&(b=d[a].lineWidth||b+1),c&&!c.dashstyle&&c.attr({"stroke-width":b},a?0:500))},setVisible:function(a,b){var c=this.chart,d=this.legendItem,e=this.group,f=this.tracker,g=this.dataLabelsGroup,h=this.markerGroup,i,j=this.points,k=c.options.chart.ignoreHiddenSeries;i=this.visible;i=(this.visible=a=a===x?!i:a)?"show":"hide";if(e)e[i]();if(h)h[i]();if(f)f[i]();else if(j)for(e=j.length;e--;)if(f=j[e],f.tracker)f.tracker[i](); -if(g)g[i]();d&&c.legend.colorizeItem(this,a);this.isDirty=!0;this.options.stacking&&n(c.series,function(a){if(a.options.stacking&&a.visible)a.isDirty=!0});if(k)c.isDirtyBox=!0;b!==!1&&c.redraw();E(this,i)},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===x?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;E(this,a?"select":"unselect")},drawTracker:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath), -e=d.length,f=a.chart,g=f.renderer,h=f.options.tooltip.snap,i=a.tracker,j=b.cursor,j=j&&{cursor:j},k=a.singlePoints,l=this.isCartesian&&this.plotGroup("trackerGroup",null,"visible",b.zIndex||1,f.trackerGroup),m;if(e&&!c)for(m=e+1;m--;)d[m]==="M"&&d.splice(m+1,0,d[m+1]-h,d[m+2],"L"),(m&&d[m]==="M"||m===e)&&d.splice(m,0,"L",d[m-2]+h,d[m-1]);for(m=0;m=0;d--)da&&i>e?(i=y(a,e),k=2*e-i):ig&&k>e?(k=y(g,e),i=2*e-k):kv?g-v:z-(f<=z?v:0));c.barX= -h;c.pointWidth=q;c.shapeType="rect";c.shapeArgs=f=b.renderer.Element.prototype.crisp.call(0,e,h,i,s,j);e%2&&(f.y-=1,f.height+=1);c.trackerArgs=L(j)<3&&A(c.shapeArgs,{height:6,y:i-3})})},getSymbol:ya,drawLegendSymbol:G.prototype.drawLegendSymbol,drawGraph:ya,drawPoints:function(){var a=this,b=a.options,c=a.chart.renderer,d;n(a.points,function(e){var f=e.plotY,g=e.graphic;if(f!==x&&!isNaN(f)&&e.y!==null)d=e.shapeArgs,g?(eb(g),g.animate(A(d))):e.graphic=c[e.shapeType](d).attr(e.pointAttr[e.selected? -"select":""]).add(a.group).shadow(b.shadow,null,b.stacking&&!b.borderRadius);else if(g)e.graphic=g.destroy()})},drawTracker:function(){var a=this,b=a.chart,c=b.renderer,d,e,f=+new Date,g=a.options,h=g.cursor,i=h&&{cursor:h},j=a.isCartesian&&a.plotGroup("trackerGroup",null,"visible",g.zIndex||1,b.trackerGroup),k,l,m;n(a.points,function(h){e=h.tracker;d=h.trackerArgs||h.shapeArgs;l=h.plotY;m=!a.isCartesian||l!==x&&!isNaN(l);delete d.strokeWidth;if(h.y!==null&&m)e?e.attr(d):h.tracker=c[h.shapeType](d).attr({isTracker:f, -fill:wb,visibility:a.visible?"visible":"hidden"}).on(fa?"touchstart":"mouseover",function(c){k=c.relatedTarget||c.fromElement;if(b.hoverSeries!==a&&z(k,"isTracker")!==f)a.onMouseOver();h.onMouseOver()}).on("mouseout",function(b){if(!g.stickyTracking&&(k=b.relatedTarget||b.toElement,z(k,"isTracker")!==f))a.onMouseOut()}).css(i).add(h.group||j)})},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=a.below||a.plotY>(this.translatedThreshold||f.plotSizeY),i=this.options.stacking||c.inside; -if(a.shapeArgs&&(d=A(a.shapeArgs),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!i))g?(d.x+=h?0:d.width,d.width=0):(d.y+=h?d.height:0,d.height=0);c.align=o(c.align,!g||i?"center":h?"right":"left");c.verticalAlign=o(c.verticalAlign,g||i?"middle":h?"top":"bottom");N.prototype.alignDataLabel.call(this,a,b,c,d,e)},animate:function(a){var b=this,c=b.points,d=b.options;if(!a)n(c,function(a){var c=a.graphic,a=a.shapeArgs,g=b.yAxis,h=d.threshold;c&&(c.attr({height:0, -y:t(h)?g.getThreshold(h):g.translate(g.getExtremes().min,0,1,0,1)}),c.animate({height:a.height,y:a.y},d.animation))}),b.animate=null},remove:function(){var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0});N.prototype.remove.apply(a,arguments)}});$.column=ha;W.bar=A(W.column);za=ca(ha,{type:"bar",inverted:!0});$.bar=za;W.scatter=A(ga,{lineWidth:0,states:{hover:{lineWidth:0}},tooltip:{headerFormat:'{series.name}
', -pointFormat:"x: {point.x}
y: {point.y}
"}});za=ca(N,{type:"scatter",sorted:!1,translate:function(){var a=this;N.prototype.translate.apply(a);n(a.points,function(b){b.shapeType="circle";b.shapeArgs={x:b.plotX,y:b.plotY,r:a.chart.options.tooltip.snap}})},drawTracker:function(){for(var a=this,b=a.options.cursor,b=b&&{cursor:b},c=a.points,d=c.length,e;d--;)if(e=c[d].graphic)e.element._i=d;a._hasTracking?a._hasTracking=!0:a.markerGroup.attr({isTracker:!0}).on(fa?"touchstart":"mouseover", -function(b){a.onMouseOver();if(b.target._i!==x)c[b.target._i].onMouseOver()}).on("mouseout",function(){if(!a.options.stickyTracking)a.onMouseOut()}).css(b)}});$.scatter=za;W.pie=A(ga,{borderColor:"#FFFFFF",borderWidth:1,center:["50%","50%"],colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},legendType:"point",marker:null,size:"75%",showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}}});ya={type:"pie",isCartesian:!1,pointClass:ca(Ta, -{init:function(){Ta.prototype.init.apply(this,arguments);var a=this,b;r(a,{visible:a.visible!==!1,name:o(a.name,"Slice")});b=function(){a.slice()};I(a,"select",b);I(a,"unselect",b);return a},setVisible:function(a){var b=this.series,c=b.chart,d=this.tracker,e=this.dataLabel,f=this.connector,g=this.shadowGroup,h;h=(this.visible=a=a===x?!this.visible:a)?"show":"hide";this.group[h]();if(d)d[h]();if(e)e[h]();if(f)f[h]();if(g)g[h]();this.legendItem&&c.legend.colorizeItem(this,a);if(!b.isDirty&&b.options.ignoreHiddenPoint)b.isDirty= -!0,c.redraw()},slice:function(a,b,c){var d=this.series.chart,e=this.slicedTranslation;ua(c,d);o(b,!0);a=this.sliced=t(a)?a:!this.sliced;a={translateX:a?e[0]:d.plotLeft,translateY:a?e[1]:d.plotTop};this.group.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}}),pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},getColor:function(){this.initialColor=this.chart.counters.color},animate:function(){var a=this;n(a.points,function(b){var c=b.graphic,b=b.shapeArgs,d= --xa/2;c&&(c.attr({r:0,start:d,end:d}),c.animate({r:b.r,start:b.start,end:b.end},a.options.animation))});a.animate=null},setData:function(a,b){N.prototype.setData.call(this,a,!1);this.processData();this.generatePoints();o(b,!0)&&this.chart.redraw()},getCenter:function(){var a=this.options,b=this.chart,c=b.plotWidth,d=b.plotHeight,a=a.center.concat([a.size,a.innerSize||0]),e=O(c,d),f;return Sa(a,function(a,b){return(f=/%$/.test(a))?[c,d,e,e][b]*w(a)/100:a})},translate:function(){this.generatePoints(); -var a=0,b=-0.25,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g=this.chart,h,i,j,k=this.points,l=2*xa,m=c.dataLabels.distance,o=c.ignoreHiddenPoint,n,r=k.length,q;this.center=f=this.getCenter();this.getX=function(a,b){j=J.asin((a-f[1])/(f[2]/2+m));return f[0]+(b?-1:1)*V(j)*(f[2]/2+m)};for(n=0;n0,r=[[],[]],s,q,t,w,y=2,v;if(d.enabled||this._hasPointLabels){N.prototype.drawDataLabels.apply(this);n(a,function(a){a.dataLabel&&r[a.labelPos[7]0){for(v=m-l-j;v<=m+l+j;v+=a)x.push(v);t=x.length;if(C>t){h=[].concat(z);h.sort(w);for(v=C;v--;)h[v].rank=v;for(v=C;v--;)z[v].rank>=t&&z.splice(v,1);C=z.length}for(v=0;v0){if(q=A.pop(),B=q.i,q=q.y,s>q&&x[B+1]!==null||s3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+M(a-c).toFixed(f).slice(2):"")}function Ha(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function Ma(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments); +a.unshift(d);return c.apply(this,a)}}function Ia(a,b){for(var c="{",d=!1,e,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");i=g.length;e=b;for(h=0;h-1?h.thousandsSep:""))):e=cb(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function mb(a){return U.pow(10,T(U.log(a)/ +U.LN10))}function nb(a,b,c,d){var e,c=m(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;dc&&(c=a[b]);return c}function Oa(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Pa(a){db||(db=Y(Ja));a&&db.appendChild(a);db.innerHTML=""}function ra(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw c;else I.console&&console.log(c)}function da(a){return parseFloat(a.toPrecision(14))}function Qa(a,b){va=m(a,b.animation)}function Cb(){var a=E.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";Ra=(a&&E.global.timezoneOffset|| +0)*6E4;eb=a?Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,m(c,1),m(g,0),m(h,0),m(i,0))).getTime()};pb=b+"Minutes";qb=b+"Hours";rb=b+"Day";Xa=b+"Date";fb=b+"Month";gb=b+"FullYear";Db=c+"Minutes";Eb=c+"Hours";sb=c+"Date";Fb=c+"Month";Gb=c+"FullYear"}function P(){}function Sa(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function la(){this.init.apply(this,arguments)}function Ya(){this.init.apply(this,arguments)}function Hb(a,b,c,d,e){var f=a.chart.inverted; +this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.alignOptions={align:b.align||(f?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(f?"middle":c?"bottom":"top"),y:m(b.y,f?4:c?14:-6),x:m(b.x,f?c?-6:6:0)};this.textAlign=b.textAlign||(f?c?"right":"left":"center")}var t,y=document,I=window,U=Math,u=U.round,T=U.floor,Ka=U.ceil,v=U.max,C=U.min,M=U.abs,Z=U.cos,ea=U.sin,ma=U.PI,Ca=ma*2/360,wa=navigator.userAgent,Ib=I.opera,Aa=/msie/i.test(wa)&& +!Ib,hb=y.documentMode===8,ib=/AppleWebKit/.test(wa),Ta=/Firefox/.test(wa),Jb=/(Mobile|Android|Windows Phone)/.test(wa),xa="http://www.w3.org/2000/svg",aa=!!y.createElementNS&&!!y.createElementNS(xa,"svg").createSVGRect,Nb=Ta&&parseInt(wa.split("Firefox/")[1],10)<4,fa=!aa&&!Aa&&!!y.createElement("canvas").getContext,Za,$a,Kb={},tb=0,db,E,cb,va,ub,A,sa=function(){},V=[],ab=0,Ja="div",Q="none",Ob=/^[0-9]+$/,Pb="stroke-width",eb,Ra,pb,qb,rb,Xa,fb,gb,Db,Eb,sb,Fb,Gb,F={},R=I.Highcharts=I.Highcharts?ra(16, +!0):{};cb=function(a,b,c){if(!r(b)||isNaN(b))return"Invalid date";var a=m(a,"%Y-%m-%d %H:%M:%S"),d=new Date(b-Ra),e,f=d[qb](),g=d[rb](),h=d[Xa](),i=d[fb](),j=d[gb](),k=E.lang,l=k.weekdays,d=q({a:l[g].substr(0,3),A:l[g],d:Ha(h),e:h,b:k.shortMonths[i],B:k.months[i],m:Ha(i+1),y:j.toString().substr(2,2),Y:j,H:Ha(f),I:Ha(f%12||12),l:f%12||12,M:Ha(d[pb]()),p:f<12?"AM":"PM",P:f<12?"am":"pm",S:Ha(d.getSeconds()),L:Ha(u(b%1E3),3)},R.dateFormats);for(e in d)for(;a.indexOf("%"+e)!==-1;)a=a.replace("%"+e,typeof d[e]=== +"function"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};Bb.prototype={wrapColor:function(a){if(this.color>=a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};A=function(){for(var a=0,b=arguments,c=b.length,d={};a-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c), +h,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length{point.key}
',pointFormat:'● {series.name}: {point.y}
',shadow:!0,snap:Jb?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px",padding:"8px", +whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var ba=E.plotOptions,S=ba.line;Cb();var Tb=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,Ub=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,Vb=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,ya=function(a){var b=[],c, +d;(function(a){a&&a.stops?d=Ua(a.stops,function(a){return ya(a[1])}):(c=Tb.exec(a))?b=[z(c[1]),z(c[2]),z(c[3]),parseFloat(c[4],10)]:(c=Ub.exec(a))?b=[z(c[1],16),z(c[2],16),z(c[3],16),1]:(c=Vb.exec(a))&&(b=[z(c[1]),z(c[2]),z(c[3]),1])})(a);return{get:function(c){var f;d?(f=w(a),f.stops=[].concat(f.stops),p(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)p(d, +function(b){b.brighten(a)});else if(ha(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=z(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};P.prototype={init:function(a,b){this.element=b==="span"?Y(b):y.createElementNS(xa,b);this.renderer=a},opacity:1,animate:function(a,b,c){b=m(b,va,!0);bb(this);if(b){b=w(b,{});if(c)b.complete=c;kb(this,a,b)}else this.attr(a),c&&c()},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,i,j,k,l,o,n,s=[];a.linearGradient? +f="linearGradient":a.radialGradient&&(f="radialGradient");if(f){g=a[f];h=d.gradients;j=a.stops;o=c.radialReference;La(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"});f==="radialGradient"&&o&&!r(g.gradientUnits)&&(g=w(g,{cx:o[0]-o[2]/2+g.cx*o[2],cy:o[1]-o[2]/2+g.cy*o[2],r:g.r*o[2],gradientUnits:"userSpaceOnUse"}));for(n in g)n!=="id"&&s.push(n,g[n]);for(n in j)s.push(j[n]);s=s.join(",");h[s]?a=h[s].attr("id"):(g.id=a="highcharts-"+tb++,h[s]=i=d.createElement(f).attr(g).add(d.defs), +i.stops=[],p(j,function(a){a[1].indexOf("rgba")===0?(e=ya(a[1]),k=e.get("rgb"),l=e.get("a")):(k=a[1],l=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":k,"stop-opacity":l}).add(i);i.stops.push(a)}));c.setAttribute(b,"url("+d.url+"#"+a+")")}},attr:function(a,b){var c,d,e=this.element,f,g=this,h;typeof a==="string"&&b!==t&&(c=a,a={},a[c]=b);if(typeof a==="string")g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(c in a){d=a[c];h=!1;this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&& +(f||(this.symbolAttr(a),f=!0),h=!0);if(this.rotation&&(c==="x"||c==="y"))this.doTransform=!0;h||(this[c+"Setter"]||this._defaultSetter).call(this,d,c,e);this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c)&&this.updateShadows(c,d)}if(this.doTransform)this.updateTransform(),this.doTransform=!1}return g},updateShadows:function(a,b){for(var c=this.shadows,d=c.length;d--;)c[d].setAttribute(a,a==="height"?v(b-(c[d].cutHeight||0),0):a==="d"?this.d:b)},addClass:function(a){var b=this.element, +c=H(b,"class")||"";c.indexOf(a)===-1&&H(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;p("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=m(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",a?"url("+this.renderer.url+"#"+a.id+")":Q)},crisp:function(a){var b,c={},d,e=a.strokeWidth||this.strokeWidth||this.attr&&this.attr("stroke-width")||0;d=u(e)%2/2;a.x=T(a.x||this.x|| +0)+d;a.y=T(a.y||this.y||0)+d;a.width=T((a.width||this.width||0)-2*d);a.height=T((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&&(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;if(a&&a.color)a.fill=a.color;if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&a.width&&d.nodeName.toLowerCase()==="text"&&z(a.width);b&&(a=q(b,c));this.styles=a;e&&(fa||!aa&&this.renderer.forExport)&&delete a.width;if(Aa&&!aa)G(this.element, +a);else{b=function(a,b){return"-"+b.toLowerCase()};for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";H(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;$a&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=Date.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(wa.indexOf("Android")===-1||Date.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference= +a;return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")"); +(r(c)||r(d))&&a.push("scale("+m(c,1)+" "+m(d,1)+")");a.length&&g.setAttribute("transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||Fa(c))this.alignTo=d=c||"renderer",ja(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=m(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x|| +0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=u(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=u(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d,e=this.rotation;c=this.element;var f=this.styles,g=e*Ca;d=this.textStr;var h;if(d===""||Ob.test(d))h="num."+d.toString().length+ +(f?"|"+f.fontSize+"|"+f.fontFamily:"");h&&(a=b.cache[h]);if(!a){if(c.namespaceURI===xa||b.forExport){try{a=c.getBBox?q({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(i){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){c=a.width;d=a.height;if(Aa&&f&&f.fontSize==="11px"&&d.toPrecision(3)==="16.9")a.height=d=14;if(e)a.width=M(d*ea(g))+M(c*Z(g)),a.height=M(d*Z(g))+M(c*ea(g))}this.bBox=a;h&&(b.cache[h]=a)}return a},show:function(a){return a&&this.element.namespaceURI=== +xa?(this.element.removeAttribute("visibility"),this):this.attr({visibility:a?"inherit":"visible"})},hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.hide()}})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=this.element,f=this.zIndex,g,h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(f)c.handleZ=!0,f=z(f);if(c.handleZ){a=d.childNodes; +for(g=0;gf||!r(f)&&r(c))){d.insertBefore(e,b);h=!0;break}}h||d.appendChild(e);this.added=!0;if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;bb(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f= +0;f/,i=/<.*href="(http[^"]+)".*>/,l&&!a.added&&this.box.appendChild(b), +e=f?e.replace(/<(b|strong)>/g,'').replace(/<(i|em)>/g,'').replace(/
/g,"").split(//g):[e],e[e.length-1]===""&&e.pop(),p(e,function(e,f){var g,n=0,e=e.replace(//g,"|||");g=e.split("|||");p(g,function(e){if(e!==""||g.length===1){var o={},m=y.createElementNS(xa,"tspan"),p;h.test(e)&&(p=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"), +H(m,"style",p));i.test(e)&&!d&&(H(m,"onclick",'location.href="'+e.match(i)[1]+'"'),G(m,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");if(e!==" "){m.appendChild(y.createTextNode(e));if(n)o.dx=0;else if(f&&j!==null)o.x=j;H(m,o);!n&&f&&(!aa&&d&&G(m,{display:"block"}),H(m,"dy",s(m),ib&&m.offsetHeight));b.appendChild(m);n++;if(l)for(var e=e.replace(/([^\^])-/g,"$1- ").split(" "),o=e.length>1&&k.whiteSpace!=="nowrap",$,r,B=a._clipHeight,q=[],v=s(),t= +1;o&&(e.length||q.length);)delete a.bBox,$=a.getBBox(),r=$.width,!aa&&c.forExport&&(r=c.measureSpanWidth(m.firstChild.data,a.styles)),$=r>l,!$||e.length===1?(e=q,q=[],e.length&&(t++,B&&t*v>B?(e=["..."],a.attr("title",a.textStr)):(m=y.createElementNS(xa,"tspan"),H(m,{dy:v,x:j}),p&&H(m,"style",p),b.appendChild(m),r>l&&(l=r)))):(m.removeChild(m.firstChild),q.unshift(e.pop())),e.length&&m.appendChild(y.createTextNode(e.join(" ").replace(/- /g,"-")))}}})}))},button:function(a,b,c,d,e,f,g,h,i){var j=this.label(a, +b,c,i,null,null,null,null,"button"),k=0,l,o,n,s,m,p,a={x1:0,y1:0,x2:0,y2:1},e=w({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);n=e.style;delete e.style;f=w(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);s=f.style;delete f.style;g=w(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);m=g.style;delete g.style;h=w(e,{style:{color:"#CCC"}},h);p=h.style;delete h.style; +K(j.element,Aa?"mouseover":"mouseenter",function(){k!==3&&j.attr(f).css(s)});K(j.element,Aa?"mouseout":"mouseleave",function(){k!==3&&(l=[e,f,g][k],o=[n,s,m][k],j.attr(l).css(o))});j.setState=function(a){(j.state=k=a)?a===2?j.attr(g).css(m):a===3&&j.attr(h).css(p):j.attr(e).css(n)};return j.on("click",function(){k!==3&&d.call(j)}).attr(e).css(q({cursor:"default"},n))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=u(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=u(a[2])+b%2/2);return a},path:function(a){var b= +{fill:Q};La(a)?b.d=a:ca(a)&&q(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=ca(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=function(a){this.element.setAttribute("cx",a)};b.ySetter=function(a){this.element.setAttribute("cy",a)};return b.attr(a)},arc:function(a,b,c,d,e,f){if(ca(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){var e=ca(a)?a.r: +e,g=this.createElement("rect"),a=ca(a)?a:a===t?{}:{x:a,y:b,width:v(c,0),height:v(d,0)};if(f!==t)a.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a){H(this.element,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[m(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return r(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f= +{preserveAspectRatio:Q};arguments.length>1&&q(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(u(b),u(c),d,e,f),i=/^url\((.*?)\)$/,j,k;if(h)g=this.path(h),q(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&q(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}), +a.alignByTranslate||a.translate(u((d-b[0])/2),u((e-b[1])/2)))},j=a.match(i)[1],a=Kb[j],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),Y("img",{onload:function(){k(g,Kb[j]=[this.width,this.height])},src:j}));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L", +a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=Z(f),j=ea(f),k=Z(g),g=ea(g),e=e.end-fc&&i>b+g&&ib+g&&id&&h>a+g&&ha+g&&hl&&/[ \-]/.test(b.textContent||b.innerText))G(b,{width:l+"px",display:"block",whiteSpace:"normal"}),i=l;this.getSpanCorrection(i,k,h,j,g)}G(b,{left:e+(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(ib)k=b.offsetHeight; +this.cTT=o}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=Aa?"-ms-transform":ib?"-webkit-transform":Ta?"MozTransform":Ib?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(Ta?"Origin":"-origin")]=d.transformOrigin=b*100+"% "+c+"px";G(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr=-a*c;this.yCorr=-b}});q(ta.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element,f=d.renderer;d.textSetter=function(a){a!==e.innerHTML&&delete this.bBox; +e.innerHTML=this.textStr=a};d.xSetter=d.ySetter=d.alignSetter=d.rotationSetter=function(a,b){b==="align"&&(b="textAlign");d[b]=a;d.htmlUpdateTransform()};d.attr({text:a,x:u(b),y:u(c)}).css({position:"absolute",whiteSpace:"nowrap",fontFamily:this.style.fontFamily,fontSize:this.style.fontSize});d.css=d.htmlCss;if(f.isSVG)d.add=function(a){var b,c=f.box.parentNode,j=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)j.push(a),a=a.parentGroup;p(j.reverse(),function(a){var d;b=a.div=a.div||Y(Ja,{className:H(a.element, +"class")},{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);d=b.style;q(a,{translateXSetter:function(b,c){d.left=b+"px";a[c]=b;a.doTransform=!0},translateYSetter:function(b,c){d.top=b+"px";a[c]=b;a.doTransform=!0},visibilitySetter:function(a,b){d[b]=a}})})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&&d.htmlUpdateTransform();return d};return d}});var X;if(!aa&&!fa){R.VMLElement=X={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute", +";"],e=b===Ja;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""),'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=Y(c);this.renderer=a},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();if(this.onAdd)this.onAdd();return this},updateTransform:P.prototype.htmlUpdateTransform, +setSpanRotation:function(){var a=this.rotation,b=Z(a*Ca),c=ea(a*Ca);G(this.element,{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",b,", M12=",-c,", M21=",c,", M22=",b,", sizingMethod='auto expand')"].join(""):Q})},getSpanCorrection:function(a,b,c,d,e){var f=d?Z(d*Ca):1,g=d?ea(d*Ca):0,h=m(this.elemHeight,this.element.offsetHeight),i;this.xCorr=f<0&&-a;this.yCorr=g<0&&-h;i=f*g<0;this.xCorr+=g*b*(i?1-c:c);this.yCorr-=f*b*(d?i?c:1-c:1);e&&e!=="left"&&(this.xCorr-=a*c*(f<0?-1:1),d&&(this.yCorr-= +h*c*(g<0?-1:1)),G(this.element,{textAlign:e}))},pathToVML:function(a){for(var b=a.length,c=[];b--;)if(ha(a[b]))c[b]=u(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))c[b+5]===c[b+7]&&(c[b+7]+=a[b+7]>a[b+5]?1:-1),c[b+6]===c[b+8]&&(c[b+8]+=a[b+8]>a[b+6]?1:-1);return c.join(" ")||"x"},clip:function(a){var b=this,c;a?(c=a.members,ja(c,b),c.push(b),b.destroyClip=function(){ja(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:hb?"inherit":"rect(auto)"}); +return b.css(a)},css:P.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Pa(a)},destroy:function(){this.destroyClip&&this.destroyClip();return P.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=I.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=z(a[c-2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path, +l,o,n,s;k&&typeof k.value!=="string"&&(k="x");o=k;if(a){n=m(a.width,3);s=(a.opacity||0.15)/n;for(e=1;e<=3;e++){l=n*2+1-2*e;c&&(o=this.cutOffPath(k.value,l+0.5));j=[''];h=Y(g.prepVML(j),null,{left:z(i.left)+m(a.offsetX,1),top:z(i.top)+m(a.offsetY,1)});if(c)h.cutOff=l+1;j=[''];Y(g.prepVML(j),null,null,h);b?b.element.appendChild(h): +f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this},updateShadows:sa,setAttr:function(a,b){hb?this.element[a]=b:this.element.setAttribute(a,b)},classSetter:function(a){this.element.className=a},dashstyleSetter:function(a,b,c){(c.getElementsByTagName("stroke")[0]||Y(this.renderer.prepVML([""]),null,null,c))[b]=a||"solid";this[b]=a},dSetter:function(a,b,c){var d=this.shadows,a=a||[];this.d=a.join(" ");c.path=a=this.pathToVML(a);if(d)for(c=d.length;c--;)d[c].path=d[c].cutOff? +this.cutOffPath(a,d[c].cutOff):a;this.setAttr(b,a)},fillSetter:function(a,b,c){var d=c.nodeName;if(d==="SPAN")c.style.color=a;else if(d!=="IMG")c.filled=a!==Q,this.setAttr("fillcolor",this.renderer.color(a,c,b,this))},opacitySetter:sa,rotationSetter:function(a,b,c){c=c.style;this[b]=c[b]=a;c.left=-u(ea(a*Ca)+1)+"px";c.top=u(Z(a*Ca))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b))},"stroke-widthSetter":function(a,b,c){c.stroked=!!a;this[b]=a;ha(a)&&(a+="px"); +this.setAttr("strokeweight",a)},titleSetter:function(a,b){this.setAttr(b,a)},visibilitySetter:function(a,b,c){a==="inherit"&&(a="visible");this.shadows&&p(this.shadows,function(c){c.style[b]=a});c.nodeName==="DIV"&&(a=a==="hidden"?"-999em":0,hb||(c.style[b]=a?"visible":"hidden"),b="top");c.style[b]=a},xSetter:function(a,b,c){this[b]=a;b==="x"?b="left":b==="y"&&(b="top");this.updateClipping?(this[b]=a,this.updateClipping()):c.style[b]=a},zIndexSetter:function(a,b,c){c.style[b]=a}};X=ka(P,X);X.prototype.ySetter= +X.prototype.widthSetter=X.prototype.heightSetter=X.prototype.xSetter;var ga={Element:X,isIE8:wa.indexOf("MSIE 8.0")>-1,init:function(a,b,c,d){var e;this.alignedObjects=[];d=this.createElement(Ja).css(q(this.getStyle(d),{position:"relative"}));e=d.element;a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.cache={};this.setSize(b,c,!1);if(!y.namespaces.hcv){y.namespaces.add("hcv","urn:schemas-microsoft-com:vml");try{y.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){y.styleSheets[0].cssText+= +"hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=ca(a);return q(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+u(a?e:d)+"px,"+u(a? +f:b)+"px,"+u(a?b:f)+"px,"+u(a?d:e)+"px)"};!a&&hb&&c==="DIV"&&q(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){p(e.members,function(a){a.element&&a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=Q;a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,o=a.linearGradient||a.radialGradient,n,s,m,J,L,x="",a=a.stops,r,v=[],q=function(){h=['']; +Y(e.prepVML(h),null,null,b)};n=a[0];r=a[a.length-1];n[0]>0&&a.unshift([0,n[1]]);r[0]<1&&a.push([1,r[1]]);p(a,function(a,b){g.test(a[1])?(f=ya(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);v.push(a[0]*100+"% "+k);b?(m=l,J=k):(s=l,L=k)});if(c==="fill")if(i==="gradient")c=o.x1||o[0]||0,a=o.y1||o[1]||0,n=o.x2||o[2]||0,o=o.y2||o[3]||0,x='angle="'+(90-U.atan((o-a)/(n-c))*180/ma)+'"',q();else{var j=o.r,t=j*2,u=j*2,y=o.cx,B=o.cy,na=b.radialReference,w,j=function(){na&&(w=d.getBBox(),y+=(na[0]-w.x)/w.width- +0.5,B+=(na[1]-w.y)/w.height-0.5,t*=na[2]/w.width,u*=na[2]/w.height);x='src="'+E.global.VMLRadialGradientURL+'" size="'+t+","+u+'" origin="0.5,0.5" position="'+y+","+B+'" color2="'+L+'" ';q()};d.added?j():d.onAdd=j;j=J}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=ya(a),h=["<",c,' opacity="',f.get("a"),'"/>'],Y(this.prepVML(h),null,null,b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b? +(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","1&&f.attr({x:b,y:c,width:d,height:e});return f},createElement:function(a){return a==="rect"?this.symbol(a):ta.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;G(a,{flip:"x",left:z(d.width)-(e?z(e.top): +1),top:z(d.height)-(e?z(e.left):1),rotation:-90});p(a.childNodes,function(b){c.invertChild(b,a)})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=Z(f),i=ea(f),j=Z(g),k=ea(g);if(g-f===0)return["x"];f=["wa",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+ +c,b+d/2,"e"]},rect:function(a,b,c,d,e){return ta.prototype.symbols[!r(e)||!e.r?"square":"callout"].call(0,a,b,c,d,e)}}};R.VMLRenderer=X=function(){this.init.apply(this,arguments)};X.prototype=w(ta.prototype,ga);Za=X}ta.prototype.measureSpanWidth=function(a,b){var c=y.createElement("span"),d;d=y.createTextNode(a);c.appendChild(d);G(c,b);this.box.appendChild(c);d=c.offsetWidth;Pa(c);return d};var Lb;if(fa)R.CanVGRenderer=X=function(){xa="http://www.w3.org/1999/xhtml"},X.prototype.symbols={},Lb=function(){function a(){var a= +b.length,d;for(d=0;dl[s]?l[s]= +g+j:o||(c=!1);if(o){l=(o=d.justifyToPlot)?d.pos:0;o=o?l+d.len:d.chart.chartWidth;do a+=e?1:-1,n=d.ticks[i[a]];while(i[a]&&(!n||n.label.line!==s));d=n&&n.label.xy&&n.label.xy.x+n.getLabelSides()[e?0:1];e&&!h||f&&h?g+kd&&(c=!1)):g+j>o&&(g=o-j,n&&g+k0&&b.height>0){f=w({align:c&&k&&"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g){q={align:f.textAlign||f.align,rotation:f.rotation};if(r(L))q.zIndex=L;a.label=g=t.text(f.text,0,0,f.useHTML).attr(q).css(f.style).add()}b=[s[1], +s[4],m(s[6],s[1])];s=[s[2],s[5],m(s[7],s[2])];c=Na(b);k=Na(s);g.align(f,!1,{x:c,y:k,width:Ba(b)-c,height:Ba(s)-k});g.show()}else g&&g.hide();return a},destroy:function(){ja(this.axis.plotLinesAndBands,this);delete this.axis;Oa(this)}};la.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:N,lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01, +maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0, +tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return Ga(this.total,-1)},style:N.style}},defaultLeftAxisOptions:{labels:{x:-15,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:20},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-15},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite= +b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.names=[];this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=r(d.linkedTo);this.tickmarkOffset=this.categories&&d.tickmarkPlacement=== +"between"?0.5:0;this.ticks={};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.min=this.max=null;this.crosshair=m(d.crosshair,qa(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;Da(this,a.axes)===-1&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this)); +this.series=this.series||[];if(a.inverted&&c&&this.reversed===t)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)K(this,f,d[f]);if(this.isLog)this.val2lin=za,this.lin2val=ia},setOptions:function(a){this.options=w(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],w(E[this.coll],a))},defaultLabelFormatter:function(){var a= +this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=E.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ia(h,this);else if(c)g=b;else if(d)g=cb(d,b);else if(f&&a>=1E3)for(;f--&&g===t;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=Ga(b/c,-1)+e[f]);g===t&&(g=M(b)>=1E4?Ga(b,0):Ga(b,-1,t,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=null;a.buildStacks&&a.buildStacks();p(a.series,function(c){if(c.visible|| +!b.options.chart.ignoreHiddenSeries){var d;d=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&d<=0&&(d=null);if(a.isXAxis){if(d=c.xData,d.length)a.dataMin=C(m(a.dataMin,d[0]),Na(d)),a.dataMax=v(m(a.dataMax,d[0]),Ba(d))}else{c.getExtremes();e=c.dataMax;c=c.dataMin;if(r(c)&&r(e))a.dataMin=C(m(a.dataMin,c),c),a.dataMax=v(m(a.dataMax,e),e);if(r(d))if(a.dataMin>=d)a.dataMin=d,a.ignoreMinPadding=!0;else if(a.dataMaxg+this.width)o=!0}else if(a=g,c=l-this.right,ih+this.height)o=!0;return o&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,j],b||1)},getLinearTickPositions:function(a, +b,c){var d,e=da(T(b/a)*a),f=da(Ka(c/a)*a),g=[];if(b===c&&ha(b))return[b];for(b=e;b<=f;){g.push(b);b=da(b+a);if(b===d)break;d=b}return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog){e=b.length;for(a=1;a=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===t&&!this.isLog)r(a.min)||r(a.max)?this.minRange=null:(p(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]-i[g-1],f===t||hc&&(h=0);d=v(d,h);f=v(f,Fa(j)?0:h/2);g=v(g,j==="on"?0:h);!a.noSharedTooltip&&r(n)&&(e=r(e)?C(e,n):n)}),h=b.ordinalSlope&&e?b.ordinalSlope/e:1,b.minPointOffset=f*=h,b.pointRangePadding=g*=h,b.pointRange=C(d,c),b.closestPointRange=e;if(a)b.oldTransA=j;b.translationSlope=b.transA=j=b.len/(c+g||1);b.transB=b.horiz?b.left:b.bottom;b.minPixelPadding=j*f},setTickPositions:function(a){var b=this,c=b.chart,d=b.options,e=b.isLog,f=b.isDatetimeAxis,g=b.isXAxis,h=b.isLinked,i=b.options.tickPositioner,j=d.maxPadding, +k=d.minPadding,l=d.tickInterval,o=d.minTickInterval,n=d.tickPixelInterval,s,$=b.categories;h?(b.linkedParent=c[b.coll][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=m(c.min,c.dataMin),b.max=m(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&ra(11,1)):(b.min=m(b.userMin,d.min,b.dataMin),b.max=m(b.userMax,d.max,b.dataMax));if(e)!a&&C(b.min,m(b.dataMin,b.min))<=0&&ra(10,1),b.min=da(za(b.min)),b.max=da(za(b.max));if(b.range&&r(b.max))b.userMin=b.min=v(b.min,b.max-b.range),b.userMax=b.max,b.range= +null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!$&&!b.axisPointRange&&!b.usePercentage&&!h&&r(b.min)&&r(b.max)&&(c=b.max-b.min)){if(!r(d.min)&&!r(b.userMin)&&k&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*k;if(!r(d.max)&&!r(b.userMax)&&j&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*j}if(ha(d.floor))b.min=v(b.min,d.floor);if(ha(d.ceiling))b.max=C(b.max,d.ceiling);b.min===b.max||b.min===void 0||b.max===void 0?b.tickInterval=1:h&&!l&&n===b.linkedParent.options.tickPixelInterval?b.tickInterval= +b.linkedParent.tickInterval:(b.tickInterval=m(l,$?1:(b.max-b.min)*n/v(b.len,n)),!r(l)&&b.lenv(2*b.len,200)&&ra(19,!0),a=f?b.getTimeTicks(b.normalizeTimeTickInterval(b.tickInterval,d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange, +!0):e?b.getLogTickPositions(b.tickInterval,b.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),s&&a.splice(1,a.length-2),b.tickPositions=a;if(!h)e=a[0],f=a[a.length-1],h=b.minPointOffset||0,d.startOnTick?b.min=e:b.min-h>e&&a.shift(),d.endOnTick?b.max=f:b.max+h1E13?1:0.001,b.min-=d,b.max+=d)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=[this.coll,this.pos,this.len].join("-");if(!this.isLinked&& +!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&this.options.alignTicks!==!1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1&&this.min!==t){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e=v(d,m(e.max,d))&&(b=t));this.displayBtn=a!==t||b!==t;this.setExtremes(a,b,!1,t,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=m(b.width,a.plotWidth-c+(b.offsetRight||0)),f=m(b.height,a.plotHeight),g=m(b.top,a.plotTop),b=m(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=parseInt(f,10)/100*a.plotHeight);c.test(g)&&(g=parseInt(g,10)/100*a.plotHeight+a.plotTop); +this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=v(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a=this.isLog;return{min:a?da(ia(this.min)):this.min,max:a?da(ia(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?ia(this.min):this.min,b=b?ia(this.max):this.max;c>a||a===null?a=c:b15&&a<165?"right":a>195&&a<345?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k=0,l,o=0,n=d.title,s=d.labels,$=0,J=b.axisOffset,L=b.clipOffset,x=[-1,1,1,-1][h],q,u=1,w=m(s.maxStaggerLines,5),y,z,A,B,na=h===2?c.fontMetrics(s.style.fontSize).b:0;a.hasData=j=a.hasVisibleSeries||r(a.min)&&r(a.max)&&!!e;a.showAxis=b=j||m(d.showEmpty,!0);a.staggerLines= +a.horiz&&s.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:s.zIndex||7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add();if(j||a.isLinked){a.labelAlign=m(s.align||a.autoLabelAlign(s.rotation));p(e,function(b){f[b]?f[b].addLabel():f[b]=new Sa(a,b)});if(a.horiz&&!a.staggerLines&&w&&!s.rotation){for(q=a.reversed?[].concat(e).reverse():e;u1)a.staggerLines=u}p(e,function(b){if(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign)$=v(f[b].getLabelSize(),$)});if(a.staggerLines)$*=a.staggerLines,a.labelOffset=$}else for(q in f)f[q].destroy(),delete f[q];if(n&&n.text&&n.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(n.text,0,0,n.useHTML).attr({zIndex:7,rotation:n.rotation|| +0,align:n.textAlign||{low:"left",middle:"center",high:"right"}[n.align]}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(n.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(b)k=a.axisTitle.getBBox()[g?"height":"width"],o=m(n.margin,g?5:10),l=n.offset;a.axisTitle[b?"show":"hide"]()}a.offset=x*m(d.offset,J[h]);a.axisTitleMargin=m(l,$+o+($&&x*d.labels[g?"y":"x"]-na));J[h]=v(J[h],a.axisTitleMargin+k+x*a.offset);L[i]=v(L[i],T(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite, +d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=z(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)* +this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,b=a.horiz,c=a.reversed,d=a.chart,e=d.renderer,f=a.options,g=a.isLog,h=a.isLinked,i=a.tickPositions,j,k=a.axisTitle,l=a.ticks,o=a.minorTicks,n=a.alternateBands,s=f.stackLabels,m=f.alternateGridColor,J=a.tickmarkOffset,L=f.lineWidth,x=d.hasRendered&&r(a.oldMin)&&!isNaN(a.oldMin),q=a.hasData,v=a.showAxis,u,w=f.labels.overflow,y=a.justifyLabels=b&&w!== +!1,z;a.labelEdge.length=0;a.justifyToPlot=w==="justify";p([l,o,n],function(a){for(var b in a)a[b].isActive=!1});if(q||h)if(a.minorTickInterval&&!a.categories&&p(a.getMinorTickPositions(),function(b){o[b]||(o[b]=new Sa(a,b,"minor"));x&&o[b].isNew&&o[b].render(null,!0);o[b].render(null,!1,1)}),i.length&&(j=i.slice(),(b&&c||!b&&!c)&&j.reverse(),y&&(j=j.slice(1).concat([j[0]])),p(j,function(b,c){y&&(c=c===j.length-1?0:c+1);if(!h||b>=a.min&&b<=a.max)l[b]||(l[b]=new Sa(a,b)),x&&l[b].isNew&&l[b].render(c, +!0,0.1),l[b].render(c,!1,1)}),J&&a.min===0&&(l[-1]||(l[-1]=new Sa(a,-1,null,!0)),l[-1].render(-1))),m&&p(i,function(b,c){if(c%2===0&&b=A.second&&(i.setMilliseconds(0),i.setSeconds(j>=A.minute?0:k*T(i.getSeconds()/k)));if(j>=A.minute)i[Db](j>=A.hour?0:k*T(i[pb]()/k));if(j>=A.hour)i[Eb](j>=A.day?0:k*T(i[qb]()/k));if(j>=A.day)i[sb](j>=A.month?1:k*T(i[Xa]()/k));j>=A.month&&(i[Fb](j>=A.year?0:k*T(i[fb]()/k)),h=i[gb]());j>=A.year&&(h-=h%k,i[Gb](h));if(j===A.week)i[sb](i[Xa]()-i[rb]()+m(d,1));b=1;Ra&&(i=new Date(i.getTime()+Ra));h=i[gb]();for(var d= +i.getTime(),l=i[fb](),o=i[Xa](),n=g?Ra:(864E5+i.getTimezoneOffset()*6E4)%864E5;d=0.5)a=u(a),g=this.getLinearTickPositions(a, +b,c);else if(a>=0.08)for(var f=T(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];fb&&(!d||k<=c)&&g.push(k),k>c&&(l=!0),k=j}else if(b=ia(b),c=ia(c),a=e[d?"minorTickInterval":"tickInterval"],a=m(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=nb(a,null,mb(a)),g=Ua(this.getLinearTickPositions(a,b,c),za),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval= +a;return g};var Mb=R.Tooltip=function(){this.init.apply(this,arguments)};Mb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=z(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});fa||this.label.shadow(b.shadow);this.shared=b.shared}, +destroy:function(){if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden,h=e.followPointer||e.len>1;q(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?t:g?(2*f.anchorX+c)/3:c,anchorY:h?t:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g&&(M(a-f.x)>1||M(b-f.y)>1))clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(){var a= +this,b;clearTimeout(this.hideTimer);if(!this.isHidden)b=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){a.label.fadeOut();a.isHidden=!0},m(this.options.hideDelay,500)),b&&p(b,function(a){a.setState()}),this.chart.hoverPoints=null},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=0,i,a=qa(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===t&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(p(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=(a.plotLow? +(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:h]);return Ua(c,u)},getPosition:function(a,b,c){var d=this.chart,e=this.distance,f={},g,h=["y",d.chartHeight,b,c.plotY+d.plotTop],i=["x",d.chartWidth,a,c.plotX+d.plotLeft],j=c.ttBelow||d.inverted&&!c.negative||!d.inverted&&c.negative,k=function(a,b,c,d){var g=cb-e)return!1;else f[a]=db-c/2?b-c-2:d-c/2},o=function(a){var b=h;h=i;i=b;g=a},n=function(){k.apply(0,h)!==!1?l.apply(0,i)===!1&&!g&&(o(!0),n()):g?f.x=f.y=0:(o(!0),n())};(d.inverted||this.len>1)&&o();n();return f},defaultFormatter:function(a){var b=this.points||qa(this),c=b[0].series,d;d=[a.tooltipHeaderFormatter(b[0])];p(b,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))}); +d.push(a.options.footerFormat||"");return d.join("")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints,k,l=this.shared;clearTimeout(this.hideTimer);this.followPointer=qa(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];l&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&p(h,function(a){a.setState()}),p(a,function(a){a.setState("hover");j.push(a.getLabelConfig())}),h={x:a[0].category, +y:a[0].y},h.points=j,this.len=j.length,a=a[0]):h=a.getLabelConfig();i=i.call(h,this);h=a.series;this.distance=m(h.tooltipOptions.distance,16);i===!1?this.hide():(this.isHidden&&(bb(d),d.attr("opacity",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow}),this.isHidden=!1);D(c,"tooltipRefresh",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart, +c=this.label,c=(this.options.positioner||this.getPosition).call(this,c.width,c.height,a);this.move(u(c.x),u(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)},tooltipHeaderFormatter:function(a){var b=a.series,c=b.tooltipOptions,d=c.dateTimeLabelFormats,e=c.xDateFormat,f=b.xAxis,g=f&&f.options.type==="datetime"&&ha(a.key),c=c.headerFormat,f=f&&f.closestPointRange,h;if(g&&!e){if(f)for(h in A){if(A[h]>=f||A[h]<=A.day&&a.key%A[h]>0){e=d[h];break}}else e=d.day;e=e||d.year}g&&e&&(c=c.replace("{point.key}","{point.key:"+ +e+"}"));return Ia(c,{point:a,series:b})}};var oa;$a=y.documentElement.ontouchstart!==t;var Wa=R.Pointer=function(a,b){this.init(a,b)};Wa.prototype={init:function(a,b){var c=b.chart,d=c.events,e=fa?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(R.Tooltip&&b.tooltip.enabled)a.tooltip=new Mb(a,b.tooltip), +this.followTouchMove=b.tooltip.followTouchMove;this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||window.event,a=Sb(a);if(!a.target)a.target=a.srcElement;d=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;if(!b)this.chartPosition=b=Rb(this.chart.container);d.pageX===t?(c=v(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return q(a,{chartX:u(c),chartY:u(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};p(this.chart.axes,function(c){b[c.isXAxis?"xAxis": +"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f,g=b.hoverPoint,h=b.hoverSeries,i,j,k=b.chartWidth,l=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!h||!h.noSharedTooltip)){f=[];i=c.length;for(j=0;jk&&f.splice(i,1);if(f.length&&f[0].clientX!==this.hoverX)d.refresh(f,a),this.hoverX=f[0].clientX}c=h&&h.tooltipOptions.followPointer;if(h&&h.tracker&&!c){if((e=h.tooltipPoints[l])&&e!==g)e.onMouseOver(a)}else d&&c&&!d.isHidden&&(h=d.getAnchor([{}],a),d.updatePosition({plotX:h[0],plotY:h[1]}));if(d&&!this._onDocumentMouseMove)this._onDocumentMouseMove= +function(a){if(V[oa])V[oa].pointer.onDocumentMouseMove(a)},K(y,"mousemove",this._onDocumentMouseMove);p(b.axes,function(b){b.drawCrosshair(a,m(e,g))})},reset:function(a){var b=this.chart,c=b.hoverSeries,d=b.hoverPoint,e=b.tooltip,f=e&&e.shared?b.hoverPoints:d;(a=a&&e&&f)&&qa(f)[0].plotX===t&&(a=!1);if(a)e.refresh(f),d&&d.setState(d.state,!0);else{if(d)d.onMouseOut();if(c)c.onMouseOut();e&&e.hide();if(this._onDocumentMouseMove)W(y,"mousemove",this._onDocumentMouseMove),this._onDocumentMouseMove=null; +p(b.axes,function(a){a.hideCrosshair()});this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;p(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY}, +drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,j=b.plotWidth,k=b.plotHeight,l,o=this.mouseDownX,n=this.mouseDownY;dh+j&&(d=h+j);ei+k&&(e=i+k);this.hasDragged=Math.sqrt(Math.pow(o-d,2)+Math.pow(n-e,2));if(this.hasDragged>10){l=b.isInsidePlot(o-h,n-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,f?1:j,g?1:k,0).attr({fill:c.selectionMarkerFill|| +"rgba(69,114,167,0.25)",zIndex:7}).add();this.selectionMarker&&f&&(d-=o,this.selectionMarker.attr({width:M(d),x:(d>0?0:d)+o}));this.selectionMarker&&g&&(d=e-n,this.selectionMarker.attr({height:M(d),y:(d>0?0:d)+n}));l&&!this.selectionMarker&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},a=this.selectionMarker,e=a.attr?a.attr("x"):a.x,f=a.attr?a.attr("y"):a.y,g=a.attr?a.attr("width"): +a.width,h=a.attr?a.attr("height"):a.height,i;if(this.hasDragged||c)p(b.axes,function(a){if(a.zoomEnabled){var b=a.horiz,c=a.toValue(b?e:f),b=a.toValue(b?e+g:f+h);!isNaN(c)&&!isNaN(b)&&(d[a.coll].push({axis:a,min:C(c,b),max:v(c,b)}),i=!0)}}),i&&D(b,"selection",d,function(a){b.zoom(q(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)G(b.container,{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched= +!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){V[oa]&&V[oa].pointer.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,d=b.hoverSeries,a=this.normalize(a,c);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){var a=V[oa];if(a)a.pointer.reset(),a.pointer.chartPosition= +null},onContainerMouseMove:function(a){var b=this.chart;oa=b.index;a=this.normalize(a);b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=H(a,"class"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf("highcharts-container")!==-1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,c=(a=a.relatedTarget|| +a.toElement)&&a.point&&a.point.series;if(b&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&c!==b)b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,a=this.normalize(a);a.cancelBubble=!0;b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(D(c.series,"click",q(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(q(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&D(b,"click",a)))},setDOMEvents:function(){var a= +this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)};b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};K(b,"mouseleave",a.onContainerMouseLeave);ab===1&&K(y,"mouseup",a.onDocumentMouseUp);if($a)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},ab===1&&K(y,"touchend",a.onDocumentTouchEnd)},destroy:function(){var a;W(this.chart.container,"mouseleave",this.onContainerMouseLeave); +ab||(W(y,"mouseup",this.onDocumentMouseUp),W(y,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};q(R.Pointer.prototype,{pinchTranslate:function(a,b,c,d,e,f){(this.zoomHor||this.pinchHor)&&this.pinchTranslateDirection(!0,a,b,c,d,e,f);(this.zoomVert||this.pinchVert)&&this.pinchTranslateDirection(!1,a,b,c,d,e,f)},pinchTranslateDirection:function(a,b,c,d,e,f,g,h){var i=this.chart,j=a?"x":"y",k=a?"X":"Y",l="chart"+k,o=a?"width":"height",n=i["plot"+(a? +"Left":"Top")],s,m,p=h||1,q=i.inverted,x=i.bounds[a?"h":"v"],r=b.length===1,v=b[0][l],u=c[0][l],t=!r&&b[1][l],w=!r&&c[1][l],y,c=function(){!r&&M(v-t)>20&&(p=h||M(u-w)/M(v-t));m=(n-u)/p+v;s=i["plot"+(a?"Width":"Height")]/p};c();b=m;bx.max&&(b=x.max-s,y=!0);y?(u-=0.8*(u-g[j][0]),r||(w-=0.8*(w-g[j][1])),c()):g[j]=[u,w];q||(f[j]=m-n,f[o]=s);f=q?1/p:p;e[o]=s;e[j]=b;d[q?a?"scaleY":"scaleX":"scale"+k]=p;d["translate"+k]=f*n+(u-f*v)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown, +e=b.followTouchMove,f=a.touches,g=f.length,h=b.lastValidTouch,i=b.hasZoom,j=b.selectionMarker,k={},l=g===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||c.runChartClick),o={};(i||e)&&!l&&a.preventDefault();Ua(f,function(a){return b.normalize(a)});if(a.type==="touchstart")p(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,d[1]&&d[1].chartY],p(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding, +e=a.toPixels(a.dataMin),f=a.toPixels(a.dataMax),g=C(e,f),e=v(e,f);b.min=C(a.pos,g-d);b.max=v(a.pos+a.len,e+d)}});else if(d.length){if(!j)b.selectionMarker=j=q({destroy:sa},c.plotBox);b.pinchTranslate(d,f,k,j,o,h);b.hasPinched=i;b.scaleGroups(k,o);!i&&e&&g===1&&this.runPointActions(b.normalize(a))}},onContainerTouchStart:function(a){var b=this.chart;oa=b.index;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),this.pinch(a)):this.reset()): +a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length===1||a.touches.length===2)&&this.pinch(a)},onDocumentTouchEnd:function(a){V[oa]&&V[oa].pointer.drop(a)}});if(I.PointerEvent||I.MSPointerEvent){var ua={},zb=!!I.PointerEvent,Wb=function(){var a,b=[];b.item=function(a){return this[a]};for(a in ua)ua.hasOwnProperty(a)&&b.push({pageX:ua[a].pageX,pageY:ua[a].pageY,target:ua[a].target});return b},Ab=function(a,b,c,d){a=a.originalEvent||a;if((a.pointerType==="touch"|| +a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&V[oa])d(a),d=V[oa].pointer,d[b]({type:c,target:a.currentTarget,preventDefault:sa,touches:Wb()})};q(Wa.prototype,{onContainerPointerDown:function(a){Ab(a,"onContainerTouchStart","touchstart",function(a){ua[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){Ab(a,"onContainerTouchMove","touchmove",function(a){ua[a.pointerId]={pageX:a.pageX,pageY:a.pageY};if(!ua[a.pointerId].target)ua[a.pointerId].target=a.currentTarget})}, +onDocumentPointerUp:function(a){Ab(a,"onContainerTouchEnd","touchend",function(a){delete ua[a.pointerId]})},batchMSEvents:function(a){a(this.chart.container,zb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,zb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(y,zb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}});Ma(Wa.prototype,"init",function(a,b,c){a.call(this,b,c);(this.hasZoom||this.followTouchMove)&&G(b.container,{"-ms-touch-action":Q,"touch-action":Q})}); +Ma(Wa.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&&this.batchMSEvents(K)});Ma(Wa.prototype,"destroy",function(a){this.batchMSEvents(W);a.call(this)})}var lb=R.Legend=function(a,b){this.init(a,b)};lb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=m(b.padding,8),f=b.itemMarginTop||0;this.options=b;if(b.enabled)c.baseline=z(d.fontSize)+3+f,c.itemStyle=d,c.itemHiddenStyle=w(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY= +e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.symbolWidth=m(b.symbolWidth,16),c.pages=[],c.render(),K(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options,d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&a.options.marker,i={fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in i.stroke=h,g=a.convertAttribs(g), +g)d=g[j],d!==t&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;p(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Pa(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()}, +positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c=b.translateY,p(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,G(f,{left:b.translateX+e.checkboxOffset+f.x-20+"px",top:g+"px",display:g>c-6&&g(o||b.chartWidth-2*j-p-d.x))this.itemX=p,this.itemY+=s+this.lastLineHeight+n,this.lastLineHeight=0;this.maxItemWidth=v(this.maxItemWidth,f);this.lastItemY=s+this.itemY+n;this.lastLineHeight=v(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=s+g+n,this.lastLineHeight=g);this.offsetWidth=o||v((e?this.itemX-p-k:f)+j,this.offsetWidth)},getAllItems:function(){var a= +[];p(this.chart.series,function(b){var c=b.options;if(m(c.showInLegend,!r(c.linkedTo)?t:!1,!0))a=a.concat(b.legendItems||(c.legendType==="point"?b.data:b))});return a},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,o=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup); +a.renderTitle();e=a.getAllItems();ob(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;p(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(l||o){g+=k;h+=k;if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp({width:g,height:h})),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor, +"stroke-width":l||0,fill:o||Q}).add(d).shadow(j.shadow),i.isNew=!0;i[f?"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;p(e,function(b){a.positionItem(b)});f&&d.align(q({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h,i=this.clipRect,j=e.navigation,k=m(j.animation,!0),l=j.arrowSize||12,o=this.nav,n=this.pages, +s,q=this.allItems;e.layout==="horizontal"&&(f/=2);g&&(f=C(f,g));n.length=0;if(a>f&&!e.useHTML){this.clipHeight=h=f-20-this.titleHeight-this.padding;this.currentPage=m(this.currentPage,1);this.fullHeight=a;p(q,function(a,b){var c=a._legendItemPos[1],d=u(a.legendItem.getBBox().height),e=n.length;if(!e||c-n[e-1]>h&&(s||c)!==n[e-1])n.push(s||c),e++;b===q.length-1&&c+d-n[e-1]>h&&n.push(c);c!==s&&(s=c)});if(!i)i=b.clipRect=d.clipRect(0,this.padding,9999,0),b.contentGroup.clip(i);i.attr({height:h});if(!o)this.nav= +o=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,l,l).on("click",function(){b.scroll(-1,k)}).add(o),this.pager=d.text("",15,10).css(j.style).add(o),this.down=d.symbol("triangle-down",0,0,l,l).on("click",function(){b.scroll(1,k)}).add(o);b.scroll(0);a=f}else if(o)i.attr({height:c.chartHeight}),o.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a,f=this.clipHeight,g=this.options.navigation, +h=g.activeColor,g=g.inactiveColor,i=this.pager,j=this.padding;e>d&&(e=d);if(e>0)b!==t&&Qa(b,this.chart),this.nav.attr({translateX:j,translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:e===1?g:h}).css({cursor:e===1?"default":"pointer"}),i.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}),this.currentPage=e,this.positionCheckboxes(c)}}; +N=R.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||12;b.legendSymbol=this.chart.renderer.rect(0,a.baseline-5-c/2,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=this.options,c=b.marker,d;d=a.symbolWidth;var e=this.chart.renderer,f=this.legendGroup,a=a.baseline-u(e.fontMetrics(a.options.itemStyle.fontSize).b*0.3),g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle=b.dashStyle;this.legendLine= +e.path(["M",0,a,"L",d,a]).attr(g).add(f)}if(c&&c.enabled!==!1)b=c.radius,this.legendSymbol=d=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0}};(/Trident\/7\.0/.test(wa)||Ta)&&Ma(lb.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});Ya.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=w(E,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing", +d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=V.length;V.push(f);ab++;d.reflow!==!1&&K(f,"load",function(){f.initReflow()});if(e)for(g in e)K(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=fa?!1:m(d.animation,!0);f.pointCount=0;f.counters=new Bb;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=F[a.type||b.type||b.defaultSeriesType])||ra(17,!0);b=new b;b.init(this, +a);return b},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&p(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.isDirtyBox,j=c.length,k=j,l=this.renderer,o=l.isHidden(),n=[];Qa(a,this);o&&this.cloneRenderTo();for(this.layOutTitles();k--;)if(a=c[k],a.options.stacking&& +(g=!0,a.isDirty)){h=!0;break}if(h)for(k=j;k--;)if(a=c[k],a.options.stacking)a.isDirty=!0;p(c,function(a){a.isDirty&&a.options.legendType==="point"&&(f=!0)});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(this.hasCartesianSeries){if(!this.isResizing)this.maxTicks=null,p(b,function(a){a.setScale()});this.adjustTickAmounts();this.getMargins();p(b,function(a){a.isDirty&&(i=!0)});p(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,n.push(function(){D(a,"afterSetExtremes", +q(a.eventArgs,a.getExtremes()));delete a.eventArgs});(i||g)&&a.redraw()})}i&&this.drawChartBox();p(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);l.draw();D(this,"redraw");o&&this.cloneRenderTo(!0);p(n,function(a){a.call()})},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container; +a?b&&(this.renderTo.appendChild(c),Pa(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),G(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),y.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+tb++;if(Fa(a))this.renderTo=a=y.getElementById(a); +a||ra(13,!0);c=z(H(a,"data-highcharts-chart"));!isNaN(c)&&V[c]&&V[c].hasRendered&&V[c].destroy();H(a,"data-highcharts-chart",this.index);a.innerHTML="";!b.skipClone&&!a.offsetWidth&&this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=Y(Ja,{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},q({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"}, +b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=b.forExport?new ta(a,c,d,b.style,!0):new Za(a,c,d,b.style);fa&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,f=m(e.margin,20),g=e.x,h=e.y,i=e.align,j=e.verticalAlign,k=this.titleOffset;this.resetMargins();b=this.axisOffset;if(k&&!r(d[0]))this.plotTop=v(this.plotTop,k+this.options.title.margin+a[0]);if(c.display&&!e.floating)if(i==="right"){if(!r(d[1]))this.marginRight= +v(this.marginRight,c.legendWidth-g+f+a[1])}else if(i==="left"){if(!r(d[3]))this.plotLeft=v(this.plotLeft,c.legendWidth+g+f+a[3])}else if(j==="top"){if(!r(d[0]))this.plotTop=v(this.plotTop,c.legendHeight+h+f+a[0])}else if(j==="bottom"&&!r(d[2]))this.marginBottom=v(this.marginBottom,c.legendHeight-h+f+a[2]);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&p(this.axes,function(a){a.getOffset()});r(d[3])|| +(this.plotLeft+=b[3]);r(d[0])||(this.plotTop+=b[0]);r(d[2])||(this.marginBottom+=b[2]);r(d[1])||(this.marginRight+=b[1]);this.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||jb(d,"width"),f=c.height||jb(d,"height"),c=a?a.target:I,d=function(){if(b.container)b.setSize(e,f,!1),b.hasUserSize=null};if(!b.hasUserSize&&e&&f&&(c===I||c===y)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),a?b.reflowTimeout=setTimeout(d,100):d();b.containerWidth= +e;b.containerHeight=f}},initReflow:function(){var a=this,b=function(b){a.reflow(b)};K(I,"resize",b);K(a,"destroy",function(){W(I,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&D(d,"endResize",null,function(){d.isResizing-=1})};Qa(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(r(a))d.chartWidth=e=v(0,u(a)),d.hasUserSize=!!e;if(r(b))d.chartHeight=f=v(0,u(b));(va?kb:G)(d.container,{width:e+"px",height:f+"px"},va);d.setChartSize(!0);d.renderer.setSize(e, +f,c);d.maxTicks=null;p(d.axes,function(a){a.isDirty=!0;a.setScale()});p(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;D(d,"resize");va===!1?g():setTimeout(g,va&&va.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=u(this.plotLeft);this.plotTop=j=u(this.plotTop);this.plotWidth= +k=v(0,u(d-i-this.marginRight));this.plotHeight=l=v(0,u(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l};d=2*T(this.plotBorderWidth/2);b=Ka(v(d,h[3])/2);c=Ka(v(d,h[0])/2);this.clipBox={x:b,y:c,width:T(this.plotSizeX-v(d,h[1])/2-b),height:T(this.plotSizeY-v(d,h[2])/2-c)};a||p(this.axes,function(a){a.setAxisSize(); +a.setAxisTranslation()})},resetMargins:function(){var a=this.spacing,b=this.margin;this.plotTop=m(b[0],a[0]);this.marginRight=m(b[1],a[1]);this.marginBottom=m(b[2],a[2]);this.plotLeft=m(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage, +o=a.plotBorderWidth||0,n,s=this.plotLeft,m=this.plotTop,p=this.plotWidth,q=this.plotHeight,r=this.plotBox,v=this.clipRect,u=this.clipBox;n=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp({width:c-n,height:d-n}));else{e={fill:j||Q};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(n/2,n/2,c-n,d-n,a.borderRadius,i).attr(e).addClass("highcharts-background").add().shadow(a.shadow)}if(k)f?f.animate(r):this.plotBackground=b.rect(s,m,p,q,0).attr({fill:k}).add().shadow(a.plotShadow); +if(l)h?h.animate(r):this.plotBGImage=b.image(l,s,m,p,q).add();v?v.animate({width:u.width,height:u.height}):this.clipRect=b.clipRect(u);if(o)g?g.animate(g.crisp({x:s,y:m,width:p,height:q})):this.plotBorder=b.rect(s,m,p,q,0,-o).attr({stroke:a.plotBorderColor,"stroke-width":o,fill:Q,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;p(["inverted","angular","polar"],function(g){c=F[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g]; +for(e=d&&d.length;!f&&e--;)(c=F[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;p(b,function(a){a.linkedSeries.length=0});p(b,function(b){var d=b.options.linkedTo;if(Fa(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d})},renderSeries:function(){p(this.series,function(a){a.translate();a.setTooltipPoints&&a.setTooltipPoints();a.render()})},render:function(){var a=this,b=a.axes,c=a.renderer,d=a.options,e=d.labels,f= +d.credits,g;a.setTitle();a.legend=new lb(a,d.legend);a.getStacks();p(b,function(a){a.setScale()});a.getMargins();a.maxTicks=null;p(b,function(a){a.setTickPositions(!0);a.setMaxTicks()});a.adjustTickAmounts();a.getMargins();a.drawChartBox();a.hasCartesianSeries&&p(b,function(a){a.render()});if(!a.seriesGroup)a.seriesGroup=c.g("series-group").attr({zIndex:3}).add();a.renderSeries();e.items&&p(e.items,function(b){var d=q(e.style,b.style),f=z(d.left)+a.plotLeft,g=z(d.top)+a.plotTop+12;delete d.left;delete d.top; +c.text(b.html,f,g).attr({zIndex:2}).css(d).add()});if(f.enabled&&!a.credits)g=f.href,a.credits=c.text(f.text,0,0).on("click",function(){if(g)location.href=g}).attr({align:f.position.align,zIndex:8}).css(f.style).add().align(f.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;D(a,"destroy");V[a.index]=t;ab--;a.renderTo.removeAttribute("data-highcharts-chart");W(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy(); +p("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",W(d),f&&Pa(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!aa&&I==I.top&&y.readyState!=="complete"||fa&&!I.canvg?(fa?Lb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):y.attachEvent("onreadystatechange", +function(){y.detachEvent("onreadystatechange",a.firstRender);y.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender()){a.getContainer();D(a,"init");a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();p(b.series||[],function(b){a.initSeries(b)});a.linkSeries();D(a,"beforeRender");if(R.Pointer)a.pointer=new Wa(a,b);a.render();a.renderer.draw();c&&c.apply(a,[a]);p(a.callbacks,function(b){b.apply(a,[a])});a.cloneRenderTo(!0); +D(a,"load")}},splashArray:function(a,b){var c=b[a],c=ca(c)?c:[c,c,c,c];return[m(b[a+"Top"],c[0]),m(b[a+"Right"],c[1]),m(b[a+"Bottom"],c[2]),m(b[a+"Left"],c[3])]}};Ya.prototype.callbacks=[];X=R.CenteredSeriesMixin={getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,a=[m(b[0],"50%"),m(b[1],"50%"),a.size||"100%",a.innerSize||0],g=C(e,f),h;return Ua(a,function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*z(a)/100: +a)+(d?c:0)})}};var Ea=function(){};Ea.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.pointValKey,a=Ea.prototype.optionsToObject.call(this,a);q(this,a);this.options=this.options?q(this.options,a):a;if(d)this.y=this[d];if(this.x=== +t&&c)this.x=b===t?c.autoIncrement():b;return this},optionsToObject:function(a){var b={},c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if(typeof a==="number"||a===null)b[d[0]]=a;else if(La(a)){if(a.length>e){c=typeof a[0];if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];f++}for(;ga+1&&b.push(d.slice(a+ +1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=w(e,c.series,a);this.tooltipOptions=w(E.tooltip,E.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&d[this.type].tooltip,a.tooltip);e.marker===null&&delete c.marker;return c},getColor:function(){var a=this.options,b=this.userOptions,c=this.chart.options.colors,d=this.chart.counters, +e;e=a.color||ba[this.type].color;if(!e&&!a.colorByPoint)r(b._colorIndex)?a=b._colorIndex:(b._colorIndex=d.color,a=d.color++),e=c[a];this.color=e;d.wrapColor(c.length)},getSymbol:function(){var a=this.userOptions,b=this.options.marker,c=this.chart,d=c.options.symbols,c=c.counters;this.symbol=b.symbol;if(!this.symbol)r(a._symbolIndex)?a=a._symbolIndex:(a._symbolIndex=c.symbol,a=c.symbol++),this.symbol=d[a];if(/^url/.test(this.symbol))b.radius=0;c.wrapSymbol(d.length)},drawLegendSymbol:N.drawLineMarker, +setData:function(a,b,c,d){var e=this,f=e.points,g=f&&f.length||0,h,i=e.options,j=e.chart,k=null,l=e.xAxis,o=l&&!!l.categories,n=e.tooltipPoints,s=i.turboThreshold,q=this.xData,r=this.yData,v=(h=e.pointArrayMap)&&h.length,a=a||[];h=a.length;b=m(b,!0);if(d!==!1&&h&&g===h&&!e.cropped&&!e.hasGroupedData)p(a,function(a,b){f[b].update(a,!1)});else{e.xIncrement=null;e.pointRange=o?1:i.pointRange;e.colorCounter=0;p(this.parallelArrays,function(a){e[a+"Data"].length=0});if(s&&h>s){for(c=0;k===null&&cj||this.forceCrop))if(o=h.min,n=h.max,b[d-1]n)b=[],c=[];else if(b[0]n)e=this.cropData(this.xData,this.yData,o,n),b=e.xData,c=e.yData, +e=e.start,f=!0,k=b.length;for(d=b.length-1;d>=0;d--)a=b[d]-b[d-1],!f&&b[d]>o&&b[d]0&&(g===t||a=c){f=v(0,i-h);break}for(;id){g=i+h;break}return{xData:a.slice(f,g),yData:b.slice(f, +g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],o;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(o=0;o0),j=this.getExtremesFromAll||this.cropped||(c[l+1]||j)>=g&&(c[l-1]||j)<=h,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=k;this.dataMin=m(void 0,Na(e));this.dataMax=m(void 0,Ba(e))},translate:function(){this.processedXData|| +this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i==="between"||ha(i),k=a.threshold,a=0;a0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h).attr(a).add(n)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a, +b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=m(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=ba[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color;f={stroke:g,fill:g};var h=a.points||[],i,j=[],k,l=a.pointAttrToOptions;k=a.hasPointSpecificOptions;var o=b.negativeColor,n=c.lineColor,s=c.fillColor;i=b.turboThreshold;var m;b.marker?(e.radius=e.radius||c.radius+2,e.lineWidth=e.lineWidth||c.lineWidth+1):e.color= +e.color||ya(e.color||g).brighten(e.brightness).get();j[""]=a.convertAttribs(c,f);p(["hover","select"],function(b){j[b]=a.convertAttribs(d[b],j[""])});a.pointAttr=j;g=h.length;if(!i||g1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=b.linecap!=="square",g=this.getGraphPath(), +h=b.negativeColor;h&&c.push(["graphNeg",h]);p(c,function(c,h){var k=c[0],l=a[k];if(l)bb(l),l.animate({d:g});else if(d&&g.length)l={stroke:c[1],"stroke-width":d,fill:Q,zIndex:1},e?l.dashstyle=e:f&&(l["stroke-linecap"]=l["stroke-linejoin"]="round"),a[k]=a.chart.renderer.path(g).attr(l).add(a.group).shadow(!h&&b.shadow)})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j= +b.chartHeight,k=v(e,j),l=this.yAxis;if(d&&(f||g)){d=u(l.toPixels(a.threshold||0,!0));d<0&&(k-=d);a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e});l.reversed?(b=k,e=a):(b=a,e=k);h?(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}}, +invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};p(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)K(c,"resize",a),K(b,"destroy",function(){W(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a=this.chart,b=this.xAxis,c=this.yAxis; +if(a.inverted)b=c,c=this.xAxis;return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=(c=d.animation)&&!!a.animate&&b.renderer.isSVG&&m(c.duration,500)||0,f=a.visible?"visible":"hidden",g=d.zIndex,h=a.hasRendered,i=b.seriesGroup;c=a.plotGroup("group","series",f,g,i);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,i);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted:!1;a.drawGraph&&(a.drawGraph(), +a.clipNeg());a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&a.options.enableMouseTracking!==!1&&a.drawTracker();b.inverted&&a.invertGroups();d.clip!==!1&&!a.sharedClipKey&&!h&&c.clip(b.clipRect);e&&a.animate();if(!h)e?a.animationTimeout=setTimeout(function(){a.afterAnimate()},e):a.afterAnimate();a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth, +height:a.plotHeight}),c.animate({translateX:m(d&&d.left,a.plotLeft),translateY:m(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints&&this.setTooltipPoints(!0);this.render();b&&D(this,"updatedData")}};Hb.prototype={destroy:function(){Oa(this,this.axis)},render:function(a){var b=this.options,c=b.format,c=c?Ia(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:"hidden"}):this.label=this.axis.chart.renderer.text(c,null,null,b.useHTML).css(b.style).attr({align:this.textAlign, +rotation:b.rotation,visibility:"hidden"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,g=c.translate(c.usePercentage?100:this.total,0,0,0,1),c=c.translate(0),c=M(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e[this.options.crop===!1||d.isInsidePlot(f.x,f.y)?"show":"hide"](!0)}};la.prototype.buildStacks=function(){var a= +this.series,b=m(this.options.reversedStacks,!0),c=a.length;if(!this.isXAxis){for(this.usePercentage=!1;c--;)a[b?c:a.length-c-1].setStackedPoints();if(this.usePercentage)for(c=0;cg;)h--;this.updateParallelArrays(d,"splice",h,0,0);this.updateParallelArrays(d, +h);if(j)j[g]=d.name;l.splice(h,0,a);o&&(this.data.splice(h,0,null),this.processData());e.legendType==="point"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),this.updateParallelArrays(d,"shift"),l.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),i.redraw())},remove:function(a,b){var c=this,d=c.chart,a=m(a,!0);if(!c.isRemoving)c.isRemoving=!0,D(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving= +!1},update:function(a,b){var c=this.chart,d=this.type,e=F[d].prototype,f,a=w(this.userOptions,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(f in e)e.hasOwnProperty(f)&&(this[f]=t);q(this,F[a.type||d].prototype);this.init(c,a);m(b,!0)&&c.redraw(!1)}});q(la.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=w(this.userOptions,a);this.destroy(!0);this._addedPlotLB=t;this.init(c,q(a,{events:t}));c.isDirtyBox= +!0;m(b,!0)&&c.redraw()},remove:function(a){for(var b=this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1);ja(b.axes,this);ja(b[c],this);b.options[c].splice(this.options.index,1);p(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;m(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});ga=ka(O);F.line=ga;ba.area=w(S,{threshold:0});var pa=ka(O,{type:"area",getSegments:function(){var a= +[],b=[],c=[],d=this.xAxis,e=this.yAxis,f=e.stacks[this.stackKey],g={},h,i,j=this.points,k=this.options.connectNulls,l,o,n;if(this.options.stacking&&!this.cropped){for(o=0;o=0;d--)g=m(a[d].yBottom,f),da&&i>e?(i=v(a,e),k=2*e-i):ig&&k>e?(k=v(g,e),i=2*e-k):k0.5*a.xAxis.len?0:1),e=a.yAxis,f=a.translatedThreshold=e.getThreshold(c.threshold),g=m(c.minPointLength,5),c=a.getColumnMetrics(),h=c.width,i=a.barW=Ka(v(h,1+2*d)),j=a.pointXOffset=c.offset,k=-(d%2?0.5:0),l=d%2?0.5:1;b.renderer.isVML&&b.inverted&&(l+=1);O.prototype.translate.apply(a);p(a.points,function(c){var d=m(c.yBottom,f),p=C(v(-999-d,c.plotY),e.len+ +999+d),q=c.plotX+j,r=i,t=C(p,d),x;x=v(p,d)-t;M(x)g?d-g:f-(e.translate(c.y,0,1,0,1)<=f?g:0)));c.barX=q;c.pointWidth=h;c.tooltipPos=b.inverted?[e.len-p,a.xAxis.len-q-r/2]:[q+r/2,p];d=M(q)<0.5;r=u(q+r)+k;q=u(q)+k;r-=q;p=M(t)<0.5;x=u(t+x)+l;t=u(t)+l;x-=t;d&&(q+=1,r-=1);p&&(t-=1,x+=1);c.shapeType="rect";c.shapeArgs={x:q,y:t,width:r,height:x}})},getSymbol:sa,drawLegendSymbol:N.drawRectangle,drawGraph:sa,drawPoints:function(){var a=this,b=this.chart,c=a.options,d=b.renderer,e=c.animationLimit|| +250,f,g,h;p(a.points,function(i){var j=i.plotY,k=i.graphic;if(j!==t&&!isNaN(j)&&i.y!==null)f=i.shapeArgs,h=r(a.borderWidth)?{"stroke-width":a.borderWidth}:{},g=i.pointAttr[i.selected?"select":""]||a.pointAttr[""],k?(bb(k),k.attr(h)[b.pointCount● {series.name}
', +pointFormat:"x: {point.x}
y: {point.y}
"},stickyTracking:!1});pa=ka(O,{type:"scatter",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["markerGroup"],takeOrdinalPosition:!1,singularTooltips:!0,drawGraph:function(){this.options.lineWidth&&O.prototype.drawGraph.call(this)}});F.scatter=pa;ba.pie=w(S,{borderColor:"#FFFFFF",borderWidth:1,center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},ignoreHiddenPoint:!0, +legendType:"point",marker:null,size:null,showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});S={type:"pie",isCartesian:!1,pointClass:ka(Ea,{init:function(){Ea.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;q(a,{visible:a.visible!==!1,name:m(a.name,"Slice")});b=function(b){a.slice(b.type==="select")};K(a,"select",b);K(a,"unselect",b);return a},setVisible:function(a){var b=this,c=b.series,d=c.chart;b.visible=b.options.visible= +a=a===t?!b.visible:a;c.options.data[Da(b,c.data)]=b.options;p(["graphic","dataLabel","connector","shadowGroup"],function(c){if(b[c])b[c][a?"show":"hide"](!0)});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;Qa(c,d.chart);m(b,!0);this.sliced=this.options.sliced=a=r(a)?a:!this.sliced;d.options.data[Da(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a); +this.shadowGroup&&this.shadowGroup.animate(a)},haloPath:function(a){var b=this.shapeArgs,c=this.series.chart;return this.series.chart.renderer.symbols.arc(c.plotLeft+b.x,c.plotTop+b.y,b.r+a,b.r+a,{innerR:this.shapeArgs.r,start:b.start,end:b.end})}}),requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},singularTooltips:!0,getColor:sa,animate:function(a){var b=this,c=b.points,d= +b.startAngleRad;if(!a)p(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b,c,d){O.prototype.setData.call(this,a,!1,c,d);this.processData();this.generatePoints();m(b,!0)&&this.chart.redraw(c)},generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;O.prototype.generatePoints.call(this);c=this.points;d=c.length;for(a=0;a0?e.y/b*100:0,e.total=b},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=ma/180*(i-90),i=(this.endAngleRad=ma/180*(m(c.endAngle,i+360)-90))-j,k=this.points,l=c.dataLabels.distance,c=c.ignoreHiddenPoint,o,n=k.length,p;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=U.asin(C((b-a[1])/(a[2]/2+l),1));return a[0]+(c?-1:1)*Z(h)*(a[2]/ +2+l)};for(o=0;o1.5*ma?h-=2*ma:h<-ma/2&&(h+=2*ma);p.slicedTranslation={translateX:u(Z(h)*d),translateY:u(ea(h)*d)};f=Z(h)*a[2]/2;g=ea(h)*a[2]/2;p.tooltipPos=[a[0]+f*0.7,a[1]+g*0.7];p.half=h<-ma/2||h>ma/2?1:0;p.angle=h;e=C(e,l/2);p.labelPos=[a[0]+f+Z(h)*l,a[1]+g+ea(h)*l,a[0]+f+Z(h)*e,a[1]+g+ea(h)*e,a[0]+f,a[1]+g,l<0? +"center":p.half?"right":"left",h]}},drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);p(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g("shadow").add(a.shadowGroup);c=h.sliced?h.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(q(g,c)):h.graphic=d=b[h.shapeType](g).setRadialReference(a.center).attr(h.pointAttr[h.selected?"select": +""]).attr({"stroke-linejoin":"round"}).attr(c).add(a.group).shadow(e,f);h.visible!==void 0&&h.setVisible(h.visible)})},sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawLegendSymbol:N.drawRectangle,getCenter:X.getCenter,getSymbol:sa};S=ka(O,S);F.pie=S;O.prototype.drawDataLabels=function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h,i;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),i=a.plotGroup("dataLabelsGroup", +"data-labels","hidden",d.zIndex||6),!a.hasRendered&&m(d.defer,!0)&&(i.attr({opacity:0}),K(a,"afterAnimate",function(){a.dataLabelsGroup.show()[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,p(e,function(b){var e,l=b.dataLabel,o,n,p=b.connector,u=!0;f=b.options&&b.options.dataLabels;e=m(f&&f.enabled,g.enabled);if(l&&!e)b.dataLabel=l.destroy();else if(e){d=w(g,f);e=d.rotation;o=b.getLabelConfig();h=d.format?Ia(d.format,o):d.formatter.call(o,d);d.style.color=m(d.color,d.style.color, +a.color,"black");if(l)if(r(h))l.attr({text:h}),u=!1;else{if(b.dataLabel=l=l.destroy(),p)b.connector=p.destroy()}else if(r(h)){l={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:e,padding:d.padding,zIndex:1};for(n in l)l[n]===t&&delete l[n];l=b.dataLabel=a.chart.renderer[e?"text":"label"](h,0,-999,null,null,null,d.useHTML).attr(l).css(q(d.style,c&&{cursor:c})).add(i).shadow(d.shadow)}l&&a.alignDataLabel(b,l,d,null,u)}})};O.prototype.alignDataLabel= +function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=m(a.plotX,-999),i=m(a.plotY,-999),j=b.getBBox();if(a=this.visible&&(a.series.forceDL||f.isInsidePlot(h,u(i),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-1,g)))d=q({x:g?f.plotWidth-i:h,y:u(g?f.plotHeight-h:i),width:0,height:0},d),q(c,{width:j.width,height:j.height}),c.rotation?(g={align:c.align,x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2},b[e?"attr":"animate"](g)):(b.align(c,null,d),g=b.alignAttr,m(c.overflow,"justify")==="justify"?this.justifyDataLabel(b, +c,g,j,d,e):m(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)));if(!a)b.attr({y:-999}),b.placed=!1};O.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k;j=c.x;if(j<0)h==="right"?b.align="left":b.x=-j,k=!0;j=c.x+d.width;if(j>g.plotWidth)h==="left"?b.align="right":b.x=g.plotWidth-j,k=!0;j=c.y;if(j<0)i==="bottom"?b.verticalAlign="top":b.y=-j,k=!0;j=c.y+d.height;if(j>g.plotHeight)i==="top"?b.verticalAlign="bottom":b.y=g.plotHeight- +j,k=!0;if(k)a.placed=!f,a.align(b,null,e)};if(F.pie)F.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=m(e.connectorPadding,10),g=m(e.connectorWidth,1),h=d.plotWidth,d=d.plotHeight,i,j,k=m(e.softConnector,!0),l=e.distance,o=a.center,n=o[2]/2,q=o[1],r=l>0,t,w,x,y,z=[[],[]],A,C,G,D,B,F=[0,0,0,0],N=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){O.prototype.drawDataLabels.apply(a);p(b,function(a){a.dataLabel&&a.visible&&z[a.half].push(a)}); +for(D=0;!y&&b[D];)y=b[D]&&b[D].dataLabel&&(b[D].dataLabel.getBBox().height||21),D++;for(D=2;D--;){var b=[],K=[],H=z[D],I=H.length,E;a.sortByAngle(H,D-0.5);if(l>0){for(B=q-n-l;B<=q+n+l;B+=y)b.push(B);w=b.length;if(I>w){c=[].concat(H);c.sort(N);for(B=I;B--;)c[B].rank=B;for(B=I;B--;)H[B].rank>=w&&H.splice(B,1);I=H.length}for(B=0;B0){if(w=K.pop(),E=w.i,C=w.y,c>C&&b[E+1]!==null||ch-f&&(F[1]=v(u(A+w-h+f),F[1])),C-y/2<0?F[0]= +v(u(-C+y/2),F[0]):C+y/2>d&&(F[2]=v(u(C+y/2-d),F[2]))}}if(Ba(F)===0||this.verifyDataLabelOverflow(F))this.placeDataLabels(),r&&g&&p(this.points,function(b){i=b.connector;x=b.labelPos;if((t=b.dataLabel)&&t._pos)G=t._attr.visibility,A=t.connX,C=t.connY,j=k?["M",A+(x[6]==="left"?5:-5),C,"C",A,C,2*x[2]-x[4],2*x[3]-x[5],x[2],x[3],"L",x[4],x[5]]:["M",A+(x[6]==="left"?5:-5),C,"L",x[2],x[3],"L",x[4],x[5]],i?(i.animate({d:j}),i.attr("visibility",G)):b.connector=i=a.chart.renderer.path(j).attr({"stroke-width":g, +stroke:e.connectorColor||b.color||"#606060",visibility:G}).add(a.dataLabelsGroup);else if(i)b.connector=i.destroy()})}},F.pie.prototype.placeDataLabels=function(){p(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999})})},F.pie.prototype.alignDataLabel=sa,F.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||80,f;d[0]!==null?e=v(b[2]-v(a[1],a[3]),c):(e=v(b[2]- +a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);d[1]!==null?e=v(C(e,b[2]-v(a[0],a[2])),c):(e=v(C(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);em(this.translatedThreshold,f.plotSizeY),j=m(c.inside,!!this.options.stacking);if(h&& +(d=w(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=m(c.align,!g||j?"center":i?"right":"left");c.verticalAlign=m(c.verticalAlign,g||j?"middle":i?"top":"bottom");O.prototype.alignDataLabel.call(this,a,b,c,d,e)};S=R.TrackerMixin={drawTrackerPoint:function(){var a=this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver(); +for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==t&&e!==b.hoverPoint)e.onMouseOver(c)};p(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)p(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),$a))a[b].on("touchstart",f)}),a._hasTracking=!0},drawTrackerGraph:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath: +a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,n=function(){if(f.hoverSeries!==a)a.onMouseOver()},q="rgba(192,192,192,"+(aa?1.0E-4:0.002)+")";if(e&&!c)for(m=e+1;m--;)d[m]==="M"&&d.splice(m+1,0,d[m+1]-i,d[m+2],"L"),(m&&d[m]==="M"||m===e)&&d.splice(m,0,"L",d[m-2]+i,d[m-1]);for(m=0;mC(k.dataMin,k.min)&&i=f.min&&c<=f.max){h=b[i+1];c=d===t?0:d+1;for(d=b[i+1]?C(v(0,T((e.clientX+ +(h?h.wrappedClientX||h.clientX:g))/2)),g):g;c>=0&&c<=d;)j[c++]=e}this.tooltipPoints=j}},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===t?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;D(this,a?"select":"unselect")},drawTracker:S.drawTrackerGraph});q(R,{Axis:la,Chart:Ya,Color:ya,Point:Ea,Tick:Sa,Renderer:Za,Series:O,SVGElement:P,SVGRenderer:ta,arrayMin:Na,arrayMax:Ba,charts:V,dateFormat:cb,format:Ia,pathAnim:ub,getOptions:function(){return E}, +hasBidiBug:Nb,isTouchDevice:Jb,numberFormat:Ga,seriesTypes:F,setOptions:function(a){E=w(!0,E,a);Cb();return E},addEvent:K,removeEvent:W,createElement:Y,discardElement:Pa,css:G,each:p,extend:q,map:Ua,merge:w,pick:m,splat:qa,extendClass:ka,pInt:z,wrap:Ma,svg:aa,canvas:fa,vml:!aa&&!fa,product:"Highcharts",version:"4.0.1"})})(); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts.src.js b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts.src.js old mode 100644 new mode 100755 index bc313485904..9e063bd7d90 --- a/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts.src.js +++ b/java/com.sap.sailing.gwt.ui/highcharts/js/highcharts.src.js @@ -1,15111 +1,17672 @@ -// ==ClosureCompiler== -// @compilation_level SIMPLE_OPTIMIZATIONS - -/** - * @license Highcharts JS v2.3.3 (2012-10-04) - * - * (c) 2009-2011 Torstein Hønsi - * - * License: www.highcharts.com/license - */ - -// JSLint options: -/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */ - -(function () { -// encapsulated variables -var UNDEFINED, - doc = document, - win = window, - math = Math, - mathRound = math.round, - mathFloor = math.floor, - mathCeil = math.ceil, - mathMax = math.max, - mathMin = math.min, - mathAbs = math.abs, - mathCos = math.cos, - mathSin = math.sin, - mathPI = math.PI, - deg2rad = mathPI * 2 / 360, - - - // some variables - userAgent = navigator.userAgent, - isOpera = win.opera, - isIE = /msie/i.test(userAgent) && !isOpera, - docMode8 = doc.documentMode === 8, - isWebKit = /AppleWebKit/.test(userAgent), - isFirefox = /Firefox/.test(userAgent), - SVG_NS = 'http://www.w3.org/2000/svg', - hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect, - hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38 - useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext, - Renderer, - hasTouch = doc.documentElement.ontouchstart !== UNDEFINED, - symbolSizes = {}, - idCounter = 0, - garbageBin, - defaultOptions, - dateFormat, // function - globalAnimation, - pathAnim, - timeUnits, - noop = function () {}, - - // some constants for frequently used strings - DIV = 'div', - ABSOLUTE = 'absolute', - RELATIVE = 'relative', - HIDDEN = 'hidden', - PREFIX = 'highcharts-', - VISIBLE = 'visible', - PX = 'px', - NONE = 'none', - M = 'M', - L = 'L', - /* - * Empirical lowest possible opacities for TRACKER_FILL - * IE6: 0.002 - * IE7: 0.002 - * IE8: 0.002 - * IE9: 0.00000000001 (unlimited) - * FF: 0.00000000001 (unlimited) - * Chrome: 0.000001 - * Safari: 0.000001 - * Opera: 0.00000000001 (unlimited) - */ - TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable - //TRACKER_FILL = 'rgba(192,192,192,0.5)', - NORMAL_STATE = '', - HOVER_STATE = 'hover', - SELECT_STATE = 'select', - MILLISECOND = 'millisecond', - SECOND = 'second', - MINUTE = 'minute', - HOUR = 'hour', - DAY = 'day', - WEEK = 'week', - MONTH = 'month', - YEAR = 'year', - - // constants for attributes - FILL = 'fill', - LINEAR_GRADIENT = 'linearGradient', - STOPS = 'stops', - STROKE = 'stroke', - STROKE_WIDTH = 'stroke-width', - - // time methods, changed based on whether or not UTC is used - makeTime, - getMinutes, - getHours, - getDay, - getDate, - getMonth, - getFullYear, - setMinutes, - setHours, - setDate, - setMonth, - setFullYear, - - - // lookup over the types and the associated classes - seriesTypes = {}; - -// The Highcharts namespace -win.Highcharts = {}; - -/** - * Extend an object with the members of another - * @param {Object} a The object to be extended - * @param {Object} b The object to add to the first one - */ -function extend(a, b) { - var n; - if (!a) { - a = {}; - } - for (n in b) { - a[n] = b[n]; - } - return a; -} - -/** - * Take an array and turn into a hash with even number arguments as keys and odd numbers as - * values. Allows creating constants for commonly used style properties, attributes etc. - * Avoid it in performance critical situations like looping - */ -function hash() { - var i = 0, - args = arguments, - length = args.length, - obj = {}; - for (; i < length; i++) { - obj[args[i++]] = args[i]; - } - return obj; -} - -/** - * Shortcut for parseInt - * @param {Object} s - * @param {Number} mag Magnitude - */ -function pInt(s, mag) { - return parseInt(s, mag || 10); -} - -/** - * Check for string - * @param {Object} s - */ -function isString(s) { - return typeof s === 'string'; -} - -/** - * Check for object - * @param {Object} obj - */ -function isObject(obj) { - return typeof obj === 'object'; -} - -/** - * Check for array - * @param {Object} obj - */ -function isArray(obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; -} - -/** - * Check for number - * @param {Object} n - */ -function isNumber(n) { - return typeof n === 'number'; -} - -function log2lin(num) { - return math.log(num) / math.LN10; -} -function lin2log(num) { - return math.pow(10, num); -} - -/** - * Remove last occurence of an item from an array - * @param {Array} arr - * @param {Mixed} item - */ -function erase(arr, item) { - var i = arr.length; - while (i--) { - if (arr[i] === item) { - arr.splice(i, 1); - break; - } - } - //return arr; -} - -/** - * Returns true if the object is not null or undefined. Like MooTools' $.defined. - * @param {Object} obj - */ -function defined(obj) { - return obj !== UNDEFINED && obj !== null; -} - -/** - * Set or get an attribute or an object of attributes. Can't use jQuery attr because - * it attempts to set expando properties on the SVG element, which is not allowed. - * - * @param {Object} elem The DOM element to receive the attribute(s) - * @param {String|Object} prop The property or an abject of key-value pairs - * @param {String} value The value if a single property is set - */ -function attr(elem, prop, value) { - var key, - setAttribute = 'setAttribute', - ret; - - // if the prop is a string - if (isString(prop)) { - // set the value - if (defined(value)) { - - elem[setAttribute](prop, value); - - // get the value - } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo... - ret = elem.getAttribute(prop); - } - - // else if prop is defined, it is a hash of key/value pairs - } else if (defined(prop) && isObject(prop)) { - for (key in prop) { - elem[setAttribute](key, prop[key]); - } - } - return ret; -} -/** - * Check if an element is an array, and if not, make it into an array. Like - * MooTools' $.splat. - */ -function splat(obj) { - return isArray(obj) ? obj : [obj]; -} - - -/** - * Return the first value that is defined. Like MooTools' $.pick. - */ -function pick() { - var args = arguments, - i, - arg, - length = args.length; - for (i = 0; i < length; i++) { - arg = args[i]; - if (typeof arg !== 'undefined' && arg !== null) { - return arg; - } - } -} - -/** - * Set CSS on a given element - * @param {Object} el - * @param {Object} styles Style object with camel case property names - */ -function css(el, styles) { - if (isIE) { - if (styles && styles.opacity !== UNDEFINED) { - styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')'; - } - } - extend(el.style, styles); -} - -/** - * Utility function to create element with attributes and styles - * @param {Object} tag - * @param {Object} attribs - * @param {Object} styles - * @param {Object} parent - * @param {Object} nopad - */ -function createElement(tag, attribs, styles, parent, nopad) { - var el = doc.createElement(tag); - if (attribs) { - extend(el, attribs); - } - if (nopad) { - css(el, {padding: 0, border: NONE, margin: 0}); - } - if (styles) { - css(el, styles); - } - if (parent) { - parent.appendChild(el); - } - return el; -} - -/** - * Extend a prototyped class by new members - * @param {Object} parent - * @param {Object} members - */ -function extendClass(parent, members) { - var object = function () {}; - object.prototype = new parent(); - extend(object.prototype, members); - return object; -} - -/** - * How many decimals are there in a number - */ -function getDecimals(number) { - - number = (number || 0).toString(); - - return number.indexOf('.') > -1 ? - number.split('.')[1].length : - 0; -} - -/** - * Format a number and return a string based on input settings - * @param {Number} number The input number to format - * @param {Number} decimals The amount of decimals - * @param {String} decPoint The decimal point, defaults to the one given in the lang options - * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options - */ -function numberFormat(number, decimals, decPoint, thousandsSep) { - var lang = defaultOptions.lang, - // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/ - n = number, - c = decimals === -1 ? - getDecimals(number) : - (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals), - d = decPoint === undefined ? lang.decimalPoint : decPoint, - t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, - s = n < 0 ? "-" : "", - i = String(pInt(n = mathAbs(+n || 0).toFixed(c))), - j = i.length > 3 ? i.length % 3 : 0; - - return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + - (c ? d + mathAbs(n - i).toFixed(c).slice(2) : ""); -} - -/** - * Pad a string to a given length by adding 0 to the beginning - * @param {Number} number - * @param {Number} length - */ -function pad(number, length) { - // Create an array of the remaining length +1 and join it with 0's - return new Array((length || 2) + 1 - String(number).length).join(0) + number; -} - -/** - * Wrap a method with extended functionality, preserving the original function - * @param {Object} obj The context object that the method belongs to - * @param {String} method The name of the method to extend - * @param {Function} func A wrapper function callback. This function is called with the same arguments - * as the original function, except that the original function is unshifted and passed as the first - * argument. - */ -function wrap(obj, method, func) { - var proceed = obj[method]; - obj[method] = function () { - var args = Array.prototype.slice.call(arguments); - args.unshift(proceed); - return func.apply(this, args); - }; -} - -/** - * Based on http://www.php.net/manual/en/function.strftime.php - * @param {String} format - * @param {Number} timestamp - * @param {Boolean} capitalize - */ -dateFormat = function (format, timestamp, capitalize) { - if (!defined(timestamp) || isNaN(timestamp)) { - return 'Invalid date'; - } - format = pick(format, '%Y-%m-%d %H:%M:%S'); - - var date = new Date(timestamp), - key, // used in for constuct below - // get the basic time values - hours = date[getHours](), - day = date[getDay](), - dayOfMonth = date[getDate](), - month = date[getMonth](), - fullYear = date[getFullYear](), - lang = defaultOptions.lang, - langWeekdays = lang.weekdays, - /* // uncomment this and the 'W' format key below to enable week numbers - weekNumber = function () { - var clone = new Date(date.valueOf()), - day = clone[getDay]() == 0 ? 7 : clone[getDay](), - dayNumber; - clone.setDate(clone[getDate]() + 4 - day); - dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000); - return 1 + mathFloor(dayNumber / 7); - }, - */ - - // list all format keys - replacements = { - - // Day - 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon' - 'A': langWeekdays[day], // Long weekday, like 'Monday' - 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31 - 'e': dayOfMonth, // Day of the month, 1 through 31 - - // Week (none implemented) - //'W': weekNumber(), - - // Month - 'b': lang.shortMonths[month], // Short month, like 'Jan' - 'B': lang.months[month], // Long month, like 'January' - 'm': pad(month + 1), // Two digit month number, 01 through 12 - - // Year - 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009 - 'Y': fullYear, // Four digits year, like 2009 - - // Time - 'H': pad(hours), // Two digits hours in 24h format, 00 through 23 - 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11 - 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12 - 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59 - 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM - 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM - 'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59 - 'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby) - }; - - - // do the replaces - for (key in replacements) { - format = format.replace('%' + key, replacements[key]); - } - - // Optionally capitalize the string and return - return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format; -}; - -/** - * 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 - */ -function normalizeTickInterval(interval, multiples, magnitude, options) { - var normalized, i; - - // round to a tenfold of 1, 2, 2.5 or 5 - magnitude = pick(magnitude, 1); - normalized = interval / magnitude; - - // multiples for a linear scale - if (!multiples) { - multiples = [1, 2, 2.5, 5, 10]; - - // the allowDecimals option - if (options && options.allowDecimals === false) { - if (magnitude === 1) { - multiples = [1, 2, 5, 10]; - } else if (magnitude <= 0.1) { - multiples = [1 / magnitude]; - } - } - } - - // normalize the interval to the nearest multiple - for (i = 0; i < multiples.length; i++) { - interval = multiples[i]; - if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) { - break; - } - } - - // multiply back to the correct magnitude - interval *= magnitude; - - return interval; -} - -/** - * 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. - * #662, #697. - */ -function normalizeTimeTickInterval(tickInterval, unitsOption) { - var units = unitsOption || [[ - MILLISECOND, // unit name - [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples - ], [ - SECOND, - [1, 2, 5, 10, 15, 30] - ], [ - MINUTE, - [1, 2, 5, 10, 15, 30] - ], [ - HOUR, - [1, 2, 3, 4, 6, 8, 12] - ], [ - DAY, - [1, 2] - ], [ - WEEK, - [1, 2] - ], [ - MONTH, - [1, 2, 3, 4, 6] - ], [ - YEAR, - null - ]], - unit = units[units.length - 1], // default unit is years - interval = timeUnits[unit[0]], - multiples = unit[1], - count, - i; - - // loop through the units to find the one that best fits the tickInterval - for (i = 0; i < units.length; i++) { - unit = units[i]; - interval = timeUnits[unit[0]]; - multiples = unit[1]; - - - if (units[i + 1]) { - // lessThan is in the middle between the highest multiple and the next unit. - var lessThan = (interval * multiples[multiples.length - 1] + - timeUnits[units[i + 1][0]]) / 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] && tickInterval < 5 * interval) { - multiples = [1, 2, 5]; - } - - // prevent 2.5 years intervals, though 25, 250 etc. are allowed - if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) { - multiples = [1, 2, 5]; - } - - // get the count - count = normalizeTickInterval(tickInterval / interval, multiples); - - return { - unitRange: interval, - count: count, - unitName: unit[0] - }; -} - -/** - * 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 - */ -function getTimeTicks(normalizedInterval, min, max, startOfWeek) { - var tickPositions = [], - i, - higherRanks = {}, - useUTC = defaultOptions.global.useUTC, - minYear, // used in months and years as a basis for Date.UTC() - minDate = new Date(min), - interval = normalizedInterval.unitRange, - count = normalizedInterval.count; - - - - if (interval >= timeUnits[SECOND]) { // second - minDate.setMilliseconds(0); - minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 : - count * mathFloor(minDate.getSeconds() / count)); - } - - if (interval >= timeUnits[MINUTE]) { // minute - minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 : - count * mathFloor(minDate[getMinutes]() / count)); - } - - if (interval >= timeUnits[HOUR]) { // hour - minDate[setHours](interval >= timeUnits[DAY] ? 0 : - count * mathFloor(minDate[getHours]() / count)); - } - - if (interval >= timeUnits[DAY]) { // day - minDate[setDate](interval >= timeUnits[MONTH] ? 1 : - count * mathFloor(minDate[getDate]() / count)); - } - - if (interval >= timeUnits[MONTH]) { // month - minDate[setMonth](interval >= timeUnits[YEAR] ? 0 : - count * mathFloor(minDate[getMonth]() / count)); - minYear = minDate[getFullYear](); - } - - if (interval >= timeUnits[YEAR]) { // year - minYear -= minYear % count; - minDate[setFullYear](minYear); - } - - // week is a special case that runs outside the hierarchy - if (interval === timeUnits[WEEK]) { - // get start of current week, independent of count - minDate[setDate](minDate[getDate]() - minDate[getDay]() + - pick(startOfWeek, 1)); - } - - - // get tick positions - i = 1; - minYear = minDate[getFullYear](); - var time = minDate.getTime(), - minMonth = minDate[getMonth](), - minDateDate = minDate[getDate](), - timezoneOffset = useUTC ? - 0 : - (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950 - - // iterate and add tick positions at appropriate values - while (time < max) { - tickPositions.push(time); - - // if the interval is years, use Date.UTC to increase years - if (interval === timeUnits[YEAR]) { - time = makeTime(minYear + i * count, 0); - - // if the interval is months, use Date.UTC to increase months - } else if (interval === timeUnits[MONTH]) { - 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] || interval === timeUnits[WEEK])) { - time = makeTime(minYear, minMonth, minDateDate + - i * count * (interval === timeUnits[DAY] ? 1 : 7)); - - // else, the interval is fixed and we use simple addition - } else { - time += interval * count; - - // mark new days if the time is dividable by day - if (interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset) { - higherRanks[time] = DAY; - } - } - - i++; - } - - // push the last time - tickPositions.push(time); - - // record information on the chosen unit - for dynamic label formatter - tickPositions.info = extend(normalizedInterval, { - higherRanks: higherRanks, - totalRange: interval * count - }); - - return tickPositions; -} - -/** - * Helper class that contains variuos counters that are local to the chart. - */ -function ChartCounters() { - this.color = 0; - this.symbol = 0; -} - -ChartCounters.prototype = { - /** - * Wraps the color counter if it reaches the specified length. - */ - wrapColor: function (length) { - if (this.color >= length) { - this.color = 0; - } - }, - - /** - * Wraps the symbol counter if it reaches the specified length. - */ - wrapSymbol: function (length) { - if (this.symbol >= length) { - this.symbol = 0; - } - } -}; - - -/** - * Utility method that sorts an object array and keeping the order of equal items. - * ECMA script standard does not specify the behaviour when items are equal. - */ -function stableSort(arr, sortFunction) { - var length = arr.length, - sortValue, - i; - - // Add index to each item - for (i = 0; i < length; i++) { - arr[i].ss_i = i; // stable sort index - } - - arr.sort(function (a, b) { - sortValue = sortFunction(a, b); - return sortValue === 0 ? a.ss_i - b.ss_i : sortValue; - }); - - // Remove index from items - for (i = 0; i < length; i++) { - delete arr[i].ss_i; // stable sort index - } -} - -/** - * Non-recursive method to find the lowest member of an array. Math.min raises a maximum - * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This - * method is slightly slower, but safe. - */ -function arrayMin(data) { - var i = data.length, - min = data[0]; - - while (i--) { - if (data[i] < min) { - min = data[i]; - } - } - return min; -} - -/** - * Non-recursive method to find the lowest member of an array. Math.min raises a maximum - * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This - * method is slightly slower, but safe. - */ -function arrayMax(data) { - var i = data.length, - max = data[0]; - - while (i--) { - if (data[i] > max) { - max = data[i]; - } - } - return max; -} - -/** - * Utility method that destroys any SVGElement or VMLElement that are properties on the given object. - * It loops all properties and invokes destroy if there is a destroy method. The property is - * then delete'ed. - * @param {Object} The object to destroy properties on - * @param {Object} Exception, do not destroy this property, only delete it. - */ -function destroyObjectProperties(obj, except) { - var n; - for (n in obj) { - // If the object is non-null and destroy is defined - if (obj[n] && obj[n] !== except && obj[n].destroy) { - // Invoke the destroy - obj[n].destroy(); - } - - // Delete the property from the object. - delete obj[n]; - } -} - - -/** - * Discard an element by moving it to the bin and delete - * @param {Object} The HTML node to discard - */ -function discardElement(element) { - // create a garbage bin element, not part of the DOM - if (!garbageBin) { - garbageBin = createElement(DIV); - } - - // move the node and empty bin - if (element) { - garbageBin.appendChild(element); - } - garbageBin.innerHTML = ''; -} - -/** - * Provide error messages for debugging, with links to online explanation - */ -function error(code, stop) { - var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code; - if (stop) { - throw msg; - } else if (win.console) { - console.log(msg); - } -} - -/** - * Fix JS round off float errors - * @param {Number} num - */ -function correctFloat(num) { - return parseFloat( - num.toPrecision(14) - ); -} - -/** - * Set the global animation to either a given value, or fall back to the - * given chart's animation option - * @param {Object} animation - * @param {Object} chart - */ -function setAnimation(animation, chart) { - globalAnimation = pick(animation, chart.animation); -} - -/** - * The time unit lookup - */ -/*jslint white: true*/ -timeUnits = hash( - MILLISECOND, 1, - SECOND, 1000, - MINUTE, 60000, - HOUR, 3600000, - DAY, 24 * 3600000, - WEEK, 7 * 24 * 3600000, - MONTH, 30 * 24 * 3600000, - YEAR, 31556952000 -); -/*jslint white: false*/ -/** - * Path interpolation algorithm used across adapters - */ -pathAnim = { - /** - * Prepare start and end values so that the path can be animated one to one - */ - init: function (elem, fromD, toD) { - fromD = fromD || ''; - var shift = elem.shift, - bezier = fromD.indexOf('C') > -1, - numParams = bezier ? 7 : 3, - endLength, - slice, - i, - start = fromD.split(' '), - end = [].concat(toD), // copy - startBaseLine, - endBaseLine, - sixify = function (arr) { // in splines make move points have six parameters like bezier curves - i = arr.length; - while (i--) { - if (arr[i] === M) { - arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]); - } - } - }; - - if (bezier) { - sixify(start); - sixify(end); - } - - // pull out the base lines before padding - if (elem.isArea) { - startBaseLine = start.splice(start.length - 6, 6); - endBaseLine = end.splice(end.length - 6, 6); - } - - // if shifting points, prepend a dummy point to the end path - if (shift <= end.length / numParams) { - while (shift--) { - end = [].concat(end).splice(0, numParams).concat(end); - } - } - elem.shift = 0; // reset for following animations - - // copy and append last point until the length matches the end length - if (start.length) { - endLength = end.length; - while (start.length < endLength) { - - //bezier && sixify(start); - slice = [].concat(start).splice(start.length - numParams, numParams); - if (bezier) { // disable first control point - slice[numParams - 6] = slice[numParams - 2]; - slice[numParams - 5] = slice[numParams - 1]; - } - start = start.concat(slice); - } - } - - if (startBaseLine) { // append the base lines for areas - start = start.concat(startBaseLine); - end = end.concat(endBaseLine); - } - return [start, end]; - }, - - /** - * Interpolate each value of the path and return the array - */ - step: function (start, end, pos, complete) { - var ret = [], - i = start.length, - startVal; - - if (pos === 1) { // land on the final path without adjustment points appended in the ends - ret = complete; - - } else if (i === end.length && pos < 1) { - while (i--) { - startVal = parseFloat(start[i]); - ret[i] = - isNaN(startVal) ? // a letter instruction like M or L - start[i] : - pos * (parseFloat(end[i] - startVal)) + startVal; - - } - } else { // if animation is finished or length not matching, land on right value - ret = end; - } - return ret; - } -}; - -(function ($) { - /** - * The default HighchartsAdapter for jQuery - */ - win.HighchartsAdapter = win.HighchartsAdapter || ($ && { - - /** - * Initialize the adapter by applying some extensions to jQuery - */ - init: function (pathAnim) { - - // extend the animate function to allow SVG animations - var Fx = $.fx, - Step = Fx.step, - dSetter, - Tween = $.Tween, - propHooks = Tween && Tween.propHooks; - - /*jslint unparam: true*//* allow unused param x in this function */ - $.extend($.easing, { - easeOutQuad: function (x, t, b, c, d) { - return -c * (t /= d) * (t - 2) + b; - } - }); - /*jslint unparam: false*/ - - - // extend some methods to check for elem.attr, which means it is a Highcharts SVG object - $.each(['cur', '_default', 'width', 'height'], function (i, fn) { - var obj = Step, - base, - elem; - - // Handle different parent objects - if (fn === 'cur') { - obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype - - } else if (fn === '_default' && Tween) { // jQuery 1.8 model - obj = propHooks[fn]; - fn = 'set'; - } - - // Overwrite the method - base = obj[fn]; - if (base) { // step.width and step.height don't exist in jQuery < 1.7 - - // create the extended function replacement - obj[fn] = function (fx) { - - // Fx.prototype.cur does not use fx argument - fx = i ? fx : this; - - // shortcut - elem = fx.elem; - - // Fx.prototype.cur returns the current value. The other ones are setters - // and returning a value has no effect. - return elem.attr ? // is SVG element wrapper - elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method - base.apply(this, arguments); // use jQuery's built-in method - }; - } - }); - - - // Define the setter function for d (path definitions) - dSetter = function (fx) { - var elem = fx.elem, - ends; - - // Normally start and end should be set in state == 0, but sometimes, - // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped - // in these cases - if (!fx.started) { - ends = pathAnim.init(elem, elem.d, elem.toD); - fx.start = ends[0]; - fx.end = ends[1]; - fx.started = true; - } - - - // interpolate each value of the path - elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD)); - }; - - // jQuery 1.8 style - if (Tween) { - propHooks.d = { - set: dSetter - }; - // pre 1.8 - } else { - // animate paths - Step.d = dSetter; - } - - /** - * Utility for iterating over an array. Parameters are reversed compared to jQuery. - * @param {Array} arr - * @param {Function} fn - */ - this.each = Array.prototype.forEach ? - function (arr, fn) { // modern browsers - return Array.prototype.forEach.call(arr, fn); - - } : - function (arr, fn) { // legacy - var i = 0, - len = arr.length; - for (; i < len; i++) { - if (fn.call(arr[i], arr[i], i, arr) === false) { - return i; - } - } - }; - - // Register Highcharts as a jQuery plugin - // TODO: MooTools and prototype as well? - // TODO: StockChart - /*$.fn.highcharts = function(options, callback) { - options.chart = merge(options.chart, { renderTo: this[0] }); - this.chart = new Chart(options, callback); - return this; - };*/ - }, - - /** - * Downloads a script and executes a callback when done. - * @param {String} scriptLocation - * @param {Function} callback - */ - getScript: $.getScript, - - /** - * Return the index of an item in an array, or -1 if not found - */ - inArray: $.inArray, - - /** - * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method. - * @param {Object} elem The HTML element - * @param {String} method Which method to run on the wrapped element - */ - adapterRun: function (elem, method) { - return $(elem)[method](); - }, - - /** - * Filter an array - */ - grep: $.grep, - - /** - * Map an array - * @param {Array} arr - * @param {Function} fn - */ - map: function (arr, fn) { - //return jQuery.map(arr, fn); - var results = [], - i = 0, - len = arr.length; - for (; i < len; i++) { - results[i] = fn.call(arr[i], arr[i], i, arr); - } - return results; - - }, - - /** - * Deep merge two objects and return a third object - */ - merge: function () { - var args = arguments; - return $.extend(true, null, args[0], args[1], args[2], args[3]); - }, - - /** - * Get the position of an element relative to the top left of the page - */ - offset: function (el) { - return $(el).offset(); - }, - - /** - * Add an event listener - * @param {Object} el A HTML element or custom object - * @param {String} event The event type - * @param {Function} fn The event handler - */ - addEvent: function (el, event, fn) { - $(el).bind(event, fn); - }, - - /** - * Remove event added with addEvent - * @param {Object} el The object - * @param {String} eventType The event type. Leave blank to remove all events. - * @param {Function} handler The function to remove - */ - removeEvent: function (el, eventType, handler) { - // workaround for jQuery issue with unbinding custom events: - // http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2 - var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent'; - if (doc[func] && !el[func]) { - el[func] = function () {}; - } - - $(el).unbind(eventType, handler); - }, - - /** - * Fire an event on a custom object - * @param {Object} el - * @param {String} type - * @param {Object} eventArguments - * @param {Function} defaultFunction - */ - fireEvent: function (el, type, eventArguments, defaultFunction) { - var event = $.Event(type), - detachedType = 'detached' + type, - defaultPrevented; - - // Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts - // never uses these properties, Chrome includes them in the default click event and - // raises the warning when they are copied over in the extend statement below. - // - // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid - // testing if they are there (warning in chrome) the only option is to test if running IE. - if (!isIE && eventArguments) { - delete eventArguments.layerX; - delete eventArguments.layerY; - } - - extend(event, eventArguments); - - // Prevent jQuery from triggering the object method that is named the - // same as the event. For example, if the event is 'select', jQuery - // attempts calling el.select and it goes into a loop. - if (el[type]) { - el[detachedType] = el[type]; - el[type] = null; - } - - // Wrap preventDefault and stopPropagation in try/catch blocks in - // order to prevent JS errors when cancelling events on non-DOM - // objects. #615. - /*jslint unparam: true*/ - $.each(['preventDefault', 'stopPropagation'], function (i, fn) { - var base = event[fn]; - event[fn] = function () { - try { - base.call(event); - } catch (e) { - if (fn === 'preventDefault') { - defaultPrevented = true; - } - } - }; - }); - /*jslint unparam: false*/ - - // trigger it - $(el).trigger(event); - - // attach the method - if (el[detachedType]) { - el[type] = el[detachedType]; - el[detachedType] = null; - } - - if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) { - defaultFunction(event); - } - }, - - /** - * Extension method needed for MooTools - */ - washMouseEvent: function (e) { - var ret = e.originalEvent || e; - - // computed by jQuery, needed by IE8 - if (ret.pageX === UNDEFINED) { // #1236 - ret.pageX = e.pageX; - ret.pageY = e.pageY; - } - - return ret; - }, - - /** - * Animate a HTML element or SVG element wrapper - * @param {Object} el - * @param {Object} params - * @param {Object} options jQuery-like animation options: duration, easing, callback - */ - animate: function (el, params, options) { - var $el = $(el); - if (params.d) { - el.toD = params.d; // keep the array form for paths, used in $.fx.step.d - params.d = 1; // because in jQuery, animating to an array has a different meaning - } - - $el.stop(); - $el.animate(params, options); - - }, - /** - * Stop running animation - */ - stop: function (el) { - $(el).stop(); - } - }); -}(win.jQuery)); - - -// check for a custom HighchartsAdapter defined prior to this file -var globalAdapter = win.HighchartsAdapter, - adapter = globalAdapter || {}; - -// Initialize the adapter -if (globalAdapter) { - globalAdapter.init.call(globalAdapter, pathAnim); -} - - - // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object - // and all the utility functions will be null. In that case they are populated by the - // default adapters below. -var adapterRun = adapter.adapterRun, - getScript = adapter.getScript, - inArray = adapter.inArray, - each = adapter.each, - grep = adapter.grep, - offset = adapter.offset, - map = adapter.map, - merge = adapter.merge, - addEvent = adapter.addEvent, - removeEvent = adapter.removeEvent, - fireEvent = adapter.fireEvent, - washMouseEvent = adapter.washMouseEvent, - animate = adapter.animate, - stop = adapter.stop; - - - -/* **************************************************************************** - * Handle the options * - *****************************************************************************/ -var - -defaultLabelOptions = { - enabled: true, - // rotation: 0, - align: 'center', - x: 0, - y: 15, - /*formatter: function () { - return this.value; - },*/ - style: { - color: '#666', - fontSize: '11px', - lineHeight: '14px' - } -}; - -defaultOptions = { - colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', - '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'], - symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'], - lang: { - loading: 'Loading...', - months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', - 'August', 'September', 'October', 'November', 'December'], - shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - decimalPoint: '.', - numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels - resetZoom: 'Reset zoom', - resetZoomTitle: 'Reset zoom level 1:1', - thousandsSep: ',' - }, - global: { - useUTC: true, - canvasToolsURL: 'http://code.highcharts.com/2.3.3/modules/canvas-tools.js', - VMLRadialGradientURL: 'http://code.highcharts.com/2.3.3/gfx/vml-radial-gradient.png' - }, - chart: { - //animation: true, - //alignTicks: false, - //reflow: true, - //className: null, - //events: { load, selection }, - //margin: [null], - //marginTop: null, - //marginRight: null, - //marginBottom: null, - //marginLeft: null, - borderColor: '#4572A7', - //borderWidth: 0, - borderRadius: 5, - defaultSeriesType: 'line', - ignoreHiddenSeries: true, - //inverted: false, - //shadow: false, - spacingTop: 10, - spacingRight: 10, - spacingBottom: 15, - spacingLeft: 10, - style: { - fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font - fontSize: '12px' - }, - backgroundColor: '#FFFFFF', - //plotBackgroundColor: null, - plotBorderColor: '#C0C0C0', - //plotBorderWidth: 0, - //plotShadow: false, - //zoomType: '' - resetZoomButton: { - theme: { - zIndex: 20 - }, - position: { - align: 'right', - x: -10, - //verticalAlign: 'top', - y: 10 - } - // relativeTo: 'plot' - } - }, - title: { - text: 'Chart title', - align: 'center', - // floating: false, - // margin: 15, - // x: 0, - // verticalAlign: 'top', - y: 15, - style: { - color: '#3E576F', - fontSize: '16px' - } - - }, - subtitle: { - text: '', - align: 'center', - // floating: false - // x: 0, - // verticalAlign: 'top', - y: 30, - style: { - color: '#6D869F' - } - }, - - plotOptions: { - line: { // base series options - allowPointSelect: false, - showCheckbox: false, - animation: { - duration: 1000 - }, - //connectNulls: false, - //cursor: 'default', - //clip: true, - //dashStyle: null, - //enableMouseTracking: true, - events: {}, - //legendIndex: 0, - lineWidth: 2, - shadow: true, - // stacking: null, - marker: { - enabled: true, - //symbol: null, - lineWidth: 0, - radius: 4, - lineColor: '#FFFFFF', - //fillColor: null, - states: { // states for a single point - hover: { - enabled: true - //radius: base + 2 - }, - select: { - fillColor: '#FFFFFF', - lineColor: '#000000', - lineWidth: 2 - } - } - }, - point: { - events: {} - }, - dataLabels: merge(defaultLabelOptions, { - enabled: false, - formatter: function () { - return this.y; - }, - verticalAlign: 'bottom', // above singular point - y: 0 - // backgroundColor: undefined, - // borderColor: undefined, - // borderRadius: undefined, - // borderWidth: undefined, - // padding: 3, - // shadow: false - }), - cropThreshold: 300, // draw points outside the plot area when the number of points is less than this - pointRange: 0, - //pointStart: 0, - //pointInterval: 1, - showInLegend: true, - states: { // states for the entire series - hover: { - //enabled: false, - //lineWidth: base + 1, - marker: { - // lineWidth: base + 1, - // radius: base + 1 - } - }, - select: { - marker: {} - } - }, - stickyTracking: true - //tooltip: { - //pointFormat: '{series.name}: {point.y}' - //valueDecimals: null, - //xDateFormat: '%A, %b %e, %Y', - //valuePrefix: '', - //ySuffix: '' - //} - // turboThreshold: 1000 - // zIndex: null - } - }, - labels: { - //items: [], - style: { - //font: defaultFont, - position: ABSOLUTE, - color: '#3E576F' - } - }, - legend: { - enabled: true, - align: 'center', - //floating: false, - layout: 'horizontal', - labelFormatter: function () { - return this.name; - }, - borderWidth: 1, - borderColor: '#909090', - borderRadius: 5, - navigation: { - // animation: true, - activeColor: '#3E576F', - // arrowSize: 12 - inactiveColor: '#CCC' - // style: {} // text styles - }, - // margin: 10, - // reversed: false, - shadow: false, - // backgroundColor: null, - /*style: { - padding: '5px' - },*/ - itemStyle: { - cursor: 'pointer', - color: '#3E576F', - fontSize: '12px' - }, - itemHoverStyle: { - //cursor: 'pointer', removed as of #601 - color: '#000' - }, - itemHiddenStyle: { - color: '#CCC' - }, - itemCheckboxStyle: { - position: ABSOLUTE, - width: '13px', // for IE precision - height: '13px' - }, - // itemWidth: undefined, - symbolWidth: 16, - symbolPadding: 5, - verticalAlign: 'bottom', - // width: undefined, - x: 0, - y: 0 - }, - - loading: { - // hideDuration: 100, - labelStyle: { - fontWeight: 'bold', - position: RELATIVE, - top: '1em' - }, - // showDuration: 0, - style: { - position: ABSOLUTE, - backgroundColor: 'white', - opacity: 0.5, - textAlign: 'center' - } - }, - - tooltip: { - enabled: true, - //crosshairs: null, - backgroundColor: 'rgba(255, 255, 255, .85)', - borderWidth: 2, - borderRadius: 5, - dateTimeLabelFormats: { - millisecond: '%A, %b %e, %H:%M:%S.%L', - second: '%A, %b %e, %H:%M:%S', - minute: '%A, %b %e, %H:%M', - hour: '%A, %b %e, %H:%M', - day: '%A, %b %e, %Y', - week: 'Week from %A, %b %e, %Y', - month: '%B %Y', - year: '%Y' - }, - //formatter: defaultFormatter, - headerFormat: '{point.key}
', - pointFormat: '{series.name}: {point.y}
', - shadow: true, - shared: useCanVG, - snap: hasTouch ? 25 : 10, - style: { - color: '#333333', - fontSize: '12px', - padding: '5px', - whiteSpace: 'nowrap' - } - //xDateFormat: '%A, %b %e, %Y', - //valueDecimals: null, - //valuePrefix: '', - //valueSuffix: '' - }, - - credits: { - enabled: true, - text: 'Highcharts.com', - href: 'http://www.highcharts.com', - position: { - align: 'right', - x: -10, - verticalAlign: 'bottom', - y: -5 - }, - style: { - cursor: 'pointer', - color: '#909090', - fontSize: '10px' - } - } -}; - - - - -// Series defaults -var defaultPlotOptions = defaultOptions.plotOptions, - defaultSeriesOptions = defaultPlotOptions.line; - -// set the default time methods -setTimeMethods(); - - - -/** - * Set the time methods globally based on the useUTC option. Time method can be either - * local time or UTC (default). - */ -function setTimeMethods() { - var useUTC = defaultOptions.global.useUTC, - GET = useUTC ? 'getUTC' : 'get', - SET = useUTC ? 'setUTC' : 'set'; - - makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) { - return new Date( - year, - month, - pick(date, 1), - pick(hours, 0), - pick(minutes, 0), - pick(seconds, 0) - ).getTime(); - }; - getMinutes = GET + 'Minutes'; - getHours = GET + 'Hours'; - getDay = GET + 'Day'; - getDate = GET + 'Date'; - getMonth = GET + 'Month'; - getFullYear = GET + 'FullYear'; - setMinutes = SET + 'Minutes'; - setHours = SET + 'Hours'; - setDate = SET + 'Date'; - setMonth = SET + 'Month'; - setFullYear = SET + 'FullYear'; - -} - -/** - * Merge the default options with custom options and return the new options structure - * @param {Object} options The new custom options - */ -function setOptions(options) { - - // Pull out axis options and apply them to the respective default axis options - /*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis); - defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis); - options.xAxis = options.yAxis = UNDEFINED;*/ - - // Merge in the default options - defaultOptions = merge(defaultOptions, options); - - // Apply UTC - setTimeMethods(); - - return defaultOptions; -} - -/** - * Get the updated default options. Merely exposing defaultOptions for outside modules - * isn't enough because the setOptions method creates a new object. - */ -function getOptions() { - return defaultOptions; -} - - - -/** - * Handle color operations. The object methods are chainable. - * @param {String} input The input color in either rbga or hex format - */ -var Color = function (input) { - // declare variables - var rgba = [], result; - - /** - * Parse the input color to rgba array - * @param {String} input - */ - function init(input) { - - // rgba - result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input); - if (result) { - rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)]; - } else { // hex - result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input); - if (result) { - rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1]; - } - } - - } - /** - * Return the color a specified format - * @param {String} format - */ - function get(format) { - var ret; - - // it's NaN if gradient colors on a column chart - if (rgba && !isNaN(rgba[0])) { - if (format === 'rgb') { - ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')'; - } else if (format === 'a') { - ret = rgba[3]; - } else { - ret = 'rgba(' + rgba.join(',') + ')'; - } - } else { - ret = input; - } - return ret; - } - - /** - * Brighten the color - * @param {Number} alpha - */ - function brighten(alpha) { - if (isNumber(alpha) && alpha !== 0) { - var i; - for (i = 0; i < 3; i++) { - rgba[i] += pInt(alpha * 255); - - if (rgba[i] < 0) { - rgba[i] = 0; - } - if (rgba[i] > 255) { - rgba[i] = 255; - } - } - } - return this; - } - /** - * Set the color's opacity to a given alpha value - * @param {Number} alpha - */ - function setOpacity(alpha) { - rgba[3] = alpha; - return this; - } - - // initialize: parse the input - init(input); - - // public methods - return { - get: get, - brighten: brighten, - setOpacity: setOpacity - }; -}; - - -/** - * A wrapper object for SVG elements - */ -function SVGElement() {} - -SVGElement.prototype = { - /** - * Initialize the SVG renderer - * @param {Object} renderer - * @param {String} nodeName - */ - init: function (renderer, nodeName) { - var wrapper = this; - wrapper.element = nodeName === 'span' ? - createElement(nodeName) : - doc.createElementNS(SVG_NS, nodeName); - wrapper.renderer = renderer; - /** - * A collection of attribute setters. These methods, if defined, are called right before a certain - * attribute is set on an element wrapper. Returning false prevents the default attribute - * setter to run. Returning a value causes the default setter to set that value. Used in - * Renderer.label. - */ - wrapper.attrSetters = {}; - }, - /** - * Animate a given attribute - * @param {Object} params - * @param {Number} options The same options as in jQuery animation - * @param {Function} complete Function to perform at the end of animation - */ - animate: function (params, options, complete) { - var animOptions = pick(options, globalAnimation, true); - stop(this); // stop regardless of animation actually running, or reverting to .attr (#607) - if (animOptions) { - animOptions = merge(animOptions); - if (complete) { // allows using a callback with the global animation without overwriting it - animOptions.complete = complete; - } - animate(this, params, animOptions); - } else { - this.attr(params); - if (complete) { - complete(); - } - } - }, - /** - * Set or get a given attribute - * @param {Object|String} hash - * @param {Mixed|Undefined} val - */ - attr: function (hash, val) { - var wrapper = this, - key, - value, - result, - i, - child, - element = wrapper.element, - nodeName = element.nodeName.toLowerCase(), // Android2 requires lower for "text" - renderer = wrapper.renderer, - skipAttr, - titleNode, - attrSetters = wrapper.attrSetters, - shadows = wrapper.shadows, - hasSetSymbolSize, - doTransform, - ret = wrapper; - - // single key-value pair - if (isString(hash) && defined(val)) { - key = hash; - hash = {}; - hash[key] = val; - } - - // used as a getter: first argument is a string, second is undefined - if (isString(hash)) { - key = hash; - if (nodeName === 'circle') { - key = { x: 'cx', y: 'cy' }[key] || key; - } else if (key === 'strokeWidth') { - key = 'stroke-width'; - } - ret = attr(element, key) || wrapper[key] || 0; - - if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step - ret = parseFloat(ret); - } - - // setter - } else { - - for (key in hash) { - skipAttr = false; // reset - value = hash[key]; - - // check for a specific attribute setter - result = attrSetters[key] && attrSetters[key].call(wrapper, value, key); - - if (result !== false) { - if (result !== UNDEFINED) { - value = result; // the attribute setter has returned a new value to set - } - - // paths - if (key === 'd') { - if (value && value.join) { // join path - value = value.join(' '); - } - if (/(NaN| {2}|^$)/.test(value)) { - value = 'M 0 0'; - } - //wrapper.d = value; // shortcut for animations - - // update child tspans x values - } else if (key === 'x' && nodeName === 'text') { - for (i = 0; i < element.childNodes.length; i++) { - child = element.childNodes[i]; - // if the x values are equal, the tspan represents a linebreak - if (attr(child, 'x') === attr(element, 'x')) { - //child.setAttribute('x', value); - attr(child, 'x', value); - } - } - - if (wrapper.rotation) { - attr(element, 'transform', 'rotate(' + wrapper.rotation + ' ' + value + ' ' + - pInt(hash.y || attr(element, 'y')) + ')'); - } - - // apply gradients - } else if (key === 'fill') { - value = renderer.color(value, element, key); - - // circle x and y - } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) { - key = { x: 'cx', y: 'cy' }[key] || key; - - // rectangle border radius - } else if (nodeName === 'rect' && key === 'r') { - attr(element, { - rx: value, - ry: value - }); - skipAttr = true; - - // translation and text rotation - } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') { - doTransform = true; - skipAttr = true; - - // apply opacity as subnode (required by legacy WebKit and Batik) - } else if (key === 'stroke') { - value = renderer.color(value, element, key); - - // emulate VML's dashstyle implementation - } else if (key === 'dashstyle') { - key = 'stroke-dasharray'; - value = value && value.toLowerCase(); - if (value === 'solid') { - value = NONE; - } else if (value) { - value = value - .replace('shortdashdotdot', '3,1,1,1,1,1,') - .replace('shortdashdot', '3,1,1,1') - .replace('shortdot', '1,1,') - .replace('shortdash', '3,1,') - .replace('longdash', '8,3,') - .replace(/dot/g, '1,3,') - .replace('dash', '4,3,') - .replace(/,$/, '') - .split(','); // ending comma - - i = value.length; - while (i--) { - value[i] = pInt(value[i]) * hash['stroke-width']; - } - value = value.join(','); - } - - // special - } else if (key === 'isTracker') { - wrapper[key] = value; - - // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2 - // is unable to cast them. Test again with final IE9. - } else if (key === 'width') { - value = pInt(value); - - // Text alignment - } else if (key === 'align') { - key = 'text-anchor'; - value = { left: 'start', center: 'middle', right: 'end' }[value]; - - // Title requires a subnode, #431 - } else if (key === 'title') { - titleNode = element.getElementsByTagName('title')[0]; - if (!titleNode) { - titleNode = doc.createElementNS(SVG_NS, 'title'); - element.appendChild(titleNode); - } - titleNode.textContent = value; - } - - // jQuery animate changes case - if (key === 'strokeWidth') { - key = 'stroke-width'; - } - - // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461) - if (isWebKit && key === 'stroke-width' && value === 0) { - value = 0.000001; - } - - // symbols - if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) { - - - if (!hasSetSymbolSize) { - wrapper.symbolAttr(hash); - hasSetSymbolSize = true; - } - skipAttr = true; - } - - // let the shadow follow the main element - if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) { - i = shadows.length; - while (i--) { - attr( - shadows[i], - key, - key === 'height' ? - mathMax(value - (shadows[i].cutHeight || 0), 0) : - value - ); - } - } - - // validate heights - if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) { - value = 0; - } - - // Record for animation and quick access without polling the DOM - wrapper[key] = value; - - // Update transform - if (doTransform) { - wrapper.updateTransform(); - } - - - if (key === 'text') { - // Delete bBox memo when the text changes - if (value !== wrapper.textStr) { - delete wrapper.bBox; - } - wrapper.textStr = value; - if (wrapper.added) { - renderer.buildText(wrapper); - } - } else if (!skipAttr) { - attr(element, key, value); - } - - } - - } - - } - - return ret; - }, - - /** - * If one of the symbol size affecting parameters are changed, - * check all the others only once for each call to an element's - * .attr() method - * @param {Object} hash - */ - symbolAttr: function (hash) { - var wrapper = this; - - each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) { - wrapper[key] = pick(hash[key], wrapper[key]); - }); - - wrapper.attr({ - d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper) - }); - }, - - /** - * Apply a clipping path to this object - * @param {String} id - */ - clip: function (clipRect) { - return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE); - }, - - /** - * Calculate the coordinates needed for drawing a rectangle crisply and return the - * calculated attributes - * @param {Number} strokeWidth - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - crisp: function (strokeWidth, x, y, width, height) { - - var wrapper = this, - key, - attribs = {}, - values = {}, - normalizer; - - strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0; - normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors - - // normalize for crisp edges - values.x = mathFloor(x || wrapper.x || 0) + normalizer; - values.y = mathFloor(y || wrapper.y || 0) + normalizer; - values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer); - values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer); - values.strokeWidth = strokeWidth; - - for (key in values) { - if (wrapper[key] !== values[key]) { // only set attribute if changed - wrapper[key] = attribs[key] = values[key]; - } - } - - return attribs; - }, - - /** - * Set styles for the element - * @param {Object} styles - */ - css: function (styles) { - /*jslint unparam: true*//* allow unused param a in the regexp function below */ - var elemWrapper = this, - elem = elemWrapper.element, - textWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text', - n, - serializedCss = '', - hyphenate = function (a, b) { return '-' + b.toLowerCase(); }; - /*jslint unparam: false*/ - - // convert legacy - if (styles && styles.color) { - styles.fill = styles.color; - } - - // Merge the new styles with the old ones - styles = extend( - elemWrapper.styles, - styles - ); - - // store object - elemWrapper.styles = styles; - - - // Don't handle line wrap on canvas - if (useCanVG && textWidth) { - delete styles.width; - } - - // serialize and set style attribute - if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute - if (textWidth) { - delete styles.width; - } - css(elemWrapper.element, styles); - } else { - for (n in styles) { - serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';'; - } - elemWrapper.attr({ - style: serializedCss - }); - } - - - // re-build text - if (textWidth && elemWrapper.added) { - elemWrapper.renderer.buildText(elemWrapper); - } - - return elemWrapper; - }, - - /** - * Add an event listener - * @param {String} eventType - * @param {Function} handler - */ - on: function (eventType, handler) { - var fn = handler; - // touch - if (hasTouch && eventType === 'click') { - eventType = 'touchstart'; - fn = function (e) { - e.preventDefault(); - handler(); - }; - } - // simplest possible event model for internal use - this.element['on' + eventType] = fn; - return this; - }, - - /** - * Set the coordinates needed to draw a consistent radial gradient across - * pie slices regardless of positioning inside the chart. The format is - * [centerX, centerY, diameter] in pixels. - */ - setRadialReference: function (coordinates) { - this.element.radialReference = coordinates; - return this; - }, - - /** - * Move an object and its children by x and y values - * @param {Number} x - * @param {Number} y - */ - translate: function (x, y) { - return this.attr({ - translateX: x, - translateY: y - }); - }, - - /** - * Invert a group, rotate and flip - */ - invert: function () { - var wrapper = this; - wrapper.inverted = true; - wrapper.updateTransform(); - return wrapper; - }, - - /** - * Apply CSS to HTML elements. This is used in text within SVG rendering and - * by the VML renderer - */ - htmlCss: function (styles) { - var wrapper = this, - element = wrapper.element, - textWidth = styles && element.tagName === 'SPAN' && styles.width; - - if (textWidth) { - delete styles.width; - wrapper.textWidth = textWidth; - wrapper.updateTransform(); - } - - wrapper.styles = extend(wrapper.styles, styles); - css(wrapper.element, styles); - - return wrapper; - }, - - - - /** - * VML and useHTML method for calculating the bounding box based on offsets - * @param {Boolean} refresh Whether to force a fresh value from the DOM or to - * use the cached value - * - * @return {Object} A hash containing values for x, y, width and height - */ - - htmlGetBBox: function () { - var wrapper = this, - element = wrapper.element, - bBox = wrapper.bBox; - - // faking getBBox in exported SVG in legacy IE - if (!bBox) { - // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?) - if (element.nodeName === 'text') { - element.style.position = ABSOLUTE; - } - - bBox = wrapper.bBox = { - x: element.offsetLeft, - y: element.offsetTop, - width: element.offsetWidth, - height: element.offsetHeight - }; - } - - return bBox; - }, - - /** - * VML override private method to update elements based on internal - * properties based on SVG transform - */ - htmlUpdateTransform: function () { - // aligning non added elements is expensive - if (!this.added) { - this.alignOnAdd = true; - return; - } - - var wrapper = this, - renderer = wrapper.renderer, - elem = wrapper.element, - translateX = wrapper.translateX || 0, - translateY = wrapper.translateY || 0, - x = wrapper.x || 0, - y = wrapper.y || 0, - align = wrapper.textAlign || 'left', - alignCorrection = { left: 0, center: 0.5, right: 1 }[align], - nonLeft = align && align !== 'left', - shadows = wrapper.shadows; - - // apply translate - if (translateX || translateY) { - css(elem, { - marginLeft: translateX, - marginTop: translateY - }); - if (shadows) { // used in labels/tooltip - each(shadows, function (shadow) { - css(shadow, { - marginLeft: translateX + 1, - marginTop: translateY + 1 - }); - }); - } - } - - // apply inversion - if (wrapper.inverted) { // wrapper is a group - each(elem.childNodes, function (child) { - renderer.invertChild(child, elem); - }); - } - - if (elem.tagName === 'SPAN') { - - var width, height, - rotation = wrapper.rotation, - baseline, - radians = 0, - costheta = 1, - sintheta = 0, - quad, - textWidth = pInt(wrapper.textWidth), - xCorr = wrapper.xCorr || 0, - yCorr = wrapper.yCorr || 0, - currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','), - rotationStyle = {}, - cssTransformKey; - - if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed - - if (defined(rotation)) { - - if (renderer.isSVG) { // #916 - cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : ''; - rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)'; - - } else { - radians = rotation * deg2rad; // deg to rad - costheta = mathCos(radians); - sintheta = mathSin(radians); - - // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented - // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+ - // has support for CSS3 transform. The getBBox method also needs to be updated - // to compensate for the rotation, like it currently does for SVG. - // Test case: http://highcharts.com/tests/?file=text-rotation - rotationStyle.filter = rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta, - ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta, - ', sizingMethod=\'auto expand\')'].join('') : NONE; - } - css(elem, rotationStyle); - } - - width = pick(wrapper.elemWidth, elem.offsetWidth); - height = pick(wrapper.elemHeight, elem.offsetHeight); - - // update textWidth - if (width > textWidth && /[ \-]/.test(elem.innerText)) { // #983 - css(elem, { - width: textWidth + PX, - display: 'block', - whiteSpace: 'normal' - }); - width = textWidth; - } - - // correct x and y - baseline = renderer.fontMetrics(elem.style.fontSize).b; - xCorr = costheta < 0 && -width; - yCorr = sintheta < 0 && -height; - - // correct for baseline and corners spilling out after rotation - quad = costheta * sintheta < 0; - xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection); - yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1); - - // correct for the length/height of the text - if (nonLeft) { - xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1); - if (rotation) { - yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1); - } - css(elem, { - textAlign: align - }); - } - - // record correction - wrapper.xCorr = xCorr; - wrapper.yCorr = yCorr; - } - - // apply position with correction - css(elem, { - left: (x + xCorr) + PX, - top: (y + yCorr) + PX - }); - - // record current text transform - wrapper.cTT = currentTextTransform; - } - }, - - /** - * Private method to update the transform attribute based on internal - * properties - */ - updateTransform: function () { - var wrapper = this, - translateX = wrapper.translateX || 0, - translateY = wrapper.translateY || 0, - inverted = wrapper.inverted, - rotation = wrapper.rotation, - transform = []; - - // flipping affects translate as adjustment for flipping around the group's axis - if (inverted) { - translateX += wrapper.attr('width'); - translateY += wrapper.attr('height'); - } - - // apply translate - if (translateX || translateY) { - transform.push('translate(' + translateX + ',' + translateY + ')'); - } - - // apply rotation - if (inverted) { - transform.push('rotate(90) scale(-1,1)'); - } else if (rotation) { // text rotation - transform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')'); - } - - if (transform.length) { - attr(wrapper.element, 'transform', transform.join(' ')); - } - }, - /** - * Bring the element to the front - */ - toFront: function () { - var element = this.element; - element.parentNode.appendChild(element); - return this; - }, - - - /** - * Break down alignment options like align, verticalAlign, x and y - * to x and y relative to the chart. - * - * @param {Object} alignOptions - * @param {Boolean} alignByTranslate - * @param {Object} box The box to align to, needs a width and height - * - */ - align: function (alignOptions, alignByTranslate, box) { - var elemWrapper = this; - - if (!alignOptions) { // called on resize - alignOptions = elemWrapper.alignOptions; - alignByTranslate = elemWrapper.alignByTranslate; - } else { // first call on instanciate - elemWrapper.alignOptions = alignOptions; - elemWrapper.alignByTranslate = alignByTranslate; - if (!box) { // boxes other than renderer handle this internally - elemWrapper.renderer.alignedObjects.push(elemWrapper); - } - } - - box = pick(box, elemWrapper.renderer); - - var align = alignOptions.align, - vAlign = alignOptions.verticalAlign, - x = (box.x || 0) + (alignOptions.x || 0), // default: left align - y = (box.y || 0) + (alignOptions.y || 0), // default: top align - attribs = {}; - - - // align - if (align === 'right' || align === 'center') { - x += (box.width - (alignOptions.width || 0)) / - { right: 1, center: 2 }[align]; - } - attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x); - - - // vertical align - if (vAlign === 'bottom' || vAlign === 'middle') { - y += (box.height - (alignOptions.height || 0)) / - ({ bottom: 1, middle: 2 }[vAlign] || 1); - - } - attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y); - - // animate only if already placed - elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs); - elemWrapper.placed = true; - elemWrapper.alignAttr = attribs; - - return elemWrapper; - }, - - /** - * Get the bounding box (width, height, x and y) for the element - */ - getBBox: function () { - var wrapper = this, - bBox = wrapper.bBox, - renderer = wrapper.renderer, - width, - height, - rotation = wrapper.rotation, - element = wrapper.element, - styles = wrapper.styles, - rad = rotation * deg2rad; - - if (!bBox) { - // SVG elements - if (element.namespaceURI === SVG_NS || renderer.forExport) { - try { // Fails in Firefox if the container has display: none. - - bBox = element.getBBox ? - // SVG: use extend because IE9 is not allowed to change width and height in case - // of rotation (below) - extend({}, element.getBBox()) : - // Canvas renderer and legacy IE in export mode - { - width: element.offsetWidth, - height: element.offsetHeight - }; - } catch (e) {} - - // If the bBox is not set, the try-catch block above failed. The other condition - // is for Opera that returns a width of -Infinity on hidden elements. - if (!bBox || bBox.width < 0) { - bBox = { width: 0, height: 0 }; - } - - - // VML Renderer or useHTML within SVG - } else { - - bBox = wrapper.htmlGetBBox(); - - } - - // True SVG elements as well as HTML elements in modern browsers using the .useHTML option - // need to compensated for rotation - if (renderer.isSVG) { - width = bBox.width; - height = bBox.height; - - // Adjust for rotated text - if (rotation) { - bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad)); - bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad)); - } - } - - // Workaround for wrong bounding box in IE9 and IE10 (#1101) - if (isIE && styles && styles.fontSize === '11px' && height === 22.700000762939453) { - bBox.height = 14; - } - - wrapper.bBox = bBox; - } - return bBox; - }, - - /** - * Show the element - */ - show: function () { - return this.attr({ visibility: VISIBLE }); - }, - - /** - * Hide the element - */ - hide: function () { - return this.attr({ visibility: HIDDEN }); - }, - - /** - * Add the element - * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined - * to append the element to the renderer.box. - */ - add: function (parent) { - - var renderer = this.renderer, - parentWrapper = parent || renderer, - parentNode = parentWrapper.element || renderer.box, - childNodes = parentNode.childNodes, - element = this.element, - zIndex = attr(element, 'zIndex'), - otherElement, - otherZIndex, - i, - inserted; - - if (parent) { - this.parentGroup = parent; - } - - // mark as inverted - this.parentInverted = parent && parent.inverted; - - // build formatted text - if (this.textStr !== undefined) { - renderer.buildText(this); - } - - // mark the container as having z indexed children - if (zIndex) { - parentWrapper.handleZ = true; - zIndex = pInt(zIndex); - } - - // insert according to this and other elements' zIndex - if (parentWrapper.handleZ) { // this element or any of its siblings has a z index - for (i = 0; i < childNodes.length; i++) { - otherElement = childNodes[i]; - otherZIndex = attr(otherElement, 'zIndex'); - if (otherElement !== element && ( - // insert before the first element with a higher zIndex - pInt(otherZIndex) > zIndex || - // if no zIndex given, insert before the first element with a zIndex - (!defined(zIndex) && defined(otherZIndex)) - - )) { - parentNode.insertBefore(element, otherElement); - inserted = true; - break; - } - } - } - - // default: append at the end - if (!inserted) { - parentNode.appendChild(element); - } - - // mark as added - this.added = true; - - // fire an event for internal hooks - fireEvent(this, 'add'); - - return this; - }, - - /** - * Removes a child either by removeChild or move to garbageBin. - * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. - */ - safeRemoveChild: function (element) { - var parentNode = element.parentNode; - if (parentNode) { - parentNode.removeChild(element); - } - }, - - /** - * Destroy the element and element wrapper - */ - destroy: function () { - var wrapper = this, - element = wrapper.element || {}, - shadows = wrapper.shadows, - key, - i; - - // remove events - element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null; - stop(wrapper); // stop running animations - - if (wrapper.clipPath) { - wrapper.clipPath = wrapper.clipPath.destroy(); - } - - // Destroy stops in case this is a gradient object - if (wrapper.stops) { - for (i = 0; i < wrapper.stops.length; i++) { - wrapper.stops[i] = wrapper.stops[i].destroy(); - } - wrapper.stops = null; - } - - // remove element - wrapper.safeRemoveChild(element); - - // destroy shadows - if (shadows) { - each(shadows, function (shadow) { - wrapper.safeRemoveChild(shadow); - }); - } - - // remove from alignObjects - erase(wrapper.renderer.alignedObjects, wrapper); - - for (key in wrapper) { - delete wrapper[key]; - } - - return null; - }, - - /** - * Empty a group element - */ - empty: function () { - var element = this.element, - childNodes = element.childNodes, - i = childNodes.length; - - while (i--) { - element.removeChild(childNodes[i]); - } - }, - - /** - * Add a shadow to the element. Must be done after the element is added to the DOM - * @param {Boolean|Object} shadowOptions - */ - shadow: function (shadowOptions, group, cutOff) { - var shadows = [], - i, - shadow, - element = this.element, - strokeWidth, - shadowWidth, - shadowElementOpacity, - - // compensate for inverted plot area - transform; - - - if (shadowOptions) { - shadowWidth = pick(shadowOptions.width, 3); - shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; - transform = this.parentInverted ? - '(-1,-1)' : - '(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')'; - for (i = 1; i <= shadowWidth; i++) { - shadow = element.cloneNode(0); - strokeWidth = (shadowWidth * 2) + 1 - (2 * i); - attr(shadow, { - 'isShadow': 'true', - 'stroke': shadowOptions.color || 'black', - 'stroke-opacity': shadowElementOpacity * i, - 'stroke-width': strokeWidth, - 'transform': 'translate' + transform, - 'fill': NONE - }); - if (cutOff) { - attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0)); - shadow.cutHeight = strokeWidth; - } - - if (group) { - group.element.appendChild(shadow); - } else { - element.parentNode.insertBefore(shadow, element); - } - - shadows.push(shadow); - } - - this.shadows = shadows; - } - return this; - - } -}; - - -/** - * The default SVG renderer - */ -var SVGRenderer = function () { - this.init.apply(this, arguments); -}; -SVGRenderer.prototype = { - Element: SVGElement, - - /** - * Initialize the SVGRenderer - * @param {Object} container - * @param {Number} width - * @param {Number} height - * @param {Boolean} forExport - */ - init: function (container, width, height, forExport) { - var renderer = this, - loc = location, - boxWrapper; - - boxWrapper = renderer.createElement('svg') - .attr({ - xmlns: SVG_NS, - version: '1.1' - }); - container.appendChild(boxWrapper.element); - - // object properties - renderer.isSVG = true; - renderer.box = boxWrapper.element; - renderer.boxWrapper = boxWrapper; - renderer.alignedObjects = []; - - // Page url used for internal references. #24, #672, #1070 - renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ? - loc.href - .replace(/#.*?$/, '') // remove the hash - .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes - .replace(/ /g, '%20') : // replace spaces (needed for Safari only) - ''; - - renderer.defs = this.createElement('defs').add(); - renderer.forExport = forExport; - renderer.gradients = {}; // Object where gradient SvgElements are stored - - renderer.setSize(width, height, false); - - - - // Issue 110 workaround: - // In Firefox, if a div is positioned by percentage, its pixel position may land - // between pixels. The container itself doesn't display this, but an SVG element - // inside this container will be drawn at subpixel precision. In order to draw - // sharp lines, this must be compensated for. This doesn't seem to work inside - // iframes though (like in jsFiddle). - var subPixelFix, rect; - if (isFirefox && container.getBoundingClientRect) { - renderer.subPixelFix = subPixelFix = function () { - css(container, { left: 0, top: 0 }); - rect = container.getBoundingClientRect(); - css(container, { - left: (mathCeil(rect.left) - rect.left) + PX, - top: (mathCeil(rect.top) - rect.top) + PX - }); - }; - - // run the fix now - subPixelFix(); - - // run it on resize - addEvent(win, 'resize', subPixelFix); - } - }, - - /** - * Detect whether the renderer is hidden. This happens when one of the parent elements - * has display: none. #608. - */ - isHidden: function () { - return !this.boxWrapper.getBBox().width; - }, - - /** - * Destroys the renderer and its allocated members. - */ - destroy: function () { - var renderer = this, - rendererDefs = renderer.defs; - renderer.box = null; - renderer.boxWrapper = renderer.boxWrapper.destroy(); - - // Call destroy on all gradient elements - destroyObjectProperties(renderer.gradients || {}); - renderer.gradients = null; - - // Defs are null in VMLRenderer - // Otherwise, destroy them here. - if (rendererDefs) { - renderer.defs = rendererDefs.destroy(); - } - - // Remove sub pixel fix handler - // We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed - // See issue #982 - if (renderer.subPixelFix) { - removeEvent(win, 'resize', renderer.subPixelFix); - } - - renderer.alignedObjects = null; - - return null; - }, - - /** - * Create a wrapper for an SVG element - * @param {Object} nodeName - */ - createElement: function (nodeName) { - var wrapper = new this.Element(); - wrapper.init(this, nodeName); - return wrapper; - }, - - /** - * Dummy function for use in canvas renderer - */ - draw: function () {}, - - /** - * Parse a simple HTML string into SVG tspans - * - * @param {Object} textNode The parent text SVG node - */ - buildText: function (wrapper) { - var textNode = wrapper.element, - lines = pick(wrapper.textStr, '').toString() - .replace(/<(b|strong)>/g, '') - .replace(/<(i|em)>/g, '') - .replace(/
/g, '') - .split(//g), - childNodes = textNode.childNodes, - styleRegex = /style="([^"]+)"/, - hrefRegex = /href="([^"]+)"/, - parentX = attr(textNode, 'x'), - textStyles = wrapper.styles, - width = textStyles && textStyles.width && pInt(textStyles.width), - textLineHeight = textStyles && textStyles.lineHeight, - lastLine, - GET_COMPUTED_STYLE = 'getComputedStyle', - i = childNodes.length, - linePositions = []; - - // Needed in IE9 because it doesn't report tspan's offsetHeight (#893) - function getLineHeightByBBox(lineNo) { - linePositions[lineNo] = textNode.getBBox ? - textNode.getBBox().height : - wrapper.renderer.fontMetrics(textNode.style.fontSize).h; // #990 - return mathRound(linePositions[lineNo] - (linePositions[lineNo - 1] || 0)); - } - - // remove old text - while (i--) { - textNode.removeChild(childNodes[i]); - } - - if (width && !wrapper.added) { - this.box.appendChild(textNode); // attach it to the DOM to read offset width - } - - // remove empty line at end - if (lines[lines.length - 1] === '') { - lines.pop(); - } - - // build the lines - each(lines, function (line, lineNo) { - var spans, spanNo = 0, lineHeight; - - line = line.replace(//g, '|||'); - spans = line.split('|||'); - - each(spans, function (span) { - if (span !== '' || spans.length === 1) { - var attributes = {}, - tspan = doc.createElementNS(SVG_NS, 'tspan'), - spanStyle; // #390 - if (styleRegex.test(span)) { - spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2'); - attr(tspan, 'style', spanStyle); - } - if (hrefRegex.test(span)) { - attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"'); - css(tspan, { cursor: 'pointer' }); - } - - span = (span.replace(/<(.|\n)*?>/g, '') || ' ') - .replace(/</g, '<') - .replace(/>/g, '>'); - - // issue #38 workaround. - /*if (reverse) { - arr = []; - i = span.length; - while (i--) { - arr.push(span.charAt(i)); - } - span = arr.join(''); - }*/ - - // add the text node - tspan.appendChild(doc.createTextNode(span)); - - if (!spanNo) { // first span in a line, align it to the left - attributes.x = parentX; - } else { - // Firefox ignores spaces at the front or end of the tspan - attributes.dx = 3; // space - } - - // first span on subsequent line, add the line height - if (!spanNo) { - if (lineNo) { - - // allow getting the right offset height in exporting in IE - if (!hasSVG && wrapper.renderer.forExport) { - css(tspan, { display: 'block' }); - } - - // Webkit and opera sometimes return 'normal' as the line height. In that - // case, webkit uses offsetHeight, while Opera falls back to 18 - lineHeight = win[GET_COMPUTED_STYLE] && - pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height')); - - if (!lineHeight || isNaN(lineHeight)) { - lineHeight = textLineHeight || lastLine.offsetHeight || getLineHeightByBBox(lineNo) || 18; - } - attr(tspan, 'dy', lineHeight); - } - lastLine = tspan; // record for use in next line - } - - // add attributes - attr(tspan, attributes); - - // append it - textNode.appendChild(tspan); - - spanNo++; - - // check width and apply soft breaks - if (width) { - var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273 - tooLong, - actualWidth, - rest = []; - - while (words.length || rest.length) { - delete wrapper.bBox; // delete cache - actualWidth = wrapper.getBBox().width; - tooLong = actualWidth > width; - if (!tooLong || words.length === 1) { // new line needed - words = rest; - rest = []; - if (words.length) { - tspan = doc.createElementNS(SVG_NS, 'tspan'); - attr(tspan, { - dy: textLineHeight || 16, - x: parentX - }); - if (spanStyle) { // #390 - attr(tspan, 'style', spanStyle); - } - textNode.appendChild(tspan); - - if (actualWidth > width) { // a single word is pressing it out - width = actualWidth; - } - } - } else { // append to existing line tspan - tspan.removeChild(tspan.firstChild); - rest.unshift(words.pop()); - } - if (words.length) { - tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-'))); - } - } - } - } - }); - }); - }, - - /** - * Create a button with preset states - * @param {String} text - * @param {Number} x - * @param {Number} y - * @param {Function} callback - * @param {Object} normalState - * @param {Object} hoverState - * @param {Object} pressedState - */ - button: function (text, x, y, callback, normalState, hoverState, pressedState) { - var label = this.label(text, x, y), - curState = 0, - stateOptions, - stateStyle, - normalStyle, - hoverStyle, - pressedStyle, - STYLE = 'style', - verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 }; - - // prepare the attributes - /*jslint white: true*/ - normalState = merge(hash( - STROKE_WIDTH, 1, - STROKE, '#999', - FILL, hash( - LINEAR_GRADIENT, verticalGradient, - STOPS, [ - [0, '#FFF'], - [1, '#DDD'] - ] - ), - 'r', 3, - 'padding', 3, - STYLE, hash( - 'color', 'black' - ) - ), normalState); - /*jslint white: false*/ - normalStyle = normalState[STYLE]; - delete normalState[STYLE]; - - /*jslint white: true*/ - hoverState = merge(normalState, hash( - STROKE, '#68A', - FILL, hash( - LINEAR_GRADIENT, verticalGradient, - STOPS, [ - [0, '#FFF'], - [1, '#ACF'] - ] - ) - ), hoverState); - /*jslint white: false*/ - hoverStyle = hoverState[STYLE]; - delete hoverState[STYLE]; - - /*jslint white: true*/ - pressedState = merge(normalState, hash( - STROKE, '#68A', - FILL, hash( - LINEAR_GRADIENT, verticalGradient, - STOPS, [ - [0, '#9BD'], - [1, '#CDF'] - ] - ) - ), pressedState); - /*jslint white: false*/ - pressedStyle = pressedState[STYLE]; - delete pressedState[STYLE]; - - // add the events - addEvent(label.element, 'mouseenter', function () { - label.attr(hoverState) - .css(hoverStyle); - }); - addEvent(label.element, 'mouseleave', function () { - stateOptions = [normalState, hoverState, pressedState][curState]; - stateStyle = [normalStyle, hoverStyle, pressedStyle][curState]; - label.attr(stateOptions) - .css(stateStyle); - }); - - label.setState = function (state) { - curState = state; - if (!state) { - label.attr(normalState) - .css(normalStyle); - } else if (state === 2) { - label.attr(pressedState) - .css(pressedStyle); - } - }; - - return label - .on('click', function () { - callback.call(label); - }) - .attr(normalState) - .css(extend({ cursor: 'default' }, normalStyle)); - }, - - /** - * Make a straight line crisper by not spilling out to neighbour pixels - * @param {Array} points - * @param {Number} width - */ - crispLine: function (points, width) { - // points format: [M, 0, 0, L, 100, 0] - // normalize to a crisp line - if (points[1] === points[4]) { - // Substract due to #1129. Now bottom and left axis gridlines behave the same. - points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2); - } - if (points[2] === points[5]) { - points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2); - } - return points; - }, - - - /** - * Draw a path - * @param {Array} path An SVG path in array form - */ - path: function (path) { - var attr = { - fill: NONE - }; - if (isArray(path)) { - attr.d = path; - } else if (isObject(path)) { // attributes - extend(attr, path); - } - return this.createElement('path').attr(attr); - }, - - /** - * Draw and return an SVG circle - * @param {Number} x The x position - * @param {Number} y The y position - * @param {Number} r The radius - */ - circle: function (x, y, r) { - var attr = isObject(x) ? - x : - { - x: x, - y: y, - r: r - }; - - return this.createElement('circle').attr(attr); - }, - - /** - * Draw and return an arc - * @param {Number} x X position - * @param {Number} y Y position - * @param {Number} r Radius - * @param {Number} innerR Inner radius like used in donut charts - * @param {Number} start Starting angle - * @param {Number} end Ending angle - */ - arc: function (x, y, r, innerR, start, end) { - // arcs are defined as symbols for the ability to set - // attributes in attr and animate - - if (isObject(x)) { - y = x.y; - r = x.r; - innerR = x.innerR; - start = x.start; - end = x.end; - x = x.x; - } - return this.symbol('arc', x || 0, y || 0, r || 0, r || 0, { - innerR: innerR || 0, - start: start || 0, - end: end || 0 - }); - }, - - /** - * Draw and return a rectangle - * @param {Number} x Left position - * @param {Number} y Top position - * @param {Number} width - * @param {Number} height - * @param {Number} r Border corner radius - * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing - */ - rect: function (x, y, width, height, r, strokeWidth) { - - r = isObject(x) ? x.r : r; - - var wrapper = this.createElement('rect').attr({ - rx: r, - ry: r, - fill: NONE - }); - return wrapper.attr( - isObject(x) ? - x : - // do not crispify when an object is passed in (as in column charts) - wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)) - ); - }, - - /** - * Resize the box and re-align all aligned elements - * @param {Object} width - * @param {Object} height - * @param {Boolean} animate - * - */ - setSize: function (width, height, animate) { - var renderer = this, - alignedObjects = renderer.alignedObjects, - i = alignedObjects.length; - - renderer.width = width; - renderer.height = height; - - renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({ - width: width, - height: height - }); - - while (i--) { - alignedObjects[i].align(); - } - }, - - /** - * Create a group - * @param {String} name The group will be given a class name of 'highcharts-{name}'. - * This can be used for styling and scripting. - */ - g: function (name) { - var elem = this.createElement('g'); - return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem; - }, - - /** - * Display an image - * @param {String} src - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - image: function (src, x, y, width, height) { - var attribs = { - preserveAspectRatio: NONE - }, - elemWrapper; - - // optional properties - if (arguments.length > 1) { - extend(attribs, { - x: x, - y: y, - width: width, - height: height - }); - } - - elemWrapper = this.createElement('image').attr(attribs); - - // set the href in the xlink namespace - if (elemWrapper.element.setAttributeNS) { - elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink', - 'href', src); - } else { - // could be exporting in IE - // using href throws "not supported" in ie7 and under, requries regex shim to fix later - elemWrapper.element.setAttribute('hc-svg-href', src); - } - - return elemWrapper; - }, - - /** - * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object. - * - * @param {Object} symbol - * @param {Object} x - * @param {Object} y - * @param {Object} radius - * @param {Object} options - */ - symbol: function (symbol, x, y, width, height, options) { - - var obj, - - // get the symbol definition function - symbolFn = this.symbols[symbol], - - // check if there's a path defined for this symbol - path = symbolFn && symbolFn( - mathRound(x), - mathRound(y), - width, - height, - options - ), - - imageRegex = /^url\((.*?)\)$/, - imageSrc, - imageSize, - centerImage; - - if (path) { - - obj = this.path(path); - // expando properties for use in animate and attr - extend(obj, { - symbolName: symbol, - x: x, - y: y, - width: width, - height: height - }); - if (options) { - extend(obj, options); - } - - - // image symbols - } else if (imageRegex.test(symbol)) { - - // On image load, set the size and position - centerImage = function (img, size) { - img.attr({ - width: size[0], - height: size[1] - }); - - if (!img.alignByTranslate) { // #185 - img.translate( - -mathRound(size[0] / 2), - -mathRound(size[1] / 2) - ); - } - }; - - imageSrc = symbol.match(imageRegex)[1]; - imageSize = symbolSizes[imageSrc]; - - // create the image synchronously, add attribs async - obj = this.image(imageSrc) - .attr({ - x: x, - y: y - }); - - if (imageSize) { - centerImage(obj, imageSize); - } else { - // initialize image to be 0 size so export will still function if there's no cached sizes - obj.attr({ width: 0, height: 0 }); - - // create a dummy JavaScript image to get the width and height - createElement('img', { - onload: function () { - var img = this; - - centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]); - }, - src: imageSrc - }); - } - } - - return obj; - }, - - /** - * An extendable collection of functions for defining symbol paths. - */ - symbols: { - 'circle': function (x, y, w, h) { - var cpw = 0.166 * w; - return [ - M, x + w / 2, y, - 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h, - 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y, - 'Z' - ]; - }, - - 'square': function (x, y, w, h) { - return [ - M, x, y, - L, x + w, y, - x + w, y + h, - x, y + h, - 'Z' - ]; - }, - - 'triangle': function (x, y, w, h) { - return [ - M, x + w / 2, y, - L, x + w, y + h, - x, y + h, - 'Z' - ]; - }, - - 'triangle-down': function (x, y, w, h) { - return [ - M, x, y, - L, x + w, y, - x + w / 2, y + h, - 'Z' - ]; - }, - 'diamond': function (x, y, w, h) { - return [ - M, x + w / 2, y, - L, x + w, y + h / 2, - x + w / 2, y + h, - x, y + h / 2, - 'Z' - ]; - }, - 'arc': function (x, y, w, h, options) { - var start = options.start, - radius = options.r || w || h, - end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs - innerRadius = options.innerR, - open = options.open, - cosStart = mathCos(start), - sinStart = mathSin(start), - cosEnd = mathCos(end), - sinEnd = mathSin(end), - longArc = options.end - start < mathPI ? 0 : 1; - - return [ - M, - x + radius * cosStart, - y + radius * sinStart, - 'A', // arcTo - radius, // x radius - radius, // y radius - 0, // slanting - longArc, // long or short arc - 1, // clockwise - x + radius * cosEnd, - y + radius * sinEnd, - open ? M : L, - x + innerRadius * cosEnd, - y + innerRadius * sinEnd, - 'A', // arcTo - innerRadius, // x radius - innerRadius, // y radius - 0, // slanting - longArc, // long or short arc - 0, // clockwise - x + innerRadius * cosStart, - y + innerRadius * sinStart, - - open ? '' : 'Z' // close - ]; - } - }, - - /** - * Define a clipping rectangle - * @param {String} id - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - clipRect: function (x, y, width, height) { - var wrapper, - id = PREFIX + idCounter++, - - clipPath = this.createElement('clipPath').attr({ - id: id - }).add(this.defs); - - wrapper = this.rect(x, y, width, height, 0).add(clipPath); - wrapper.id = id; - wrapper.clipPath = clipPath; - - return wrapper; - }, - - - /** - * Take a color and return it if it's a string, make it a gradient if it's a - * gradient configuration object. Prior to Highstock, an array was used to define - * a linear gradient with pixel positions relative to the SVG. In newer versions - * we change the coordinates to apply relative to the shape, using coordinates - * 0-1 within the shape. To preserve backwards compatibility, linearGradient - * in this definition is an object of x1, y1, x2 and y2. - * - * @param {Object} color The color or config object - */ - color: function (color, elem, prop) { - var renderer = this, - colorObject, - regexRgba = /^rgba/, - gradName; - - // Apply linear or radial gradients - if (color && color.linearGradient) { - gradName = 'linearGradient'; - } else if (color && color.radialGradient) { - gradName = 'radialGradient'; - } - - if (gradName) { - var gradAttr = color[gradName], - gradients = renderer.gradients, - gradientObject, - stopColor, - stopOpacity, - radialReference = elem.radialReference; - - // Check if a gradient object with the same config object is created within this renderer - if (!gradAttr.id || !gradients[gradAttr.id]) { - - // Keep < 2.2 kompatibility - if (isArray(gradAttr)) { - color[gradName] = gradAttr = { - x1: gradAttr[0], - y1: gradAttr[1], - x2: gradAttr[2], - y2: gradAttr[3], - gradientUnits: 'userSpaceOnUse' - }; - } - - // Correct the radial gradient for the radial reference system - if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) { - extend(gradAttr, { - cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2], - cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2], - r: gradAttr.r * radialReference[2], - gradientUnits: 'userSpaceOnUse' - }); - } - - // Set the id and create the element - gradAttr.id = PREFIX + idCounter++; - gradients[gradAttr.id] = gradientObject = renderer.createElement(gradName) - .attr(gradAttr) - .add(renderer.defs); - - - // The gradient needs to keep a list of stops to be able to destroy them - gradientObject.stops = []; - each(color.stops, function (stop) { - var stopObject; - if (regexRgba.test(stop[1])) { - colorObject = Color(stop[1]); - stopColor = colorObject.get('rgb'); - stopOpacity = colorObject.get('a'); - } else { - stopColor = stop[1]; - stopOpacity = 1; - } - stopObject = renderer.createElement('stop').attr({ - offset: stop[0], - 'stop-color': stopColor, - 'stop-opacity': stopOpacity - }).add(gradientObject); - - // Add the stop element to the gradient - gradientObject.stops.push(stopObject); - }); - } - - // Return the reference to the gradient object - return 'url(' + renderer.url + '#' + gradAttr.id + ')'; - - // Webkit and Batik can't show rgba. - } else if (regexRgba.test(color)) { - colorObject = Color(color); - attr(elem, prop + '-opacity', colorObject.get('a')); - - return colorObject.get('rgb'); - - - } else { - // Remove the opacity attribute added above. Does not throw if the attribute is not there. - elem.removeAttribute(prop + '-opacity'); - - return color; - } - - }, - - - /** - * Add text to the SVG object - * @param {String} str - * @param {Number} x Left position - * @param {Number} y Top position - * @param {Boolean} useHTML Use HTML to render the text - */ - text: function (str, x, y, useHTML) { - - // declare variables - var renderer = this, - defaultChartStyle = defaultOptions.chart.style, - fakeSVG = useCanVG || (!hasSVG && renderer.forExport), - wrapper; - - if (useHTML && !renderer.forExport) { - return renderer.html(str, x, y); - } - - x = mathRound(pick(x, 0)); - y = mathRound(pick(y, 0)); - - wrapper = renderer.createElement('text') - .attr({ - x: x, - y: y, - text: str - }) - .css({ - fontFamily: defaultChartStyle.fontFamily, - fontSize: defaultChartStyle.fontSize - }); - - // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063) - if (fakeSVG) { - wrapper.css({ - position: ABSOLUTE - }); - } - - wrapper.x = x; - wrapper.y = y; - return wrapper; - }, - - - /** - * Create HTML text node. This is used by the VML renderer as well as the SVG - * renderer through the useHTML option. - * - * @param {String} str - * @param {Number} x - * @param {Number} y - */ - html: function (str, x, y) { - var defaultChartStyle = defaultOptions.chart.style, - wrapper = this.createElement('span'), - attrSetters = wrapper.attrSetters, - element = wrapper.element, - renderer = wrapper.renderer; - - // Text setter - attrSetters.text = function (value) { - if (value !== element.innerHTML) { - delete this.bBox; - } - element.innerHTML = value; - return false; - }; - - // Various setters which rely on update transform - attrSetters.x = attrSetters.y = attrSetters.align = function (value, key) { - if (key === 'align') { - key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML. - } - wrapper[key] = value; - wrapper.htmlUpdateTransform(); - return false; - }; - - // Set the default attributes - wrapper.attr({ - text: str, - x: mathRound(x), - y: mathRound(y) - }) - .css({ - position: ABSOLUTE, - whiteSpace: 'nowrap', - fontFamily: defaultChartStyle.fontFamily, - fontSize: defaultChartStyle.fontSize - }); - - // Use the HTML specific .css method - wrapper.css = wrapper.htmlCss; - - // This is specific for HTML within SVG - if (renderer.isSVG) { - wrapper.add = function (svgGroupWrapper) { - - var htmlGroup, - container = renderer.box.parentNode, - parentGroup, - parents = []; - - // Create a mock group to hold the HTML elements - if (svgGroupWrapper) { - htmlGroup = svgGroupWrapper.div; - if (!htmlGroup) { - - // Read the parent chain into an array and read from top down - parentGroup = svgGroupWrapper; - while (parentGroup) { - - parents.push(parentGroup); - - // Move up to the next parent group - parentGroup = parentGroup.parentGroup; - } - - // Ensure dynamically updating position when any parent is translated - each(parents.reverse(), function (parentGroup) { - var htmlGroupStyle; - - // Create a HTML div and append it to the parent div to emulate - // the SVG group structure - htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, { - className: attr(parentGroup.element, 'class') - }, { - position: ABSOLUTE, - left: (parentGroup.translateX || 0) + PX, - top: (parentGroup.translateY || 0) + PX - }, htmlGroup || container); // the top group is appended to container - - // Shortcut - htmlGroupStyle = htmlGroup.style; - - // Set listeners to update the HTML div's position whenever the SVG group - // position is changed - extend(parentGroup.attrSetters, { - translateX: function (value) { - htmlGroupStyle.left = value + PX; - }, - translateY: function (value) { - htmlGroupStyle.top = value + PX; - }, - visibility: function (value, key) { - htmlGroupStyle[key] = value; - } - }); - }); - - } - } else { - htmlGroup = container; - } - - htmlGroup.appendChild(element); - - // Shared with VML: - wrapper.added = true; - if (wrapper.alignOnAdd) { - wrapper.htmlUpdateTransform(); - } - - return wrapper; - }; - } - return wrapper; - }, - - /** - * Utility to return the baseline offset and total line height from the font size - */ - fontMetrics: function (fontSize) { - fontSize = pInt(fontSize || 11); - - // Empirical values found by comparing font size and bounding box height. - // Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/ - var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2), - baseline = mathRound(lineHeight * 0.8); - - return { - h: lineHeight, - b: baseline - }; - }, - - /** - * Add a label, a text item that can hold a colored or gradient background - * as well as a border and shadow. - * @param {string} str - * @param {Number} x - * @param {Number} y - * @param {String} shape - * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the - * coordinates it should be pinned to - * @param {Number} anchorY - * @param {Boolean} baseline Whether to position the label relative to the text baseline, - * like renderer.text, or to the upper border of the rectangle. - * @param {String} className Class name for the group - */ - label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { - - var renderer = this, - wrapper = renderer.g(className), - text = renderer.text('', 0, 0, useHTML) - .attr({ - zIndex: 1 - }), - //.add(wrapper), - box, - bBox, - alignFactor = 0, - padding = 3, - width, - height, - wrapperX, - wrapperY, - crispAdjust = 0, - deferredAttr = {}, - baselineOffset, - attrSetters = wrapper.attrSetters; - - /** - * This function runs after the label is added to the DOM (when the bounding box is - * available), and after the text of the label is updated to detect the new bounding - * box and reflect it in the border box. - */ - function updateBoxSize() { - var boxY, - style = text.element.style; - - bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) && - text.getBBox(); - wrapper.width = (width || bBox.width || 0) + 2 * padding; - wrapper.height = (height || bBox.height || 0) + 2 * padding; - - // update the label-scoped y offset - baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b; - - - // create the border box if it is not already present - if (!box) { - boxY = baseline ? -baselineOffset : 0; - - wrapper.box = box = shape ? - renderer.symbol(shape, -alignFactor * padding, boxY, wrapper.width, wrapper.height) : - renderer.rect(-alignFactor * padding, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]); - box.add(wrapper); - } - - // apply the box attributes - box.attr(merge({ - width: wrapper.width, - height: wrapper.height - }, deferredAttr)); - deferredAttr = null; - } - - /** - * This function runs after setting text or padding, but only if padding is changed - */ - function updateTextPadding() { - var styles = wrapper.styles, - textAlign = styles && styles.textAlign, - x = padding * (1 - alignFactor), - y; - - // determin y based on the baseline - y = baseline ? 0 : baselineOffset; - - // compensate for alignment - if (defined(width) && (textAlign === 'center' || textAlign === 'right')) { - x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width); - } - - // update if anything changed - if (x !== text.x || y !== text.y) { - text.attr({ - x: x, - y: y - }); - } - - // record current values - text.x = x; - text.y = y; - } - - /** - * Set a box attribute, or defer it if the box is not yet created - * @param {Object} key - * @param {Object} value - */ - function boxAttr(key, value) { - if (box) { - box.attr(key, value); - } else { - deferredAttr[key] = value; - } - } - - function getSizeAfterAdd() { - text.add(wrapper); - wrapper.attr({ - text: str, // alignment is available now - x: x, - y: y - }); - - if (defined(anchorX)) { - wrapper.attr({ - anchorX: anchorX, - anchorY: anchorY - }); - } - } - - /** - * After the text element is added, get the desired size of the border box - * and add it before the text in the DOM. - */ - addEvent(wrapper, 'add', getSizeAfterAdd); - - /* - * Add specific attribute setters. - */ - - // only change local variables - attrSetters.width = function (value) { - width = value; - return false; - }; - attrSetters.height = function (value) { - height = value; - return false; - }; - attrSetters.padding = function (value) { - if (defined(value) && value !== padding) { - padding = value; - updateTextPadding(); - } - - return false; - }; - - // change local variable and set attribue as well - attrSetters.align = function (value) { - alignFactor = { left: 0, center: 0.5, right: 1 }[value]; - return false; // prevent setting text-anchor on the group - }; - - // apply these to the box and the text alike - attrSetters.text = function (value, key) { - text.attr(key, value); - updateBoxSize(); - updateTextPadding(); - return false; - }; - - // apply these to the box but not to the text - attrSetters[STROKE_WIDTH] = function (value, key) { - crispAdjust = value % 2 / 2; - boxAttr(key, value); - return false; - }; - attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) { - boxAttr(key, value); - return false; - }; - attrSetters.anchorX = function (value, key) { - anchorX = value; - boxAttr(key, value + crispAdjust - wrapperX); - return false; - }; - attrSetters.anchorY = function (value, key) { - anchorY = value; - boxAttr(key, value - wrapperY); - return false; - }; - - // rename attributes - attrSetters.x = function (value) { - wrapper.x = value; // for animation getter - value -= alignFactor * ((width || bBox.width) + padding); - wrapperX = mathRound(value); - - wrapper.attr('translateX', wrapperX); - return false; - }; - attrSetters.y = function (value) { - wrapperY = wrapper.y = mathRound(value); - wrapper.attr('translateY', value); - return false; - }; - - // Redirect certain methods to either the box or the text - var baseCss = wrapper.css; - return extend(wrapper, { - /** - * Pick up some properties and apply them to the text instead of the wrapper - */ - css: function (styles) { - if (styles) { - var textStyles = {}; - styles = merge({}, styles); // create a copy to avoid altering the original object (#537) - each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width'], function (prop) { - if (styles[prop] !== UNDEFINED) { - textStyles[prop] = styles[prop]; - delete styles[prop]; - } - }); - text.css(textStyles); - } - return baseCss.call(wrapper, styles); - }, - /** - * Return the bounding box of the box, not the group - */ - getBBox: function () { - return box.getBBox(); - }, - /** - * Apply the shadow to the box - */ - shadow: function (b) { - box.shadow(b); - return wrapper; - }, - /** - * Destroy and release memory. - */ - destroy: function () { - removeEvent(wrapper, 'add', getSizeAfterAdd); - - // Added by button implementation - removeEvent(wrapper.element, 'mouseenter'); - removeEvent(wrapper.element, 'mouseleave'); - - if (text) { - text = text.destroy(); - } - if (box) { - box = box.destroy(); - } - // Call base implementation to destroy the rest - SVGElement.prototype.destroy.call(wrapper); - } - }); - } -}; // end SVGRenderer - - -// general renderer -Renderer = SVGRenderer; - - -/* **************************************************************************** - * * - * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE * - * * - * For applications and websites that don't need IE support, like platform * - * targeted mobile apps and web apps, this code can be removed. * - * * - *****************************************************************************/ - -/** - * @constructor - */ -var VMLRenderer; -if (!hasSVG && !useCanVG) { - -/** - * The VML element wrapper. - */ -var VMLElement = { - - /** - * Initialize a new VML element wrapper. It builds the markup as a string - * to minimize DOM traffic. - * @param {Object} renderer - * @param {Object} nodeName - */ - init: function (renderer, nodeName) { - var wrapper = this, - markup = ['<', nodeName, ' filled="f" stroked="f"'], - style = ['position: ', ABSOLUTE, ';']; - - // divs and shapes need size - if (nodeName === 'shape' || nodeName === DIV) { - style.push('left:0;top:0;width:1px;height:1px;'); - } - if (docMode8) { - style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE); - } - - markup.push(' style="', style.join(''), '"/>'); - - // create element with default attributes and style - if (nodeName) { - markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ? - markup.join('') - : renderer.prepVML(markup); - wrapper.element = createElement(markup); - } - - wrapper.renderer = renderer; - wrapper.attrSetters = {}; - }, - - /** - * Add the node to the given parent - * @param {Object} parent - */ - add: function (parent) { - var wrapper = this, - renderer = wrapper.renderer, - element = wrapper.element, - box = renderer.box, - inverted = parent && parent.inverted, - - // get the parent node - parentNode = parent ? - parent.element || parent : - box; - - - // if the parent group is inverted, apply inversion on all children - if (inverted) { // only on groups - renderer.invertChild(element, parentNode); - } - - // append it - parentNode.appendChild(element); - - // align text after adding to be able to read offset - wrapper.added = true; - if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) { - wrapper.updateTransform(); - } - - // fire an event for internal hooks - fireEvent(wrapper, 'add'); - - return wrapper; - }, - - /** - * VML always uses htmlUpdateTransform - */ - updateTransform: SVGElement.prototype.htmlUpdateTransform, - - /** - * Get or set attributes - */ - attr: function (hash, val) { - var wrapper = this, - key, - value, - i, - result, - element = wrapper.element || {}, - elemStyle = element.style, - nodeName = element.nodeName, - renderer = wrapper.renderer, - symbolName = wrapper.symbolName, - hasSetSymbolSize, - shadows = wrapper.shadows, - skipAttr, - attrSetters = wrapper.attrSetters, - ret = wrapper; - - // single key-value pair - if (isString(hash) && defined(val)) { - key = hash; - hash = {}; - hash[key] = val; - } - - // used as a getter, val is undefined - if (isString(hash)) { - key = hash; - if (key === 'strokeWidth' || key === 'stroke-width') { - ret = wrapper.strokeweight; - } else { - ret = wrapper[key]; - } - - // setter - } else { - for (key in hash) { - value = hash[key]; - skipAttr = false; - - // check for a specific attribute setter - result = attrSetters[key] && attrSetters[key].call(wrapper, value, key); - - if (result !== false && value !== null) { // #620 - - if (result !== UNDEFINED) { - value = result; // the attribute setter has returned a new value to set - } - - - // prepare paths - // symbols - if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) { - // if one of the symbol size affecting parameters are changed, - // check all the others only once for each call to an element's - // .attr() method - if (!hasSetSymbolSize) { - wrapper.symbolAttr(hash); - - hasSetSymbolSize = true; - } - skipAttr = true; - - } else if (key === 'd') { - value = value || []; - wrapper.d = value.join(' '); // used in getter for animation - - // convert paths - i = value.length; - var convertedPath = []; - while (i--) { - - // Multiply by 10 to allow subpixel precision. - // Substracting half a pixel seems to make the coordinates - // align with SVG, but this hasn't been tested thoroughly - if (isNumber(value[i])) { - convertedPath[i] = mathRound(value[i] * 10) - 5; - } else if (value[i] === 'Z') { // close the path - convertedPath[i] = 'x'; - } else { - convertedPath[i] = value[i]; - } - - } - value = convertedPath.join(' ') || 'x'; - element.path = value; - - // update shadows - if (shadows) { - i = shadows.length; - while (i--) { - shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value; - } - } - skipAttr = true; - - // handle visibility - } else if (key === 'visibility') { - - // let the shadow follow the main element - if (shadows) { - i = shadows.length; - while (i--) { - shadows[i].style[key] = value; - } - } - - // Instead of toggling the visibility CSS property, move the div out of the viewport. - // This works around #61 and #586 - if (nodeName === 'DIV') { - value = value === HIDDEN ? '-999em' : 0; - key = 'top'; - } - - elemStyle[key] = value; - skipAttr = true; - - // directly mapped to css - } else if (key === 'zIndex') { - - if (value) { - elemStyle[key] = value; - } - skipAttr = true; - - // width and height - } else if (key === 'width' || key === 'height') { - - value = mathMax(0, value); // don't set width or height below zero (#311) - - this[key] = value; // used in getter - - // clipping rectangle special - if (wrapper.updateClipping) { - wrapper[key] = value; - wrapper.updateClipping(); - } else { - // normal - elemStyle[key] = value; - } - - skipAttr = true; - - // x and y - } else if (key === 'x' || key === 'y') { - wrapper[key] = value; // used in getter - elemStyle[{ x: 'left', y: 'top' }[key]] = value; - - // class name - } else if (key === 'class') { - // IE8 Standards mode has problems retrieving the className - element.className = value; - - // stroke - } else if (key === 'stroke') { - - value = renderer.color(value, element, key); - - key = 'strokecolor'; - - // stroke width - } else if (key === 'stroke-width' || key === 'strokeWidth') { - element.stroked = value ? true : false; - key = 'strokeweight'; - wrapper[key] = value; // used in getter, issue #113 - if (isNumber(value)) { - value += PX; - } - - // dashStyle - } else if (key === 'dashstyle') { - var strokeElem = element.getElementsByTagName('stroke')[0] || - createElement(renderer.prepVML(['']), null, null, element); - strokeElem[key] = value || 'solid'; - wrapper.dashstyle = value; /* because changing stroke-width will change the dash length - and cause an epileptic effect */ - skipAttr = true; - - // fill - } else if (key === 'fill') { - - if (nodeName === 'SPAN') { // text color - elemStyle.color = value; - } else { - element.filled = value !== NONE ? true : false; - - value = renderer.color(value, element, key, wrapper); - - key = 'fillcolor'; - } - - // rotation on VML elements - } else if (nodeName === 'shape' && key === 'rotation') { - wrapper[key] = value; - // Correction for the 1x1 size of the shape container. Used in gauge needles. - element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX; - element.style.top = mathRound(mathCos(value * deg2rad)) + PX; - - // translation for animation - } else if (key === 'translateX' || key === 'translateY' || key === 'rotation') { - wrapper[key] = value; - wrapper.updateTransform(); - - skipAttr = true; - - // text for rotated and non-rotated elements - } else if (key === 'text') { - this.bBox = null; - element.innerHTML = value; - skipAttr = true; - } - - if (!skipAttr) { - if (docMode8) { // IE8 setAttribute bug - element[key] = value; - } else { - attr(element, key, value); - } - } - - } - } - } - return ret; - }, - - /** - * Set the element's clipping to a predefined rectangle - * - * @param {String} id The id of the clip rectangle - */ - clip: function (clipRect) { - var wrapper = this, - clipMembers, - element = wrapper.element, - parentNode = element.parentNode, - cssRet; - - if (clipRect) { - clipMembers = clipRect.members; - clipMembers.push(wrapper); - wrapper.destroyClip = function () { - erase(clipMembers, wrapper); - }; - // Issue #863 workaround - related to #140, #61, #74 - if (parentNode && parentNode.className === 'highcharts-tracker' && !docMode8) { - css(element, { visibility: HIDDEN }); - } - cssRet = clipRect.getCSS(wrapper); - - } else { - if (wrapper.destroyClip) { - wrapper.destroyClip(); - } - cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214 - } - - return wrapper.css(cssRet); - - }, - - /** - * Set styles for the element - * @param {Object} styles - */ - css: SVGElement.prototype.htmlCss, - - /** - * Removes a child either by removeChild or move to garbageBin. - * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. - */ - safeRemoveChild: function (element) { - // discardElement will detach the node from its parent before attaching it - // to the garbage bin. Therefore it is important that the node is attached and have parent. - if (element.parentNode) { - discardElement(element); - } - }, - - /** - * Extend element.destroy by removing it from the clip members array - */ - destroy: function () { - if (this.destroyClip) { - this.destroyClip(); - } - - return SVGElement.prototype.destroy.apply(this); - }, - - /** - * Remove all child nodes of a group, except the v:group element - */ - empty: function () { - var element = this.element, - childNodes = element.childNodes, - i = childNodes.length, - node; - - while (i--) { - node = childNodes[i]; - node.parentNode.removeChild(node); - } - }, - - /** - * Add an event listener. VML override for normalizing event parameters. - * @param {String} eventType - * @param {Function} handler - */ - on: function (eventType, handler) { - // simplest possible event model for internal use - this.element['on' + eventType] = function () { - var evt = win.event; - evt.target = evt.srcElement; - handler(evt); - }; - return this; - }, - - /** - * In stacked columns, cut off the shadows so that they don't overlap - */ - cutOffPath: function (path, length) { - - var len; - - path = path.split(/[ ,]/); - len = path.length; - - if (len === 9 || len === 11) { - path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length; - } - return path.join(' '); - }, - - /** - * Apply a drop shadow by copying elements and giving them different strokes - * @param {Boolean|Object} shadowOptions - */ - shadow: function (shadowOptions, group, cutOff) { - var shadows = [], - i, - element = this.element, - renderer = this.renderer, - shadow, - elemStyle = element.style, - markup, - path = element.path, - strokeWidth, - modifiedPath, - shadowWidth, - shadowElementOpacity; - - // some times empty paths are not strings - if (path && typeof path.value !== 'string') { - path = 'x'; - } - modifiedPath = path; - - if (shadowOptions) { - shadowWidth = pick(shadowOptions.width, 3); - shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; - for (i = 1; i <= 3; i++) { - - strokeWidth = (shadowWidth * 2) + 1 - (2 * i); - - // Cut off shadows for stacked column items - if (cutOff) { - modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5); - } - - markup = ['']; - - shadow = createElement(renderer.prepVML(markup), - null, { - left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1), - top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1) - } - ); - if (cutOff) { - shadow.cutOff = strokeWidth + 1; - } - - // apply the opacity - markup = ['']; - createElement(renderer.prepVML(markup), null, null, shadow); - - - // insert it - if (group) { - group.element.appendChild(shadow); - } else { - element.parentNode.insertBefore(shadow, element); - } - - // record it - shadows.push(shadow); - - } - - this.shadows = shadows; - } - return this; - - } -}; -VMLElement = extendClass(SVGElement, VMLElement); - -/** - * The VML renderer - */ -var VMLRendererExtension = { // inherit SVGRenderer - - Element: VMLElement, - isIE8: userAgent.indexOf('MSIE 8.0') > -1, - - - /** - * Initialize the VMLRenderer - * @param {Object} container - * @param {Number} width - * @param {Number} height - */ - init: function (container, width, height) { - var renderer = this, - boxWrapper, - box; - - renderer.alignedObjects = []; - - boxWrapper = renderer.createElement(DIV); - box = boxWrapper.element; - box.style.position = RELATIVE; // for freeform drawing using renderer directly - container.appendChild(boxWrapper.element); - - - // generate the containing box - renderer.box = box; - renderer.boxWrapper = boxWrapper; - - - renderer.setSize(width, height, false); - - // The only way to make IE6 and IE7 print is to use a global namespace. However, - // with IE8 the only way to make the dynamic shapes visible in screen and print mode - // seems to be to add the xmlns attribute and the behaviour style inline. - if (!doc.namespaces.hcv) { - - doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml'); - - // setup default css - doc.createStyleSheet().cssText = - 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' + - '{ behavior:url(#default#VML); display: inline-block; } '; - - } - }, - - - /** - * Detect whether the renderer is hidden. This happens when one of the parent elements - * has display: none - */ - isHidden: function () { - return !this.box.offsetWidth; - }, - - /** - * Define a clipping rectangle. In VML it is accomplished by storing the values - * for setting the CSS style to all associated members. - * - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - clipRect: function (x, y, width, height) { - - // create a dummy element - var clipRect = this.createElement(), - isObj = isObject(x); - - // mimic a rectangle with its style object for automatic updating in attr - return extend(clipRect, { - members: [], - left: isObj ? x.x : x, - top: isObj ? x.y : y, - width: isObj ? x.width : width, - height: isObj ? x.height : height, - getCSS: function (wrapper) { - var inverted = wrapper.inverted, - rect = this, - top = rect.top, - left = rect.left, - right = left + rect.width, - bottom = top + rect.height, - ret = { - clip: 'rect(' + - mathRound(inverted ? left : top) + 'px,' + - mathRound(inverted ? bottom : right) + 'px,' + - mathRound(inverted ? right : bottom) + 'px,' + - mathRound(inverted ? top : left) + 'px)' - }; - - // issue 74 workaround - if (!inverted && docMode8 && wrapper.element.nodeName !== 'IMG') { - extend(ret, { - width: right + PX, - height: bottom + PX - }); - } - - return ret; - }, - - // used in attr and animation to update the clipping of all members - updateClipping: function () { - each(clipRect.members, function (member) { - member.css(clipRect.getCSS(member)); - }); - } - }); - - }, - - - /** - * Take a color and return it if it's a string, make it a gradient if it's a - * gradient configuration object, and apply opacity. - * - * @param {Object} color The color or config object - */ - color: function (color, elem, prop, wrapper) { - var renderer = this, - colorObject, - regexRgba = /^rgba/, - markup, - fillType, - ret = NONE; - - // Check for linear or radial gradient - if (color && color.linearGradient) { - fillType = 'gradient'; - } else if (color && color.radialGradient) { - fillType = 'pattern'; - } - - - if (fillType) { - - var stopColor, - stopOpacity, - gradient = color.linearGradient || color.radialGradient, - x1, - y1, - x2, - y2, - opacity1, - opacity2, - color1, - color2, - fillAttr = '', - stops = color.stops, - firstStop, - lastStop, - colors = [], - addFillNode = function () { - // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2 - // are reversed. - markup = ['']; - createElement(renderer.prepVML(markup), null, null, elem); - }; - - // Extend from 0 to 1 - firstStop = stops[0]; - lastStop = stops[stops.length - 1]; - if (firstStop[0] > 0) { - stops.unshift([ - 0, - firstStop[1] - ]); - } - if (lastStop[0] < 1) { - stops.push([ - 1, - lastStop[1] - ]); - } - - // Compute the stops - each(stops, function (stop, i) { - if (regexRgba.test(stop[1])) { - colorObject = Color(stop[1]); - stopColor = colorObject.get('rgb'); - stopOpacity = colorObject.get('a'); - } else { - stopColor = stop[1]; - stopOpacity = 1; - } - - // Build the color attribute - colors.push((stop[0] * 100) + '% ' + stopColor); - - // Only start and end opacities are allowed, so we use the first and the last - if (!i) { - opacity1 = stopOpacity; - color2 = stopColor; - } else { - opacity2 = stopOpacity; - color1 = stopColor; - } - }); - - // Apply the gradient to fills only. - if (prop === 'fill') { - - // Handle linear gradient angle - if (fillType === 'gradient') { - x1 = gradient.x1 || gradient[0] || 0; - y1 = gradient.y1 || gradient[1] || 0; - x2 = gradient.x2 || gradient[2] || 0; - y2 = gradient.y2 || gradient[3] || 0; - fillAttr = 'angle="' + (90 - math.atan( - (y2 - y1) / // y vector - (x2 - x1) // x vector - ) * 180 / mathPI) + '"'; - - addFillNode(); - - // Radial (circular) gradient - } else { - - var r = gradient.r, - sizex = r * 2, - sizey = r * 2, - cx = gradient.cx, - cy = gradient.cy, - radialReference = elem.radialReference, - bBox, - applyRadialGradient = function () { - if (radialReference) { - bBox = wrapper.getBBox(); - cx += (radialReference[0] - bBox.x) / bBox.width - 0.5; - cy += (radialReference[1] - bBox.y) / bBox.height - 0.5; - sizex *= radialReference[2] / bBox.width; - sizey *= radialReference[2] / bBox.height; - } - fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' + - 'size="' + sizex + ',' + sizey + '" ' + - 'origin="0.5,0.5" ' + - 'position="' + cx + ',' + cy + '" ' + - 'color2="' + color2 + '" '; - - addFillNode(); - }; - - // Apply radial gradient - if (wrapper.added) { - applyRadialGradient(); - } else { - // We need to know the bounding box to get the size and position right - addEvent(wrapper, 'add', applyRadialGradient); - } - - // The fill element's color attribute is broken in IE8 standards mode, so we - // need to set the parent shape's fillcolor attribute instead. - ret = color1; - } - - // Gradients are not supported for VML stroke, return the first color. #722. - } else { - ret = stopColor; - } - - // if the color is an rgba color, split it and add a fill node - // to hold the opacity component - } else if (regexRgba.test(color) && elem.tagName !== 'IMG') { - - colorObject = Color(color); - - markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>']; - createElement(this.prepVML(markup), null, null, elem); - - ret = colorObject.get('rgb'); - - - } else { - var strokeNodes = elem.getElementsByTagName(prop); - if (strokeNodes.length) { - strokeNodes[0].opacity = 1; - } - ret = color; - } - - return ret; - }, - - /** - * Take a VML string and prepare it for either IE8 or IE6/IE7. - * @param {Array} markup A string array of the VML markup to prepare - */ - prepVML: function (markup) { - var vmlStyle = 'display:inline-block;behavior:url(#default#VML);', - isIE8 = this.isIE8; - - markup = markup.join(''); - - if (isIE8) { // add xmlns and style inline - markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />'); - if (markup.indexOf('style="') === -1) { - markup = markup.replace('/>', ' style="' + vmlStyle + '" />'); - } else { - markup = markup.replace('style="', 'style="' + vmlStyle); - } - - } else { // add namespace - markup = markup.replace('<', ' 1) { - obj.attr({ - x: x, - y: y, - width: width, - height: height - }); - } - return obj; - }, - - /** - * VML uses a shape for rect to overcome bugs and rotation problems - */ - rect: function (x, y, width, height, r, strokeWidth) { - - if (isObject(x)) { - y = x.y; - width = x.width; - height = x.height; - strokeWidth = x.strokeWidth; - x = x.x; - } - var wrapper = this.symbol('rect'); - wrapper.r = r; - - return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))); - }, - - /** - * In the VML renderer, each child of an inverted div (group) is inverted - * @param {Object} element - * @param {Object} parentNode - */ - invertChild: function (element, parentNode) { - var parentStyle = parentNode.style; - css(element, { - flip: 'x', - left: pInt(parentStyle.width) - 1, - top: pInt(parentStyle.height) - 1, - rotation: -90 - }); - }, - - /** - * Symbol definitions that override the parent SVG renderer's symbols - * - */ - symbols: { - // VML specific arc function - arc: function (x, y, w, h, options) { - var start = options.start, - end = options.end, - radius = options.r || w || h, - cosStart = mathCos(start), - sinStart = mathSin(start), - cosEnd = mathCos(end), - sinEnd = mathSin(end), - innerRadius = options.innerR, - circleCorrection = 0.08 / radius, // #760 - innerCorrection = (innerRadius && 0.1 / innerRadius) || 0, - ret; - - if (end - start === 0) { // no angle, don't show it. - return ['x']; - - } else if (2 * mathPI - end + start < circleCorrection) { // full circle - // empirical correction found by trying out the limits for different radii - cosEnd = -circleCorrection; - } else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem - cosEnd = mathCos(start + innerCorrection); - } - - ret = [ - 'wa', // clockwise arc to - x - radius, // left - y - radius, // top - x + radius, // right - y + radius, // bottom - x + radius * cosStart, // start x - y + radius * sinStart, // start y - x + radius * cosEnd, // end x - y + radius * sinEnd // end y - ]; - - if (options.open && !innerRadius) { - ret.push( - 'e', - M, - x,// - innerRadius, - y// - innerRadius - ); - } - - ret.push( - 'at', // anti clockwise arc to - x - innerRadius, // left - y - innerRadius, // top - x + innerRadius, // right - y + innerRadius, // bottom - x + innerRadius * cosEnd, // start x - y + innerRadius * sinEnd, // start y - x + innerRadius * cosStart, // end x - y + innerRadius * sinStart, // end y - 'x', // finish path - 'e' // close - ); - - return ret; - - }, - // Add circle symbol path. This performs significantly faster than v:oval. - circle: function (x, y, w, h) { - - return [ - 'wa', // clockwisearcto - x, // left - y, // top - x + w, // right - y + h, // bottom - x + w, // start x - y + h / 2, // start y - x + w, // end x - y + h / 2, // end y - //'x', // finish path - 'e' // close - ]; - }, - /** - * Add rectangle symbol path which eases rotation and omits arcsize problems - * compared to the built-in VML roundrect shape - * - * @param {Number} left Left position - * @param {Number} top Top position - * @param {Number} r Border radius - * @param {Object} options Width and height - */ - - rect: function (left, top, width, height, options) { - - var right = left + width, - bottom = top + height, - ret, - r; - - // No radius, return the more lightweight square - if (!defined(options) || !options.r) { - ret = SVGRenderer.prototype.symbols.square.apply(0, arguments); - - // Has radius add arcs for the corners - } else { - - r = mathMin(options.r, width, height); - ret = [ - M, - left + r, top, - - L, - right - r, top, - 'wa', - right - 2 * r, top, - right, top + 2 * r, - right - r, top, - right, top + r, - - L, - right, bottom - r, - 'wa', - right - 2 * r, bottom - 2 * r, - right, bottom, - right, bottom - r, - right - r, bottom, - - L, - left + r, bottom, - 'wa', - left, bottom - 2 * r, - left + 2 * r, bottom, - left + r, bottom, - left, bottom - r, - - L, - left, top + r, - 'wa', - left, top, - left + 2 * r, top + 2 * r, - left, top + r, - left + r, top, - - - 'x', - 'e' - ]; - } - return ret; - } - } -}; -VMLRenderer = function () { - this.init.apply(this, arguments); -}; -VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension); - - // general renderer - Renderer = VMLRenderer; -} - -/* **************************************************************************** - * * - * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE * - * * - *****************************************************************************/ -/* **************************************************************************** - * * - * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT * - * TARGETING THAT SYSTEM. * - * * - *****************************************************************************/ -var CanVGRenderer, - CanVGController; - -if (useCanVG) { - /** - * The CanVGRenderer is empty from start to keep the source footprint small. - * When requested, the CanVGController downloads the rest of the source packaged - * together with the canvg library. - */ - CanVGRenderer = function () { - // Override the global SVG namespace to fake SVG/HTML that accepts CSS - SVG_NS = 'http://www.w3.org/1999/xhtml'; - }; - - /** - * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but - * the implementation from SvgRenderer will not be merged in until first render. - */ - CanVGRenderer.prototype.symbols = {}; - - /** - * Handles on demand download of canvg rendering support. - */ - CanVGController = (function () { - // List of renderering calls - var deferredRenderCalls = []; - - /** - * When downloaded, we are ready to draw deferred charts. - */ - function drawDeferred() { - var callLength = deferredRenderCalls.length, - callIndex; - - // Draw all pending render calls - for (callIndex = 0; callIndex < callLength; callIndex++) { - deferredRenderCalls[callIndex](); - } - // Clear the list - deferredRenderCalls = []; - } - - return { - push: function (func, scriptLocation) { - // Only get the script once - if (deferredRenderCalls.length === 0) { - getScript(scriptLocation, drawDeferred); - } - // Register render call - deferredRenderCalls.push(func); - } - }; - }()); -} // end CanVGRenderer - -/* **************************************************************************** - * * - * END OF ANDROID < 3 SPECIFIC CODE * - * * - *****************************************************************************/ - -/** - * General renderer - */ -Renderer = VMLRenderer || CanVGRenderer || SVGRenderer; -/** - * The Tick class - */ -function Tick(axis, pos, type) { - this.axis = axis; - this.pos = pos; - this.type = type || ''; - this.isNew = true; - - if (!type) { - this.addLabel(); - } -} - -Tick.prototype = { - /** - * Write the tick label - */ - addLabel: function () { - var tick = this, - axis = tick.axis, - options = axis.options, - chart = axis.chart, - horiz = axis.horiz, - categories = axis.categories, - pos = tick.pos, - labelOptions = options.labels, - str, - tickPositions = axis.tickPositions, - width = (categories && horiz && categories.length && - !labelOptions.step && !labelOptions.staggerLines && - !labelOptions.rotation && - chart.plotWidth / tickPositions.length) || - (!horiz && chart.plotWidth / 2), - isFirst = pos === tickPositions[0], - isLast = pos === tickPositions[tickPositions.length - 1], - css, - attr, - value = categories && defined(categories[pos]) ? categories[pos] : pos, - label = tick.label, - tickPositionInfo = tickPositions.info, - dateTimeLabelFormat; - - // Set the datetime label format. If a higher rank is set for this position, use that. If not, - // use the general format. - if (axis.isDatetimeAxis && tickPositionInfo) { - dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName]; - } - - // set properties for access in render method - tick.isFirst = isFirst; - tick.isLast = isLast; - - // get the string - str = axis.labelFormatter.call({ - axis: axis, - chart: chart, - isFirst: isFirst, - isLast: isLast, - dateTimeLabelFormat: dateTimeLabelFormat, - value: axis.isLog ? correctFloat(lin2log(value)) : value - }); - - // prepare CSS - css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX }; - css = extend(css, labelOptions.style); - - // first call - if (!defined(label)) { - attr = { - align: labelOptions.align - }; - if (isNumber(labelOptions.rotation)) { - attr.rotation = labelOptions.rotation; - } - tick.label = - defined(str) && labelOptions.enabled ? - chart.renderer.text( - str, - 0, - 0, - labelOptions.useHTML - ) - .attr(attr) - // without position absolute, IE export sometimes is wrong - .css(css) - .add(axis.labelGroup) : - null; - - // update - } else if (label) { - label.attr({ - text: str - }) - .css(css); - } - }, - - /** - * Get the offset height or width of the label - */ - getLabelSize: function () { - var label = this.label, - axis = this.axis; - return label ? - ((this.labelBBox = label.getBBox()))[axis.horiz ? 'height' : 'width'] : - 0; - }, - - /** - * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision - * detection with overflow logic. - */ - getLabelSides: function () { - var bBox = this.labelBBox, // assume getLabelSize has run at this point - axis = this.axis, - options = axis.options, - labelOptions = options.labels, - width = bBox.width, - leftSide = width * { left: 0, center: 0.5, right: 1 }[labelOptions.align] - labelOptions.x; - - return [-leftSide, width - leftSide]; - }, - - /** - * Handle the label overflow by adjusting the labels to the left and right edge, or - * hide them if they collide into the neighbour label. - */ - handleOverflow: function (index, xy) { - var show = true, - axis = this.axis, - chart = axis.chart, - isFirst = this.isFirst, - isLast = this.isLast, - x = xy.x, - reversed = axis.reversed, - tickPositions = axis.tickPositions; - - if (isFirst || isLast) { - - var sides = this.getLabelSides(), - leftSide = sides[0], - rightSide = sides[1], - plotLeft = chart.plotLeft, - plotRight = plotLeft + axis.len, - neighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]], - neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1]; - - if ((isFirst && !reversed) || (isLast && reversed)) { - // Is the label spilling out to the left of the plot area? - if (x + leftSide < plotLeft) { - - // Align it to plot left - x = plotLeft - leftSide; - - // Hide it if it now overlaps the neighbour label - if (neighbour && x + rightSide > neighbourEdge) { - show = false; - } - } - - } else { - // Is the label spilling out to the right of the plot area? - if (x + rightSide > plotRight) { - - // Align it to plot right - x = plotRight - rightSide; - - // Hide it if it now overlaps the neighbour label - if (neighbour && x + leftSide < neighbourEdge) { - show = false; - } - - } - } - - // Set the modified x position of the label - xy.x = x; - } - return show; - }, - - /** - * Get the x and y position for ticks and labels - */ - getPosition: function (horiz, pos, tickmarkOffset, old) { - var axis = this.axis, - chart = axis.chart, - cHeight = (old && chart.oldChartHeight) || chart.chartHeight; - - return { - x: horiz ? - axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB : - axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0), - - y: horiz ? - cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) : - cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB - }; - - }, - - /** - * Get the x, y position of the tick label - */ - getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { - var axis = this.axis, - transA = axis.transA, - reversed = axis.reversed, - staggerLines = axis.staggerLines; - - x = x + labelOptions.x - (tickmarkOffset && horiz ? - tickmarkOffset * transA * (reversed ? -1 : 1) : 0); - y = y + labelOptions.y - (tickmarkOffset && !horiz ? - tickmarkOffset * transA * (reversed ? 1 : -1) : 0); - - // Vertically centered - if (!defined(labelOptions.y)) { - y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2; - } - - // Correct for staggered labels - if (staggerLines) { - y += (index / (step || 1) % staggerLines) * 16; - } - - return { - x: x, - y: y - }; - }, - - /** - * Extendible method to return the path of the marker - */ - getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) { - return renderer.crispLine([ - M, - x, - y, - L, - x + (horiz ? 0 : -tickLength), - y + (horiz ? tickLength : 0) - ], tickWidth); - }, - - /** - * Put everything in place - * - * @param index {Number} - * @param old {Boolean} Use old coordinates to prepare an animation into new position - */ - render: function (index, old) { - var tick = this, - axis = tick.axis, - options = axis.options, - chart = axis.chart, - renderer = chart.renderer, - horiz = axis.horiz, - type = tick.type, - label = tick.label, - pos = tick.pos, - labelOptions = options.labels, - gridLine = tick.gridLine, - gridPrefix = type ? type + 'Grid' : 'grid', - tickPrefix = type ? type + 'Tick' : 'tick', - gridLineWidth = options[gridPrefix + 'LineWidth'], - gridLineColor = options[gridPrefix + 'LineColor'], - dashStyle = options[gridPrefix + 'LineDashStyle'], - tickLength = options[tickPrefix + 'Length'], - tickWidth = options[tickPrefix + 'Width'] || 0, - tickColor = options[tickPrefix + 'Color'], - tickPosition = options[tickPrefix + 'Position'], - gridLinePath, - mark = tick.mark, - markPath, - step = labelOptions.step, - attribs, - show = true, - tickmarkOffset = axis.tickmarkOffset, - xy = tick.getPosition(horiz, pos, tickmarkOffset, old), - x = xy.x, - y = xy.y, - staggerLines = axis.staggerLines; - - // create the grid line - if (gridLineWidth) { - gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old); - - if (gridLine === UNDEFINED) { - attribs = { - stroke: gridLineColor, - 'stroke-width': gridLineWidth - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - if (!type) { - attribs.zIndex = 1; - } - tick.gridLine = gridLine = - gridLineWidth ? - renderer.path(gridLinePath) - .attr(attribs).add(axis.gridGroup) : - null; - } - - // If the parameter 'old' is set, the current call will be followed - // by another call, therefore do not do any animations this time - if (!old && gridLine && gridLinePath) { - gridLine[tick.isNew ? 'attr' : 'animate']({ - d: gridLinePath - }); - } - } - - // create the tick mark - if (tickWidth && tickLength) { - - // negate the length - if (tickPosition === 'inside') { - tickLength = -tickLength; - } - if (axis.opposite) { - tickLength = -tickLength; - } - - markPath = tick.getMarkPath(x, y, tickLength, tickWidth, horiz, renderer); - - if (mark) { // updating - mark.animate({ - d: markPath - }); - } else { // first time - tick.mark = renderer.path( - markPath - ).attr({ - stroke: tickColor, - 'stroke-width': tickWidth - }).add(axis.axisGroup); - } - } - - // the label is created on init - now move it into place - if (label && !isNaN(x)) { - label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step); - - // apply show first and show last - if ((tick.isFirst && !pick(options.showFirstLabel, 1)) || - (tick.isLast && !pick(options.showLastLabel, 1))) { - show = false; - - // Handle label overflow and show or hide accordingly - } else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index, xy)) { - show = false; - } - - // apply step - if (step && index % step) { - // show those indices dividable by step - show = false; - } - - // Set the new position, and show or hide - if (show) { - label[tick.isNew ? 'attr' : 'animate'](xy); - label.show(); - tick.isNew = false; - } else { - label.hide(); - } - } - }, - - /** - * Destructor for the tick prototype - */ - destroy: function () { - destroyObjectProperties(this, this.axis); - } -}; - -/** - * The object wrapper for plot lines and plot bands - * @param {Object} options - */ -function PlotLineOrBand(axis, options) { - this.axis = axis; - - if (options) { - this.options = options; - this.id = options.id; - } - - //plotLine.render() - return this; -} - -PlotLineOrBand.prototype = { - - /** - * Render the plot line or plot band. If it is already existing, - * move it. - */ - render: function () { - var plotLine = this, - axis = plotLine.axis, - horiz = axis.horiz, - halfPointRange = (axis.pointRange || 0) / 2, - options = plotLine.options, - optionsLabel = options.label, - label = plotLine.label, - width = options.width, - to = options.to, - from = options.from, - isBand = defined(from) && defined(to), - value = options.value, - dashStyle = options.dashStyle, - svgElem = plotLine.svgElem, - path = [], - addEvent, - eventType, - xs, - ys, - x, - y, - color = options.color, - zIndex = options.zIndex, - events = options.events, - attribs, - renderer = axis.chart.renderer; - - // logarithmic conversion - if (axis.isLog) { - from = log2lin(from); - to = log2lin(to); - value = log2lin(value); - } - - // plot line - if (width) { - path = axis.getPlotLinePath(value, width); - attribs = { - stroke: color, - 'stroke-width': width - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - } else if (isBand) { // plot band - - // keep within plot area - from = mathMax(from, axis.min - halfPointRange); - to = mathMin(to, axis.max + halfPointRange); - - path = axis.getPlotBandPath(from, to, options); - attribs = { - fill: color - }; - if (options.borderWidth) { - attribs.stroke = options.borderColor; - attribs['stroke-width'] = options.borderWidth; - } - } else { - return; - } - // zIndex - if (defined(zIndex)) { - attribs.zIndex = zIndex; - } - - // common for lines and bands - if (svgElem) { - if (path) { - svgElem.animate({ - d: path - }, null, svgElem.onGetPath); - } else { - svgElem.hide(); - svgElem.onGetPath = function () { - svgElem.show(); - }; - } - } else if (path && path.length) { - plotLine.svgElem = svgElem = renderer.path(path) - .attr(attribs).add(); - - // events - if (events) { - addEvent = function (eventType) { - svgElem.on(eventType, function (e) { - events[eventType].apply(plotLine, [e]); - }); - }; - for (eventType in events) { - addEvent(eventType); - } - } - } - - // the plot band/line label - if (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) { - // apply defaults - optionsLabel = merge({ - align: horiz && isBand && 'center', - x: horiz ? !isBand && 4 : 10, - verticalAlign : !horiz && isBand && 'middle', - y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4, - rotation: horiz && !isBand && 90 - }, optionsLabel); - - // add the SVG element - if (!label) { - plotLine.label = label = renderer.text( - optionsLabel.text, - 0, - 0 - ) - .attr({ - align: optionsLabel.textAlign || optionsLabel.align, - rotation: optionsLabel.rotation, - zIndex: zIndex - }) - .css(optionsLabel.style) - .add(); - } - - // get the bounding box and align the label - xs = [path[1], path[4], pick(path[6], path[1])]; - ys = [path[2], path[5], pick(path[7], path[2])]; - x = arrayMin(xs); - y = arrayMin(ys); - - label.align(optionsLabel, false, { - x: x, - y: y, - width: arrayMax(xs) - x, - height: arrayMax(ys) - y - }); - label.show(); - - } else if (label) { // move out of sight - label.hide(); - } - - // chainable - return plotLine; - }, - - /** - * Remove the plot line or band - */ - destroy: function () { - var plotLine = this, - axis = plotLine.axis; - - // remove it from the lookup - erase(axis.plotLinesAndBands, plotLine); - - destroyObjectProperties(plotLine, this.axis); - } -}; -/** - * The class for stack items - */ -function StackItem(axis, options, isNegative, x, stackOption, stacking) { - - var inverted = axis.chart.inverted; - - this.axis = axis; - - // Tells if the stack is negative - this.isNegative = isNegative; - - // Save the options to be able to style the label - this.options = options; - - // Save the x value to be able to position the label later - this.x = x; - - // Save the stack option on the series configuration object, and whether to treat it as percent - this.stack = stackOption; - this.percent = stacking === 'percent'; - - // The align options and text align varies on whether the stack is negative and - // if the chart is inverted or not. - // First test the user supplied value, then use the dynamic. - this.alignOptions = { - align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'), - verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')), - y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)), - x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0) - }; - - this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center'); -} - -StackItem.prototype = { - destroy: function () { - destroyObjectProperties(this, this.axis); - }, - - /** - * Sets the total of this stack. Should be called when a serie is hidden or shown - * since that will affect the total of other stacks. - */ - setTotal: function (total) { - this.total = total; - this.cum = total; - }, - - /** - * Renders the stack total label and adds it to the stack label group. - */ - render: function (group) { - var str = this.options.formatter.call(this); // format the text in the label - - // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden - if (this.label) { - this.label.attr({text: str, visibility: HIDDEN}); - // Create new label - } else { - this.label = - this.axis.chart.renderer.text(str, 0, 0) // dummy positions, actual position updated with setOffset method in columnseries - .css(this.options.style) // apply style - .attr({ - align: this.textAlign, // fix the text-anchor - rotation: this.options.rotation, // rotation - visibility: HIDDEN // hidden until setOffset is called - }) - .add(group); // add to the labels-group - } - }, - - /** - * Sets the offset that the stack has from the x value and repositions the label. - */ - setOffset: function (xOffset, xWidth) { - var stackItem = this, - axis = stackItem.axis, - chart = axis.chart, - inverted = chart.inverted, - neg = this.isNegative, // special treatment is needed for negative stacks - y = axis.translate(this.percent ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates - yZero = axis.translate(0), // stack origin - h = mathAbs(y - yZero), // stack height - x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position - plotHeight = chart.plotHeight, - stackBox = { // this is the box for the complete stack - x: inverted ? (neg ? y : y - h) : x, - y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y), - width: inverted ? h : xWidth, - height: inverted ? xWidth : h - }, - label = this.label, - alignAttr; - - if (label) { - label.align(this.alignOptions, null, stackBox); // align the label to the box - - // Set visibility (#678) - alignAttr = label.alignAttr; - label.attr({ - visibility: this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ? - (hasSVG ? 'inherit' : VISIBLE) : - HIDDEN - }); - } - } -}; -/** - * Create a new axis object - * @param {Object} chart - * @param {Object} options - */ -function Axis() { - this.init.apply(this, arguments); -} - -Axis.prototype = { - - /** - * Default options for the X axis - the Y axis has extended defaults - */ - defaultOptions: { - // allowDecimals: null, - // alternateGridColor: null, - // categories: [], - dateTimeLabelFormats: { - millisecond: '%H:%M:%S.%L', - second: '%H:%M:%S', - minute: '%H:%M', - hour: '%H:%M', - day: '%e. %b', - week: '%e. %b', - month: '%b \'%y', - year: '%Y' - }, - endOnTick: false, - gridLineColor: '#C0C0C0', - // gridLineDashStyle: 'solid', - // gridLineWidth: 0, - // reversed: false, - - labels: defaultLabelOptions, - // { step: null }, - lineColor: '#C0D0E0', - lineWidth: 1, - //linkedTo: null, - //max: undefined, - //min: undefined, - minPadding: 0.01, - maxPadding: 0.01, - //minRange: null, - minorGridLineColor: '#E0E0E0', - // minorGridLineDashStyle: null, - minorGridLineWidth: 1, - minorTickColor: '#A0A0A0', - //minorTickInterval: null, - minorTickLength: 2, - minorTickPosition: 'outside', // inside or outside - //minorTickWidth: 0, - //opposite: false, - //offset: 0, - //plotBands: [{ - // events: {}, - // zIndex: 1, - // labels: { align, x, verticalAlign, y, style, rotation, textAlign } - //}], - //plotLines: [{ - // events: {} - // dashStyle: {} - // zIndex: - // labels: { align, x, verticalAlign, y, style, rotation, textAlign } - //}], - //reversed: false, - // showFirstLabel: true, - // showLastLabel: true, - startOfWeek: 1, - startOnTick: false, - tickColor: '#C0D0E0', - //tickInterval: null, - tickLength: 5, - tickmarkPlacement: 'between', // on or between - tickPixelInterval: 100, - tickPosition: 'outside', - tickWidth: 1, - title: { - //text: null, - align: 'middle', // low, middle or high - //margin: 0 for horizontal, 10 for vertical axes, - //rotation: 0, - //side: 'outside', - style: { - color: '#6D869F', - //font: defaultFont.replace('normal', 'bold') - fontWeight: 'bold' - } - //x: 0, - //y: 0 - }, - type: 'linear' // linear, logarithmic or datetime - }, - - /** - * This options set extends the defaultOptions for Y axes - */ - defaultYAxisOptions: { - endOnTick: true, - gridLineWidth: 1, - tickPixelInterval: 72, - showLastLabel: true, - labels: { - align: 'right', - x: -8, - y: 3 - }, - lineWidth: 0, - maxPadding: 0.05, - minPadding: 0.05, - startOnTick: true, - tickWidth: 0, - title: { - rotation: 270, - text: 'Y-values' - }, - stackLabels: { - enabled: false, - //align: dynamic, - //y: dynamic, - //x: dynamic, - //verticalAlign: dynamic, - //textAlign: dynamic, - //rotation: 0, - formatter: function () { - return this.total; - }, - style: defaultLabelOptions.style - } - }, - - /** - * These options extend the defaultOptions for left axes - */ - defaultLeftAxisOptions: { - labels: { - align: 'right', - x: -8, - y: null - }, - title: { - rotation: 270 - } - }, - - /** - * These options extend the defaultOptions for right axes - */ - defaultRightAxisOptions: { - labels: { - align: 'left', - x: 8, - y: null - }, - title: { - rotation: 90 - } - }, - - /** - * These options extend the defaultOptions for bottom axes - */ - defaultBottomAxisOptions: { - labels: { - align: 'center', - x: 0, - y: 14 - // overflow: undefined, - // staggerLines: null - }, - title: { - rotation: 0 - } - }, - /** - * These options extend the defaultOptions for left axes - */ - defaultTopAxisOptions: { - labels: { - align: 'center', - x: 0, - y: -5 - // overflow: undefined - // staggerLines: null - }, - title: { - rotation: 0 - } - }, - - /** - * Initialize the axis - */ - init: function (chart, userOptions) { - - - var isXAxis = userOptions.isX, - axis = this; - - // Flag, is the axis horizontal - axis.horiz = chart.inverted ? !isXAxis : isXAxis; - - // Flag, isXAxis - axis.isXAxis = isXAxis; - axis.xOrY = isXAxis ? 'x' : 'y'; - - - axis.opposite = userOptions.opposite; // needed in setOptions - axis.side = axis.horiz ? - (axis.opposite ? 0 : 2) : // top : bottom - (axis.opposite ? 1 : 3); // right : left - - axis.setOptions(userOptions); - - - var options = this.options, - type = options.type, - isDatetimeAxis = type === 'datetime'; - - axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format - - - // Flag, stagger lines or not - axis.staggerLines = axis.horiz && options.labels.staggerLines; - axis.userOptions = userOptions; - - //axis.axisTitleMargin = UNDEFINED,// = options.title.margin, - axis.minPixelPadding = 0; - //axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series - //axis.ignoreMaxPadding = UNDEFINED; - - axis.chart = chart; - axis.reversed = options.reversed; - - // Initial categories - axis.categories = options.categories; - - // Elements - //axis.axisGroup = UNDEFINED; - //axis.gridGroup = UNDEFINED; - //axis.axisTitle = UNDEFINED; - //axis.axisLine = UNDEFINED; - - // Flag if type === logarithmic - axis.isLog = type === 'logarithmic'; - - // Flag, if axis is linked to another axis - axis.isLinked = defined(options.linkedTo); - // Linked axis. - //axis.linkedParent = UNDEFINED; - - // Flag if type === datetime - axis.isDatetimeAxis = isDatetimeAxis; - - // Flag if percentage mode - //axis.usePercentage = UNDEFINED; - - - // Tick positions - //axis.tickPositions = UNDEFINED; // array containing predefined positions - // Tick intervals - //axis.tickInterval = UNDEFINED; - //axis.minorTickInterval = UNDEFINED; - - axis.tickmarkOffset = (options.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0; - - // Major ticks - axis.ticks = {}; - // Minor ticks - axis.minorTicks = {}; - //axis.tickAmount = UNDEFINED; - - // List of plotLines/Bands - axis.plotLinesAndBands = []; - - // Alternate bands - axis.alternateBands = {}; - - // Axis metrics - //axis.left = UNDEFINED; - //axis.top = UNDEFINED; - //axis.width = UNDEFINED; - //axis.height = UNDEFINED; - //axis.bottom = UNDEFINED; - //axis.right = UNDEFINED; - //axis.transA = UNDEFINED; - //axis.transB = UNDEFINED; - //axis.oldTransA = UNDEFINED; - axis.len = 0; - //axis.oldMin = UNDEFINED; - //axis.oldMax = UNDEFINED; - //axis.oldUserMin = UNDEFINED; - //axis.oldUserMax = UNDEFINED; - //axis.oldAxisLength = UNDEFINED; - axis.minRange = axis.userMinRange = options.minRange || options.maxZoom; - axis.range = options.range; - axis.offset = options.offset || 0; - - - // Dictionary for stacks - axis.stacks = {}; - - // Min and max in the data - //axis.dataMin = UNDEFINED, - //axis.dataMax = UNDEFINED, - - // The axis range - axis.max = null; - axis.min = null; - - // User set min and max - //axis.userMin = UNDEFINED, - //axis.userMax = UNDEFINED, - - // Run Axis - - var eventType, - events = axis.options.events; - - // Register - chart.axes.push(axis); - chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis); - - axis.series = []; // populated by Series - - // inverted charts have reversed xAxes as default - if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) { - axis.reversed = true; - } - - axis.removePlotBand = axis.removePlotBandOrLine; - axis.removePlotLine = axis.removePlotBandOrLine; - axis.addPlotBand = axis.addPlotBandOrLine; - axis.addPlotLine = axis.addPlotBandOrLine; - - - // register event listeners - for (eventType in events) { - addEvent(axis, eventType, events[eventType]); - } - - // extend logarithmic axis - if (axis.isLog) { - axis.val2lin = log2lin; - axis.lin2val = lin2log; - } - }, - - /** - * Merge and set options - */ - setOptions: function (userOptions) { - this.options = merge( - this.defaultOptions, - this.isXAxis ? {} : this.defaultYAxisOptions, - [this.defaultTopAxisOptions, this.defaultRightAxisOptions, - this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side], - merge( - defaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053) - userOptions - ) - ); - }, - - - /** - * The default label formatter. The context is a special config object for the label. - */ - defaultLabelFormatter: function () { - var axis = this.axis, - value = this.value, - categories = axis.categories, - dateTimeLabelFormat = this.dateTimeLabelFormat, - numericSymbols = defaultOptions.lang.numericSymbols, - i = numericSymbols && numericSymbols.length, - multi, - ret, - - // make sure the same symbol is added for all labels on a linear axis - numericSymbolDetector = axis.isLog ? value : axis.tickInterval; - - if (categories) { - ret = value; - - } else if (dateTimeLabelFormat) { // datetime axis - ret = dateFormat(dateTimeLabelFormat, value); - - } else if (i && numericSymbolDetector >= 1000) { - // Decide whether we should add a numeric symbol like k (thousands) or M (millions). - // If we are to enable this in tooltip or other places as well, we can move this - // logic to the numberFormatter and enable it by a parameter. - while (i-- && ret === UNDEFINED) { - multi = Math.pow(1000, i + 1); - if (numericSymbolDetector >= multi && numericSymbols[i] !== null) { - ret = numberFormat(value / multi, -1) + numericSymbols[i]; - } - } - } - - if (ret === UNDEFINED) { - if (value >= 1000) { // add thousands separators - ret = numberFormat(value, 0); - - } else { // small numbers - ret = numberFormat(value, -1); - } - } - - return ret; - }, - - /** - * Get the minimum and maximum for the series of each axis - */ - getSeriesExtremes: function () { - var axis = this, - chart = axis.chart, - stacks = axis.stacks, - posStack = [], - negStack = [], - i; - - axis.hasVisibleSeries = false; - - // reset dataMin and dataMax in case we're redrawing - axis.dataMin = axis.dataMax = null; - - // loop through this axis' series - each(axis.series, function (series) { - - if (series.visible || !chart.options.chart.ignoreHiddenSeries) { - - var seriesOptions = series.options, - stacking, - posPointStack, - negPointStack, - stackKey, - stackOption, - negKey, - xData, - yData, - x, - y, - threshold = seriesOptions.threshold, - yDataLength, - activeYData = [], - activeCounter = 0; - - axis.hasVisibleSeries = true; - - // Validate threshold in logarithmic axes - if (axis.isLog && threshold <= 0) { - threshold = seriesOptions.threshold = null; - } - - // Get dataMin and dataMax for X axes - if (axis.isXAxis) { - xData = series.xData; - if (xData.length) { - axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData)); - axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData)); - } - - // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data - } else { - var isNegative, - pointStack, - key, - cropped = series.cropped, - xExtremes = series.xAxis.getExtremes(), - //findPointRange, - //pointRange, - j, - hasModifyValue = !!series.modifyValue; - - - // Handle stacking - stacking = seriesOptions.stacking; - axis.usePercentage = stacking === 'percent'; - - // create a stack for this particular series type - if (stacking) { - stackOption = seriesOptions.stack; - stackKey = series.type + pick(stackOption, ''); - negKey = '-' + stackKey; - series.stackKey = stackKey; // used in translate - - posPointStack = posStack[stackKey] || []; // contains the total values for each x - posStack[stackKey] = posPointStack; - - negPointStack = negStack[negKey] || []; - negStack[negKey] = negPointStack; - } - if (axis.usePercentage) { - axis.dataMin = 0; - axis.dataMax = 99; - } - - // processData can alter series.pointRange, so this goes after - //findPointRange = series.pointRange === null; - - xData = series.processedXData; - yData = series.processedYData; - yDataLength = yData.length; - - // loop over the non-null y values and read them into a local array - for (i = 0; i < yDataLength; i++) { - x = xData[i]; - y = yData[i]; - - // Read stacked values into a stack based on the x value, - // the sign of y and the stack key. Stacking is also handled for null values (#739) - if (stacking) { - isNegative = y < threshold; - pointStack = isNegative ? negPointStack : posPointStack; - key = isNegative ? negKey : stackKey; - - y = pointStack[x] = - defined(pointStack[x]) ? - correctFloat(pointStack[x] + y) : - y; - - - // add the series - if (!stacks[key]) { - stacks[key] = {}; - } - - // If the StackItem is there, just update the values, - // if not, create one first - if (!stacks[key][x]) { - stacks[key][x] = new StackItem(axis, axis.options.stackLabels, isNegative, x, stackOption, stacking); - } - stacks[key][x].setTotal(y); - } - - // Handle non null values - if (y !== null && y !== UNDEFINED) { - - // general hook, used for Highstock compare values feature - if (hasModifyValue) { - y = series.modifyValue(y); - } - - // for points within the visible range, including the first point outside the - // visible range, consider y extremes - if (cropped || ((xData[i + 1] || x) >= xExtremes.min && (xData[i - 1] || x) <= xExtremes.max)) { - - j = y.length; - if (j) { // array, like ohlc or range data - while (j--) { - if (y[j] !== null) { - activeYData[activeCounter++] = y[j]; - } - } - } else { - activeYData[activeCounter++] = y; - } - } - } - } - - // record the least unit distance - /*if (findPointRange) { - series.pointRange = pointRange || 1; - } - series.closestPointRange = pointRange;*/ - - // Get the dataMin and dataMax so far. If percentage is used, the min and max are - // always 0 and 100. If the length of activeYData is 0, continue with null values. - if (!axis.usePercentage && activeYData.length) { - axis.dataMin = mathMin(pick(axis.dataMin, activeYData[0]), arrayMin(activeYData)); - axis.dataMax = mathMax(pick(axis.dataMax, activeYData[0]), arrayMax(activeYData)); - } - - // Adjust to threshold - if (defined(threshold)) { - if (axis.dataMin >= threshold) { - axis.dataMin = threshold; - axis.ignoreMinPadding = true; - } else if (axis.dataMax < threshold) { - axis.dataMax = threshold; - axis.ignoreMaxPadding = true; - } - } - } - } - }); - - }, - - /** - * Translate from axis value to pixel position on the chart, or back - * - */ - translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacementBetween) { - var axis = this, - axisLength = axis.len, - sign = 1, - cvsOffset = 0, - localA = old ? axis.oldTransA : axis.transA, - localMin = old ? axis.oldMin : axis.min, - returnValue, - postTranslate = axis.options.ordinal || (axis.isLog && handleLog); - - if (!localA) { - localA = axis.transA; - } - - if (cvsCoord) { - sign *= -1; // canvas coordinates inverts the value - cvsOffset = axisLength; - } - if (axis.reversed) { // reversed axis - sign *= -1; - cvsOffset -= sign * axisLength; - } - - if (backwards) { // reverse translation - if (axis.reversed) { - val = axisLength - val; - } - returnValue = val / localA + localMin; // from chart pixel to value - if (postTranslate) { // log and ordinal axes - returnValue = axis.lin2val(returnValue); - } - - } else { // normal translation, from axis value to pixel, relative to plot - if (postTranslate) { // log and ordinal axes - val = axis.val2lin(val); - } - - returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * axis.minPixelPadding) + - (pointPlacementBetween ? localA * axis.pointRange / 2 : 0); - } - - return returnValue; - }, - - /** - * Create the path for a plot line that goes from the given value on - * this axis, across the plot to the opposite side - * @param {Number} value - * @param {Number} lineWidth Used for calculation crisp line - * @param {Number] old Use old coordinates (for resizing and rescaling) - */ - getPlotLinePath: function (value, lineWidth, old) { - var axis = this, - chart = axis.chart, - axisLeft = axis.left, - axisTop = axis.top, - x1, - y1, - x2, - y2, - translatedValue = axis.translate(value, null, null, old), - cHeight = (old && chart.oldChartHeight) || chart.chartHeight, - cWidth = (old && chart.oldChartWidth) || chart.chartWidth, - skip, - transB = axis.transB; - - x1 = x2 = mathRound(translatedValue + transB); - y1 = y2 = mathRound(cHeight - translatedValue - transB); - - if (isNaN(translatedValue)) { // no min or max - skip = true; - - } else if (axis.horiz) { - y1 = axisTop; - y2 = cHeight - axis.bottom; - if (x1 < axisLeft || x1 > axisLeft + axis.width) { - skip = true; - } - } else { - x1 = axisLeft; - x2 = cWidth - axis.right; - - if (y1 < axisTop || y1 > axisTop + axis.height) { - skip = true; - } - } - return skip ? - null : - chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0); - }, - - /** - * Create the path for a plot band - */ - getPlotBandPath: function (from, to) { - - var toPath = this.getPlotLinePath(to), - path = this.getPlotLinePath(from); - - if (path && toPath) { - path.push( - toPath[4], - toPath[5], - toPath[1], - toPath[2] - ); - } else { // outside the axis area - path = null; - } - - return path; - }, - - /** - * Set the tick positions of a linear axis to round values like whole tens or every five. - */ - getLinearTickPositions: function (tickInterval, min, max) { - var pos, - lastPos, - roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval), - roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval), - tickPositions = []; - - // Populate the intermediate values - pos = roundedMin; - while (pos <= roundedMax) { - - // Place the tick on the rounded value - tickPositions.push(pos); - - // Always add the raw tickInterval, not the corrected one. - pos = correctFloat(pos + tickInterval); - - // If the interval is not big enough in the current min - max range to actually increase - // the loop variable, we need to break out to prevent endless loop. Issue #619 - if (pos === lastPos) { - break; - } - - // Record the last value - lastPos = pos; - } - return tickPositions; - }, - - /** - * Set the tick positions of a logarithmic axis - */ - getLogTickPositions: function (interval, min, max, minor) { - var axis = this, - options = axis.options, - axisLength = axis.len; - - // Since we use this method for both major and minor ticks, - // use a local variable and return the result - var positions = []; - - // Reset - if (!minor) { - axis._minorAutoInterval = null; - } - - // First case: All ticks fall on whole logarithms: 1, 10, 100 etc. - if (interval >= 0.5) { - interval = mathRound(interval); - positions = axis.getLinearTickPositions(interval, min, max); - - // Second case: We need intermediary ticks. For example - // 1, 2, 4, 6, 8, 10, 20, 40 etc. - } else if (interval >= 0.08) { - var roundedMin = mathFloor(min), - intermediate, - i, - j, - len, - pos, - lastPos, - break2; - - if (interval > 0.3) { - intermediate = [1, 2, 4]; - } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc - intermediate = [1, 2, 4, 6, 8]; - } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc - intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - } - - for (i = roundedMin; i < max + 1 && !break2; i++) { - len = intermediate.length; - for (j = 0; j < len && !break2; j++) { - pos = log2lin(lin2log(i) * intermediate[j]); - - if (pos > min) { - positions.push(lastPos); - } - - if (lastPos > max) { - break2 = true; - } - lastPos = pos; - } - } - - // Third case: We are so deep in between whole logarithmic values that - // we might as well handle the tick positions like a linear axis. For - // example 1.01, 1.02, 1.03, 1.04. - } else { - var realMin = lin2log(min), - realMax = lin2log(max), - tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'], - filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption, - tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), - totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength; - - interval = pick( - filteredTickIntervalOption, - axis._minorAutoInterval, - (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1) - ); - - interval = normalizeTickInterval( - interval, - null, - math.pow(10, mathFloor(math.log(interval) / math.LN10)) - ); - - positions = map(axis.getLinearTickPositions( - interval, - realMin, - realMax - ), log2lin); - - if (!minor) { - axis._minorAutoInterval = interval / 5; - } - } - - // Set the axis-level tickInterval variable - if (!minor) { - axis.tickInterval = interval; - } - return positions; - }, - - /** - * Return the minor tick positions. For logarithmic axes, reuse the same logic - * as for major ticks. - */ - getMinorTickPositions: function () { - var axis = this, - tickPositions = axis.tickPositions, - minorTickInterval = axis.minorTickInterval; - - var minorTickPositions = [], - pos, - i, - len; - - if (axis.isLog) { - len = tickPositions.length; - for (i = 1; i < len; i++) { - minorTickPositions = minorTickPositions.concat( - axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true) - ); - } - - } else { - for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) { - minorTickPositions.push(pos); - } - } - - return minorTickPositions; - }, - - /** - * Adjust the min and max for the minimum range. Keep in mind that the series data is - * not yet processed, so we don't have information on data cropping and grouping, or - * updated axis.pointRange or series.pointRange. The data can't be processed until - * we have finally established min and max. - */ - adjustForMinRange: function () { - var axis = this, - options = axis.options, - min = axis.min, - max = axis.max, - zoomOffset, - spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange, - closestDataRange, - i, - distance, - xData, - loopLength, - minArgs, - maxArgs; - - // Set the automatic minimum range based on the closest point distance - if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) { - - if (defined(options.min) || defined(options.max)) { - axis.minRange = null; // don't do this again - - } else { - - // Find the closest distance between raw data points, as opposed to - // closestPointRange that applies to processed points (cropped and grouped) - each(axis.series, function (series) { - xData = series.xData; - loopLength = series.xIncrement ? 1 : xData.length - 1; - for (i = loopLength; i > 0; i--) { - distance = xData[i] - xData[i - 1]; - if (closestDataRange === UNDEFINED || distance < closestDataRange) { - closestDataRange = distance; - } - } - }); - axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin); - } - } - - // if minRange is exceeded, adjust - if (max - min < axis.minRange) { - var minRange = axis.minRange; - zoomOffset = (minRange - max + min) / 2; - - // if min and max options have been set, don't go beyond it - minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)]; - if (spaceAvailable) { // if space is available, stay within the data range - minArgs[2] = axis.dataMin; - } - min = arrayMax(minArgs); - - maxArgs = [min + minRange, pick(options.max, min + minRange)]; - if (spaceAvailable) { // if space is availabe, stay within the data range - maxArgs[2] = axis.dataMax; - } - - max = arrayMin(maxArgs); - - // now if the max is adjusted, adjust the min back - if (max - min < minRange) { - minArgs[0] = max - minRange; - minArgs[1] = pick(options.min, max - minRange); - min = arrayMax(minArgs); - } - } - - // Record modified extremes - axis.min = min; - axis.max = max; - }, - - /** - * Update translation information - */ - setAxisTranslation: function () { - var axis = this, - range = axis.max - axis.min, - pointRange = 0, - closestPointRange, - minPointOffset = 0, - pointRangePadding = 0, - linkedParent = axis.linkedParent, - transA = axis.transA; - - // adjust translation for padding - if (axis.isXAxis) { - if (linkedParent) { - minPointOffset = linkedParent.minPointOffset; - pointRangePadding = linkedParent.pointRangePadding; - - } else { - each(axis.series, function (series) { - var seriesPointRange = series.pointRange, - pointPlacement = series.options.pointPlacement, - seriesClosestPointRange = series.closestPointRange; - - pointRange = mathMax(pointRange, seriesPointRange); - - // minPointOffset is the value padding to the left of the axis in order to make - // room for points with a pointRange, typically columns. When the pointPlacement option - // is 'between' or 'on', this padding does not apply. - minPointOffset = mathMax( - minPointOffset, - pointPlacement ? 0 : seriesPointRange / 2 - ); - - // Determine the total padding needed to the length of the axis to make room for the - // pointRange. If the series' pointPlacement is 'on', no padding is added. - pointRangePadding = mathMax( - pointRangePadding, - pointPlacement === 'on' ? 0 : seriesPointRange - ); - - // Set the closestPointRange - if (!series.noSharedTooltip && defined(seriesClosestPointRange)) { - closestPointRange = defined(closestPointRange) ? - mathMin(closestPointRange, seriesClosestPointRange) : - seriesClosestPointRange; - } - }); - } - - // Record minPointOffset and pointRangePadding - axis.minPointOffset = minPointOffset; - axis.pointRangePadding = pointRangePadding; - - // pointRange means the width reserved for each point, like in a column chart - axis.pointRange = pointRange; - - // closestPointRange means the closest distance between points. In columns - // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange - // is some other value - axis.closestPointRange = closestPointRange; - } - - // secondary values - axis.oldTransA = transA; - //axis.translationSlope = axis.transA = transA = axis.len / ((range + (2 * minPointOffset)) || 1); - axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1); - axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend - axis.minPixelPadding = transA * minPointOffset; - }, - - /** - * Set the tick positions to round values and optionally extend the extremes - * to the nearest tick - */ - setTickPositions: function (secondPass) { - var axis = this, - chart = axis.chart, - options = axis.options, - isLog = axis.isLog, - isDatetimeAxis = axis.isDatetimeAxis, - isXAxis = axis.isXAxis, - isLinked = axis.isLinked, - tickPositioner = axis.options.tickPositioner, - magnitude, - maxPadding = options.maxPadding, - minPadding = options.minPadding, - length, - linkedParentExtremes, - tickIntervalOption = options.tickInterval, - minTickIntervalOption = options.minTickInterval, - tickPixelIntervalOption = options.tickPixelInterval, - tickPositions, - categories = axis.categories; - - // linked axis gets the extremes from the parent axis - if (isLinked) { - axis.linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo]; - linkedParentExtremes = axis.linkedParent.getExtremes(); - axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin); - axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax); - if (options.type !== axis.linkedParent.options.type) { - error(11, 1); // Can't link axes of different type - } - } else { // initial min and max from the extreme data values - axis.min = pick(axis.userMin, options.min, axis.dataMin); - axis.max = pick(axis.userMax, options.max, axis.dataMax); - } - - if (isLog) { - if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978 - error(10, 1); // Can't plot negative values on log axis - } - axis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934 - axis.max = correctFloat(log2lin(axis.max)); - } - - // handle zoomed range - if (axis.range) { - axis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618 - axis.userMax = axis.max; - if (secondPass) { - axis.range = null; // don't use it when running setExtremes - } - } - - // adjust min and max for the minimum range - axis.adjustForMinRange(); - - // pad the values to get clear of the chart's edges - if (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) { - length = (axis.max - axis.min) || 1; - if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) { - axis.min -= length * minPadding; - } - if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) { - axis.max += length * maxPadding; - } - } - - // get tickInterval - if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) { - axis.tickInterval = 1; - } else if (isLinked && !tickIntervalOption && - tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) { - axis.tickInterval = axis.linkedParent.tickInterval; - } else { - axis.tickInterval = pick( - tickIntervalOption, - categories ? // for categoried axis, 1 is default, for linear axis use tickPix - 1 : - (axis.max - axis.min) * tickPixelIntervalOption / (axis.len || 1) - ); - } - - // Now we're finished detecting min and max, crop and group series data. This - // is in turn needed in order to find tick positions in ordinal axes. - if (isXAxis && !secondPass) { - each(axis.series, function (series) { - series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax); - }); - } - - // set the translation factor used in translate function - axis.setAxisTranslation(secondPass); - - // hook for ordinal axes and radial axes - if (axis.beforeSetTickPositions) { - axis.beforeSetTickPositions(); - } - - // hook for extensions, used in Highstock ordinal axes - if (axis.postProcessTickInterval) { - axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval); - } - - // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined. - if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) { - axis.tickInterval = minTickIntervalOption; - } - - // for linear axes, get magnitude and normalize the interval - if (!isDatetimeAxis && !isLog) { // linear - magnitude = math.pow(10, mathFloor(math.log(axis.tickInterval) / math.LN10)); - if (!tickIntervalOption) { - axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, magnitude, options); - } - } - - // get minorTickInterval - axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ? - axis.tickInterval / 5 : options.minorTickInterval; - - // find the tick positions - axis.tickPositions = tickPositions = options.tickPositions || (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max])); - if (!tickPositions) { - if (isDatetimeAxis) { - tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)( - normalizeTimeTickInterval(axis.tickInterval, options.units), - axis.min, - axis.max, - options.startOfWeek, - axis.ordinalPositions, - axis.closestPointRange, - true - ); - } else if (isLog) { - tickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max); - } else { - tickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max); - } - axis.tickPositions = tickPositions; - } - - if (!isLinked) { - - // reset min/max or remove extremes based on start/end on tick - var roundedMin = tickPositions[0], - roundedMax = tickPositions[tickPositions.length - 1], - minPointOffset = axis.minPointOffset || 0; - - if (options.startOnTick) { - axis.min = roundedMin; - } else if (axis.min - minPointOffset > roundedMin) { - tickPositions.shift(); - } - - if (options.endOnTick) { - axis.max = roundedMax; - } else if (axis.max + minPointOffset < roundedMax) { - tickPositions.pop(); - } - - } - }, - - /** - * Set the max ticks of either the x and y axis collection - */ - setMaxTicks: function () { - - var chart = this.chart, - maxTicks = chart.maxTicks, - tickPositions = this.tickPositions, - xOrY = this.xOrY; - - if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation - maxTicks = { - x: 0, - y: 0 - }; - } - - if (!this.isLinked && !this.isDatetimeAxis && tickPositions.length > maxTicks[xOrY] && this.options.alignTicks !== false) { - maxTicks[xOrY] = tickPositions.length; - } - chart.maxTicks = maxTicks; - }, - - /** - * When using multiple axes, adjust the number of ticks to match the highest - * number of ticks in that group - */ - adjustTickAmount: function () { - var axis = this, - chart = axis.chart, - xOrY = axis.xOrY, - tickPositions = axis.tickPositions, - maxTicks = chart.maxTicks; - - if (maxTicks && maxTicks[xOrY] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale - var oldTickAmount = axis.tickAmount, - calculatedTickAmount = tickPositions.length, - tickAmount; - - // set the axis-level tickAmount to use below - axis.tickAmount = tickAmount = maxTicks[xOrY]; - - if (calculatedTickAmount < tickAmount) { - while (tickPositions.length < tickAmount) { - tickPositions.push(correctFloat( - tickPositions[tickPositions.length - 1] + axis.tickInterval - )); - } - axis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1); - axis.max = tickPositions[tickPositions.length - 1]; - - } - if (defined(oldTickAmount) && tickAmount !== oldTickAmount) { - axis.isDirty = true; - } - } - }, - - /** - * Set the scale based on data min and max, user set min and max or options - * - */ - setScale: function () { - var axis = this, - stacks = axis.stacks, - type, - i, - isDirtyData, - isDirtyAxisLength; - - axis.oldMin = axis.min; - axis.oldMax = axis.max; - axis.oldAxisLength = axis.len; - - // set the new axisLength - axis.setAxisSize(); - //axisLength = horiz ? axisWidth : axisHeight; - isDirtyAxisLength = axis.len !== axis.oldAxisLength; - - // is there new data? - each(axis.series, function (series) { - if (series.isDirtyData || series.isDirty || - series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well - isDirtyData = true; - } - }); - - // do we really need to go through all this? - if (isDirtyAxisLength || isDirtyData || axis.isLinked || - axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) { - - // get data extremes if needed - axis.getSeriesExtremes(); - - // get fixed positions based on tickInterval - axis.setTickPositions(); - - // record old values to decide whether a rescale is necessary later on (#540) - axis.oldUserMin = axis.userMin; - axis.oldUserMax = axis.userMax; - - // Mark as dirty if it is not already set to dirty and extremes have changed. #595. - if (!axis.isDirty) { - axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax; - } - } - - - // reset stacks - if (!axis.isXAxis) { - for (type in stacks) { - for (i in stacks[type]) { - stacks[type][i].cum = stacks[type][i].total; - } - } - } - - // Set the maximum tick amount - axis.setMaxTicks(); - }, - - /** - * Set the extremes and optionally redraw - * @param {Number} newMin - * @param {Number} newMax - * @param {Boolean} redraw - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * @param {Object} eventArguments - * - */ - setExtremes: function (newMin, newMax, redraw, animation, eventArguments) { - var axis = this, - chart = axis.chart; - - redraw = pick(redraw, true); // defaults to true - - // Extend the arguments with min and max - eventArguments = extend(eventArguments, { - min: newMin, - max: newMax - }); - - // Fire the event - fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler - - axis.userMin = newMin; - axis.userMax = newMax; - - // Mark for running afterSetExtremes - axis.isDirtyExtremes = true; - - // redraw - if (redraw) { - chart.redraw(animation); - } - }); - }, - - /** - * Overridable method for zooming chart. Pulled out in a separate method to allow overriding - * in stock charts. - */ - zoom: function (newMin, newMax) { - this.setExtremes(newMin, newMax, false, UNDEFINED, { trigger: 'zoom' }); - return true; - }, - - /** - * Update the axis metrics - */ - setAxisSize: function () { - var axis = this, - chart = axis.chart, - options = axis.options; - - var offsetLeft = options.offsetLeft || 0, - offsetRight = options.offsetRight || 0; - - // basic values - // expose to use in Series object and navigator - axis.left = pick(options.left, chart.plotLeft + offsetLeft); - axis.top = pick(options.top, chart.plotTop); - axis.width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight); - axis.height = pick(options.height, chart.plotHeight); - axis.bottom = chart.chartHeight - axis.height - axis.top; - axis.right = chart.chartWidth - axis.width - axis.left; - axis.len = mathMax(axis.horiz ? axis.width : axis.height, 0); // mathMax fixes #905 - }, - - /** - * Get the actual axis extremes - */ - getExtremes: function () { - var axis = this, - isLog = axis.isLog; - - return { - min: isLog ? correctFloat(lin2log(axis.min)) : axis.min, - max: isLog ? correctFloat(lin2log(axis.max)) : axis.max, - dataMin: axis.dataMin, - dataMax: axis.dataMax, - userMin: axis.userMin, - userMax: axis.userMax - }; - }, - - /** - * Get the zero plane either based on zero or on the min or max value. - * Used in bar and area plots - */ - getThreshold: function (threshold) { - var axis = this, - isLog = axis.isLog; - - var realMin = isLog ? lin2log(axis.min) : axis.min, - realMax = isLog ? lin2log(axis.max) : axis.max; - - if (realMin > threshold || threshold === null) { - threshold = realMin; - } else if (realMax < threshold) { - threshold = realMax; - } - - return axis.translate(threshold, 0, 1, 0, 1); - }, - - /** - * Add a plot band or plot line after render time - * - * @param options {Object} The plotBand or plotLine configuration object - */ - addPlotBandOrLine: function (options) { - var obj = new PlotLineOrBand(this, options).render(); - this.plotLinesAndBands.push(obj); - return obj; - }, - - /** - * Render the tick labels to a preliminary position to get their sizes - */ - getOffset: function () { - var axis = this, - chart = axis.chart, - renderer = chart.renderer, - options = axis.options, - tickPositions = axis.tickPositions, - ticks = axis.ticks, - horiz = axis.horiz, - side = axis.side, - hasData, - showAxis, - titleOffset = 0, - titleOffsetOption, - titleMargin = 0, - axisTitleOptions = options.title, - labelOptions = options.labels, - labelOffset = 0, // reset - axisOffset = chart.axisOffset, - directionFactor = [-1, 1, 1, -1][side], - n; - - - // For reuse in Axis.render - axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions)); - axis.showAxis = showAxis = hasData || pick(options.showEmpty, true); - - - // Create the axisGroup and gridGroup elements on first iteration - if (!axis.axisGroup) { - axis.gridGroup = renderer.g('grid') - .attr({ zIndex: options.gridZIndex || 1 }) - .add(); - axis.axisGroup = renderer.g('axis') - .attr({ zIndex: options.zIndex || 2 }) - .add(); - axis.labelGroup = renderer.g('axis-labels') - .attr({ zIndex: labelOptions.zIndex || 7 }) - .add(); - } - - if (hasData || axis.isLinked) { - each(tickPositions, function (pos) { - if (!ticks[pos]) { - ticks[pos] = new Tick(axis, pos); - } else { - ticks[pos].addLabel(); // update labels depending on tick interval - } - - }); - - each(tickPositions, function (pos) { - // left side must be align: right and right side must have align: left for labels - if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) { - - // get the highest offset - labelOffset = mathMax( - ticks[pos].getLabelSize(), - labelOffset - ); - } - - }); - - if (axis.staggerLines) { - labelOffset += (axis.staggerLines - 1) * 16; - } - - } else { // doesn't have data - for (n in ticks) { - ticks[n].destroy(); - delete ticks[n]; - } - } - - if (axisTitleOptions && axisTitleOptions.text) { - if (!axis.axisTitle) { - axis.axisTitle = renderer.text( - axisTitleOptions.text, - 0, - 0, - axisTitleOptions.useHTML - ) - .attr({ - zIndex: 7, - rotation: axisTitleOptions.rotation || 0, - align: - axisTitleOptions.textAlign || - { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align] - }) - .css(axisTitleOptions.style) - .add(axis.axisGroup); - axis.axisTitle.isNew = true; - } - - if (showAxis) { - titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width']; - titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10); - titleOffsetOption = axisTitleOptions.offset; - } - - // hide or show the title depending on whether showEmpty is set - axis.axisTitle[showAxis ? 'show' : 'hide'](); - } - - // handle automatic or user set offset - axis.offset = directionFactor * pick(options.offset, axisOffset[side]); - - - axis.axisTitleMargin = - pick(titleOffsetOption, - labelOffset + titleMargin + - (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) - ); - - axisOffset[side] = mathMax( - axisOffset[side], - axis.axisTitleMargin + titleOffset + directionFactor * axis.offset - ); - - }, - - /** - * Get the path for the axis line - */ - getLinePath: function (lineWidth) { - var chart = this.chart, - opposite = this.opposite, - offset = this.offset, - horiz = this.horiz, - lineLeft = this.left + (opposite ? this.width : 0) + offset, - lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset; - - this.lineTop = lineTop; // used by flag series - - return chart.renderer.crispLine([ - M, - horiz ? - this.left : - lineLeft, - horiz ? - lineTop : - this.top, - L, - horiz ? - chart.chartWidth - this.right : - lineLeft, - horiz ? - lineTop : - chart.chartHeight - this.bottom - ], lineWidth); - }, - - /** - * Position the title - */ - getTitlePosition: function () { - // compute anchor points for each of the title align options - var horiz = this.horiz, - axisLeft = this.left, - axisTop = this.top, - axisLength = this.len, - axisTitleOptions = this.options.title, - margin = horiz ? axisLeft : axisTop, - opposite = this.opposite, - offset = this.offset, - fontSize = pInt(axisTitleOptions.style.fontSize || 12), - - // the position in the length direction of the axis - alongAxis = { - low: margin + (horiz ? 0 : axisLength), - middle: margin + axisLength / 2, - high: margin + (horiz ? axisLength : 0) - }[axisTitleOptions.align], - - // the position in the perpendicular direction of the axis - offAxis = (horiz ? axisTop + this.height : axisLeft) + - (horiz ? 1 : -1) * // horizontal axis reverses the margin - (opposite ? -1 : 1) * // so does opposite axes - this.axisTitleMargin + - (this.side === 2 ? fontSize : 0); - - return { - x: horiz ? - alongAxis : - offAxis + (opposite ? this.width : 0) + offset + - (axisTitleOptions.x || 0), // x - y: horiz ? - offAxis - (opposite ? this.height : 0) + offset : - alongAxis + (axisTitleOptions.y || 0) // y - }; - }, - - /** - * Render the axis - */ - render: function () { - var axis = this, - chart = axis.chart, - renderer = chart.renderer, - options = axis.options, - isLog = axis.isLog, - isLinked = axis.isLinked, - tickPositions = axis.tickPositions, - axisTitle = axis.axisTitle, - stacks = axis.stacks, - ticks = axis.ticks, - minorTicks = axis.minorTicks, - alternateBands = axis.alternateBands, - stackLabelOptions = options.stackLabels, - alternateGridColor = options.alternateGridColor, - tickmarkOffset = axis.tickmarkOffset, - lineWidth = options.lineWidth, - linePath, - hasRendered = chart.hasRendered, - slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin), - hasData = axis.hasData, - showAxis = axis.showAxis, - from, - to; - - // If the series has data draw the ticks. Else only the line and title - if (hasData || isLinked) { - - // minor ticks - if (axis.minorTickInterval && !axis.categories) { - each(axis.getMinorTickPositions(), function (pos) { - if (!minorTicks[pos]) { - minorTicks[pos] = new Tick(axis, pos, 'minor'); - } - - // render new ticks in old position - if (slideInTicks && minorTicks[pos].isNew) { - minorTicks[pos].render(null, true); - } - - - minorTicks[pos].isActive = true; - minorTicks[pos].render(); - }); - } - - // Major ticks. Pull out the first item and render it last so that - // we can get the position of the neighbour label. #808. - each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) { - - // Reorganize the indices - i = (i === tickPositions.length - 1) ? 0 : i + 1; - - // linked axes need an extra check to find out if - if (!isLinked || (pos >= axis.min && pos <= axis.max)) { - - if (!ticks[pos]) { - ticks[pos] = new Tick(axis, pos); - } - - // render new ticks in old position - if (slideInTicks && ticks[pos].isNew) { - ticks[pos].render(i, true); - } - - ticks[pos].isActive = true; - ticks[pos].render(i); - } - - }); - - // alternate grid color - if (alternateGridColor) { - each(tickPositions, function (pos, i) { - if (i % 2 === 0 && pos < axis.max) { - if (!alternateBands[pos]) { - alternateBands[pos] = new PlotLineOrBand(axis); - } - from = pos + tickmarkOffset; // #949 - to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max; - alternateBands[pos].options = { - from: isLog ? lin2log(from) : from, - to: isLog ? lin2log(to) : to, - color: alternateGridColor - }; - alternateBands[pos].render(); - alternateBands[pos].isActive = true; - } - }); - } - - // custom plot lines and bands - if (!axis._addedPlotLB) { // only first time - each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) { - //plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render()); - axis.addPlotBandOrLine(plotLineOptions); - }); - axis._addedPlotLB = true; - } - - } // end if hasData - - // remove inactive ticks - each([ticks, minorTicks, alternateBands], function (coll) { - var pos; - for (pos in coll) { - if (!coll[pos].isActive) { - coll[pos].destroy(); - delete coll[pos]; - } else { - coll[pos].isActive = false; // reset - } - } - }); - - // Static items. As the axis group is cleared on subsequent calls - // to render, these items are added outside the group. - // axis line - if (lineWidth) { - linePath = axis.getLinePath(lineWidth); - if (!axis.axisLine) { - axis.axisLine = renderer.path(linePath) - .attr({ - stroke: options.lineColor, - 'stroke-width': lineWidth, - zIndex: 7 - }) - .add(axis.axisGroup); - } else { - axis.axisLine.animate({ d: linePath }); - } - - // show or hide the line depending on options.showEmpty - axis.axisLine[showAxis ? 'show' : 'hide'](); - } - - if (axisTitle && showAxis) { - - axisTitle[axisTitle.isNew ? 'attr' : 'animate']( - axis.getTitlePosition() - ); - axisTitle.isNew = false; - } - - // Stacked totals: - if (stackLabelOptions && stackLabelOptions.enabled) { - var stackKey, oneStack, stackCategory, - stackTotalGroup = axis.stackTotalGroup; - - // Create a separate group for the stack total labels - if (!stackTotalGroup) { - axis.stackTotalGroup = stackTotalGroup = - renderer.g('stack-labels') - .attr({ - visibility: VISIBLE, - zIndex: 6 - }) - .add(); - } - - // plotLeft/Top will change when y axis gets wider so we need to translate the - // stackTotalGroup at every render call. See bug #506 and #516 - stackTotalGroup.translate(chart.plotLeft, chart.plotTop); - - // Render each stack total - for (stackKey in stacks) { - oneStack = stacks[stackKey]; - for (stackCategory in oneStack) { - oneStack[stackCategory].render(stackTotalGroup); - } - } - } - // End stacked totals - - axis.isDirty = false; - }, - - /** - * Remove a plot band or plot line from the chart by id - * @param {Object} id - */ - removePlotBandOrLine: function (id) { - var plotLinesAndBands = this.plotLinesAndBands, - i = plotLinesAndBands.length; - while (i--) { - if (plotLinesAndBands[i].id === id) { - plotLinesAndBands[i].destroy(); - } - } - }, - - /** - * Update the axis title by options - */ - setTitle: function (newTitleOptions, redraw) { - var chart = this.chart, - options = this.options, - axisTitle = this.axisTitle; - - options.title = merge(options.title, newTitleOptions); - - this.axisTitle = axisTitle && axisTitle.destroy(); // #922 - this.isDirty = true; - - if (pick(redraw, true)) { - chart.redraw(); - } - }, - - /** - * Redraw the axis to reflect changes in the data or axis extremes - */ - redraw: function () { - var axis = this, - chart = axis.chart; - - // hide tooltip and hover states - if (chart.tracker.resetTracker) { - chart.tracker.resetTracker(true); - } - - // render the axis - axis.render(); - - // move plot lines and bands - each(axis.plotLinesAndBands, function (plotLine) { - plotLine.render(); - }); - - // mark associated series as dirty and ready for redraw - each(axis.series, function (series) { - series.isDirty = true; - }); - - }, - - /** - * Set new axis categories and optionally redraw - * @param {Array} newCategories - * @param {Boolean} doRedraw - */ - setCategories: function (newCategories, doRedraw) { - var axis = this, - chart = axis.chart; - - // set the categories - axis.categories = axis.userOptions.categories = newCategories; - - // force reindexing tooltips - each(axis.series, function (series) { - series.translate(); - series.setTooltipPoints(true); - }); - - - // optionally redraw - axis.isDirty = true; - - if (pick(doRedraw, true)) { - chart.redraw(); - } - }, - - /** - * Destroys an Axis instance. - */ - destroy: function () { - var axis = this, - stacks = axis.stacks, - stackKey; - - // Remove the events - removeEvent(axis); - - // Destroy each stack total - for (stackKey in stacks) { - destroyObjectProperties(stacks[stackKey]); - - stacks[stackKey] = null; - } - - // Destroy collections - each([axis.ticks, axis.minorTicks, axis.alternateBands, axis.plotLinesAndBands], function (coll) { - destroyObjectProperties(coll); - }); - - // Destroy local variables - each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) { - if (axis[prop]) { - axis[prop] = axis[prop].destroy(); - } - }); - } - - -}; // end Axis - -/** - * The tooltip object - * @param {Object} chart The chart instance - * @param {Object} options Tooltip options - */ -function Tooltip(chart, options) { - var borderWidth = options.borderWidth, - style = options.style, - padding = pInt(style.padding); - - // Save the chart and options - this.chart = chart; - this.options = options; - - // Keep track of the current series - //this.currentSeries = UNDEFINED; - - // List of crosshairs - this.crosshairs = []; - - // Current values of x and y when animating - this.now = { x: 0, y: 0 }; - - // The tooltip is initially hidden - this.isHidden = true; - - // create the label - this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip') - .attr({ - padding: padding, - fill: options.backgroundColor, - 'stroke-width': borderWidth, - r: options.borderRadius, - zIndex: 8 - }) - .css(style) - .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117) - .hide() - .add(); - - // When using canVG the shadow shows up as a gray circle - // even if the tooltip is hidden. - if (!useCanVG) { - this.label.shadow(options.shadow); - } - - // Public property for getting the shared state. - this.shared = options.shared; -} - -Tooltip.prototype = { - /** - * Destroy the tooltip and its elements. - */ - destroy: function () { - each(this.crosshairs, function (crosshair) { - if (crosshair) { - crosshair.destroy(); - } - }); - - // Destroy and clear local variables - if (this.label) { - this.label = this.label.destroy(); - } - }, - - /** - * Provide a soft movement for the tooltip - * - * @param {Number} x - * @param {Number} y - * @private - */ - move: function (x, y, anchorX, anchorY) { - var tooltip = this, - now = tooltip.now, - animate = tooltip.options.animation !== false && !tooltip.isHidden; - - // get intermediate values for animation - extend(now, { - x: animate ? (2 * now.x + x) / 3 : x, - y: animate ? (now.y + y) / 2 : y, - anchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX, - anchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY - }); - - // move to the intermediate value - tooltip.label.attr(now); - - - // run on next tick of the mouse tracker - if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) { - - // never allow two timeouts - clearTimeout(this.tooltipTimeout); - - // set the fixed interval ticking for the smooth tooltip - this.tooltipTimeout = setTimeout(function () { - // The interval function may still be running during destroy, so check that the chart is really there before calling. - if (tooltip) { - tooltip.move(x, y, anchorX, anchorY); - } - }, 32); - - } - }, - - /** - * Hide the tooltip - */ - hide: function () { - if (!this.isHidden) { - var hoverPoints = this.chart.hoverPoints; - - this.label.hide(); - - // hide previous hoverPoints and set new - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - this.chart.hoverPoints = null; - this.isHidden = true; - } - }, - - /** - * Hide the crosshairs - */ - hideCrosshairs: function () { - each(this.crosshairs, function (crosshair) { - if (crosshair) { - crosshair.hide(); - } - }); - }, - - /** - * Extendable method to get the anchor position of the tooltip - * from a point or set of points - */ - getAnchor: function (points, mouseEvent) { - var ret, - chart = this.chart, - inverted = chart.inverted, - plotX = 0, - plotY = 0, - yAxis; - - points = splat(points); - - // Pie uses a special tooltipPos - ret = points[0].tooltipPos; - - // When shared, use the average position - if (!ret) { - each(points, function (point) { - yAxis = point.series.yAxis; - plotX += point.plotX; - plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) + - (!inverted && yAxis ? yAxis.top - chart.plotTop : 0); // #1151 - }); - - plotX /= points.length; - plotY /= points.length; - - ret = [ - inverted ? chart.plotWidth - plotY : plotX, - this.shared && !inverted && points.length > 1 && mouseEvent ? - mouseEvent.chartY - chart.plotTop : // place shared tooltip next to the mouse (#424) - inverted ? chart.plotHeight - plotX : plotY - ]; - } - - return map(ret, mathRound); - }, - - /** - * Place the tooltip in a chart without spilling over - * and not covering the point it self. - */ - getPosition: function (boxWidth, boxHeight, point) { - - // Set up the variables - var chart = this.chart, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop, - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - distance = pick(this.options.distance, 12), - pointX = point.plotX, - pointY = point.plotY, - x = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance), - y = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip - alignedRight; - - // It is too far to the left, adjust it - if (x < 7) { - x = plotLeft + mathMax(pointX, 0) + distance; - } - - // Test to see if the tooltip is too far to the right, - // if it is, move it back to be inside and then up to not cover the point. - if ((x + boxWidth) > (plotLeft + plotWidth)) { - x -= (x + boxWidth) - (plotLeft + plotWidth); - y = pointY - boxHeight + plotTop - distance; - alignedRight = true; - } - - // If it is now above the plot area, align it to the top of the plot area - if (y < plotTop + 5) { - y = plotTop + 5; - - // If the tooltip is still covering the point, move it below instead - if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) { - y = pointY + plotTop + distance; // below - } - } - - // Now if the tooltip is below the chart, move it up. It's better to cover the - // point than to disappear outside the chart. #834. - if (y + boxHeight > plotTop + plotHeight) { - y = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below - } - - return {x: x, y: y}; - }, - - /** - * Refresh the tooltip's text and position. - * @param {Object} point - */ - refresh: function (point, mouseEvent) { - var tooltip = this, - chart = tooltip.chart, - label = tooltip.label, - options = tooltip.options; - - /** - * In case no user defined formatter is given, this will be used - */ - function defaultFormatter() { - var pThis = this, - items = pThis.points || splat(pThis), - series = items[0].series, - s; - - // build the header - s = [series.tooltipHeaderFormatter(items[0].key)]; - - // build the values - each(items, function (item) { - series = item.series; - s.push((series.tooltipFormatter && series.tooltipFormatter(item)) || - item.point.tooltipFormatter(series.tooltipOptions.pointFormat)); - }); - - // footer - s.push(options.footerFormat || ''); - - return s.join(''); - } - - var x, - y, - show, - anchor, - textConfig = {}, - text, - pointConfig = [], - formatter = options.formatter || defaultFormatter, - hoverPoints = chart.hoverPoints, - placedTooltipPoint, - borderColor, - crosshairsOptions = options.crosshairs, - shared = tooltip.shared, - currentSeries; - - // get the reference point coordinates (pie charts use tooltipPos) - anchor = tooltip.getAnchor(point, mouseEvent); - x = anchor[0]; - y = anchor[1]; - - // shared tooltip, array is sent over - if (shared && !(point.series && point.series.noSharedTooltip)) { - - // hide previous hoverPoints and set new - - chart.hoverPoints = point; - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - each(point, function (item) { - item.setState(HOVER_STATE); - - pointConfig.push(item.getLabelConfig()); - }); - - textConfig = { - x: point[0].category, - y: point[0].y - }; - textConfig.points = pointConfig; - point = point[0]; - - // single point tooltip - } else { - textConfig = point.getLabelConfig(); - } - text = formatter.call(textConfig); - - // register the current series - currentSeries = point.series; - - - // For line type series, hide tooltip if the point falls outside the plot - show = shared || !currentSeries.isCartesian || currentSeries.tooltipOutsidePlot || chart.isInsidePlot(x, y); - - // update the inner HTML - if (text === false || !show) { - this.hide(); - } else { - - // show it - if (tooltip.isHidden) { - label.show(); - } - - // update text - label.attr({ - text: text - }); - - // set the stroke color of the box - borderColor = options.borderColor || point.color || currentSeries.color || '#606060'; - label.attr({ - stroke: borderColor - }); - - placedTooltipPoint = (options.positioner || tooltip.getPosition).call( - tooltip, - label.width, - label.height, - { plotX: x, plotY: y } - ); - - // do the move - tooltip.move( - mathRound(placedTooltipPoint.x), - mathRound(placedTooltipPoint.y), - x + chart.plotLeft, - y + chart.plotTop - ); - - - tooltip.isHidden = false; - } - - // crosshairs - if (crosshairsOptions) { - crosshairsOptions = splat(crosshairsOptions); // [x, y] - - var path, - i = crosshairsOptions.length, - attribs, - axis; - - while (i--) { - axis = point.series[i ? 'yAxis' : 'xAxis']; - if (crosshairsOptions[i] && axis) { - - path = axis.getPlotLinePath( - i ? pick(point.stackY, point.y) : point.x, // #814 - 1 - ); - - if (tooltip.crosshairs[i]) { - tooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE }); - } else { - attribs = { - 'stroke-width': crosshairsOptions[i].width || 1, - stroke: crosshairsOptions[i].color || '#C0C0C0', - zIndex: crosshairsOptions[i].zIndex || 2 - }; - if (crosshairsOptions[i].dashStyle) { - attribs.dashstyle = crosshairsOptions[i].dashStyle; - } - tooltip.crosshairs[i] = chart.renderer.path(path) - .attr(attribs) - .add(); - } - } - } - } - fireEvent(chart, 'tooltipRefresh', { - text: text, - x: x + chart.plotLeft, - y: y + chart.plotTop, - borderColor: borderColor - }); - } -}; -/** - * The mouse tracker object - * @param {Object} chart The Chart instance - * @param {Object} options The root options object - */ -function MouseTracker(chart, options) { - var zoomType = useCanVG ? '' : options.chart.zoomType; - - // Zoom status - this.zoomX = /x/.test(zoomType); - this.zoomY = /y/.test(zoomType); - - // Store reference to options - this.options = options; - - // Reference to the chart - this.chart = chart; - - // The interval id - //this.tooltipTimeout = UNDEFINED; - - // The cached x hover position - //this.hoverX = UNDEFINED; - - // The chart position - //this.chartPosition = UNDEFINED; - - // The selection marker element - //this.selectionMarker = UNDEFINED; - - // False or a value > 0 if a dragging operation - //this.mouseDownX = UNDEFINED; - //this.mouseDownY = UNDEFINED; - this.init(chart, options.tooltip); -} - -MouseTracker.prototype = { - /** - * Add crossbrowser support for chartX and chartY - * @param {Object} e The event object in standard browsers - */ - normalizeMouseEvent: function (e) { - var chartPosition, - chartX, - chartY, - ePos; - - // common IE normalizing - e = e || win.event; - if (!e.target) { - e.target = e.srcElement; - } - - // Framework specific normalizing (#1165) - e = washMouseEvent(e); - - // iOS - ePos = e.touches ? e.touches.item(0) : e; - - // get mouse position - this.chartPosition = chartPosition = offset(this.chart.container); - - // chartX and chartY - if (ePos.pageX === UNDEFINED) { // IE < 9. #886. - chartX = e.x; - chartY = e.y; - } else { - chartX = ePos.pageX - chartPosition.left; - chartY = ePos.pageY - chartPosition.top; - } - - return extend(e, { - chartX: mathRound(chartX), - chartY: mathRound(chartY) - }); - }, - - /** - * Get the click position in terms of axis values. - * - * @param {Object} e A mouse event - */ - getMouseCoordinates: function (e) { - var coordinates = { - xAxis: [], - yAxis: [] - }, - chart = this.chart; - - each(chart.axes, function (axis) { - var isXAxis = axis.isXAxis, - isHorizontal = chart.inverted ? !isXAxis : isXAxis; - - coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({ - axis: axis, - value: axis.translate( - (isHorizontal ? - e.chartX - chart.plotLeft : - axis.top + axis.len - e.chartY) - axis.minPixelPadding, // #1051 - true - ) - }); - }); - return coordinates; - }, - - /** - * Return the index in the tooltipPoints array, corresponding to pixel position in - * the plot area. - */ - getIndex: function (e) { - var chart = this.chart; - return chart.inverted ? - chart.plotHeight + chart.plotTop - e.chartY : - e.chartX - chart.plotLeft; - }, - - /** - * With line type charts with a single tracker, get the point closest to the mouse - */ - onmousemove: function (e) { - var mouseTracker = this, - chart = mouseTracker.chart, - series = chart.series, - tooltip = chart.tooltip, - point, - points, - hoverPoint = chart.hoverPoint, - hoverSeries = chart.hoverSeries, - i, - j, - distance = chart.chartWidth, - index = mouseTracker.getIndex(e); - - // shared tooltip - if (tooltip && mouseTracker.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) { - points = []; - - // loop over all series and find the ones with points closest to the mouse - i = series.length; - for (j = 0; j < i; j++) { - if (series[j].visible && - series[j].options.enableMouseTracking !== false && - !series[j].noSharedTooltip && series[j].tooltipPoints.length) { - point = series[j].tooltipPoints[index]; - point._dist = mathAbs(index - point[series[j].xAxis.tooltipPosName || 'plotX']); - distance = mathMin(distance, point._dist); - points.push(point); - } - } - // remove furthest points - i = points.length; - while (i--) { - if (points[i]._dist > distance) { - points.splice(i, 1); - } - } - // refresh the tooltip if necessary - if (points.length && (points[0].plotX !== mouseTracker.hoverX)) { - tooltip.refresh(points, e); - mouseTracker.hoverX = points[0].plotX; - } - } - - // separate tooltip and general mouse events - if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker - - // get the point - point = hoverSeries.tooltipPoints[index]; - - // a new point is hovered, refresh the tooltip - if (point && point !== hoverPoint) { - - // trigger the events - point.onMouseOver(); - - } - } - }, - - - - /** - * Reset the tracking by hiding the tooltip, the hover series state and the hover point - * - * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible - */ - resetTracker: function (allowMove) { - var mouseTracker = this, - chart = mouseTracker.chart, - hoverSeries = chart.hoverSeries, - hoverPoint = chart.hoverPoint, - tooltip = chart.tooltip, - tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint; - - // Narrow in allowMove - allowMove = allowMove && tooltip && tooltipPoints; - - // Check if the points have moved outside the plot area, #1003 - if (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) { - allowMove = false; - } - - // Just move the tooltip, #349 - if (allowMove) { - tooltip.refresh(tooltipPoints); - - // Full reset - } else { - - if (hoverPoint) { - hoverPoint.onMouseOut(); - } - - if (hoverSeries) { - hoverSeries.onMouseOut(); - } - - if (tooltip) { - tooltip.hide(); - tooltip.hideCrosshairs(); - } - - mouseTracker.hoverX = null; - - } - }, - - /** - * Set the JS events on the container element - */ - setDOMEvents: function () { - var lastWasOutsidePlot = true, - mouseTracker = this, - chart = mouseTracker.chart, - container = chart.container, - hasDragged, - zoomHor = (mouseTracker.zoomX && !chart.inverted) || (mouseTracker.zoomY && chart.inverted), - zoomVert = (mouseTracker.zoomY && !chart.inverted) || (mouseTracker.zoomX && chart.inverted); - - /** - * Mouse up or outside the plot area - */ - function drop() { - if (mouseTracker.selectionMarker) { - var selectionData = { - xAxis: [], - yAxis: [] - }, - selectionBox = mouseTracker.selectionMarker.getBBox(), - selectionLeft = selectionBox.x - chart.plotLeft, - selectionTop = selectionBox.y - chart.plotTop, - runZoom; - - // a selection has been made - if (hasDragged) { - - // record each axis' min and max - each(chart.axes, function (axis) { - if (axis.options.zoomEnabled !== false) { - var isXAxis = axis.isXAxis, - isHorizontal = chart.inverted ? !isXAxis : isXAxis, - selectionMin = axis.translate( - isHorizontal ? - selectionLeft : - chart.plotHeight - selectionTop - selectionBox.height, - true, - 0, - 0, - 1 - ), - selectionMax = axis.translate( - (isHorizontal ? - selectionLeft + selectionBox.width : - chart.plotHeight - selectionTop) - - 2 * axis.minPixelPadding, // #875 - true, - 0, - 0, - 1 - ); - - if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859 - selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({ - axis: axis, - min: mathMin(selectionMin, selectionMax), // for reversed axes, - max: mathMax(selectionMin, selectionMax) - }); - runZoom = true; - } - } - }); - if (runZoom) { - fireEvent(chart, 'selection', selectionData, function (args) { chart.zoom(args); }); - } - - } - mouseTracker.selectionMarker = mouseTracker.selectionMarker.destroy(); - } - - if (chart) { // it may be destroyed on mouse up - #877 - css(container, { cursor: 'auto' }); - chart.cancelClick = hasDragged; // #370 - chart.mouseIsDown = hasDragged = false; - } - - removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop); - } - - /** - * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea. - */ - mouseTracker.hideTooltipOnMouseMove = function (e) { - - // Get e.pageX and e.pageY back in MooTools - e = washMouseEvent(e); - - // If we're outside, hide the tooltip - if (mouseTracker.chartPosition && chart.hoverSeries && chart.hoverSeries.isCartesian && - !chart.isInsidePlot(e.pageX - mouseTracker.chartPosition.left - chart.plotLeft, - e.pageY - mouseTracker.chartPosition.top - chart.plotTop)) { - mouseTracker.resetTracker(); - } - }; - - /** - * When mouse leaves the container, hide the tooltip. - */ - mouseTracker.hideTooltipOnMouseLeave = function () { - mouseTracker.resetTracker(); - mouseTracker.chartPosition = null; // also reset the chart position, used in #149 fix - }; - - - /* - * Record the starting position of a dragoperation - */ - container.onmousedown = function (e) { - e = mouseTracker.normalizeMouseEvent(e); - - // issue #295, dragging not always working in Firefox - if (!hasTouch && e.preventDefault) { - e.preventDefault(); - } - - // record the start position - chart.mouseIsDown = true; - chart.cancelClick = false; - chart.mouseDownX = mouseTracker.mouseDownX = e.chartX; - mouseTracker.mouseDownY = e.chartY; - - addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop); - }; - - // The mousemove, touchmove and touchstart event handler - var mouseMove = function (e) { - // let the system handle multitouch operations like two finger scroll - // and pinching - if (e && e.touches && e.touches.length > 1) { - return; - } - - // normalize - e = mouseTracker.normalizeMouseEvent(e); - if (!hasTouch) { // not for touch devices - e.returnValue = false; - } - - var chartX = e.chartX, - chartY = e.chartY, - isOutsidePlot = !chart.isInsidePlot(chartX - chart.plotLeft, chartY - chart.plotTop); - - // on touch devices, only trigger click if a handler is defined - if (hasTouch && e.type === 'touchstart') { - if (attr(e.target, 'isTracker')) { - if (!chart.runTrackerClick) { - e.preventDefault(); - } - } else if (!chart.runChartClick && !isOutsidePlot) { - e.preventDefault(); - } - } - - // cancel on mouse outside - if (isOutsidePlot) { - - /*if (!lastWasOutsidePlot) { - // reset the tracker - resetTracker(); - }*/ - - // drop the selection if any and reset mouseIsDown and hasDragged - //drop(); - if (chartX < chart.plotLeft) { - chartX = chart.plotLeft; - } else if (chartX > chart.plotLeft + chart.plotWidth) { - chartX = chart.plotLeft + chart.plotWidth; - } - - if (chartY < chart.plotTop) { - chartY = chart.plotTop; - } else if (chartY > chart.plotTop + chart.plotHeight) { - chartY = chart.plotTop + chart.plotHeight; - } - } - - if (chart.mouseIsDown && e.type !== 'touchstart') { // make selection - - // determine if the mouse has moved more than 10px - hasDragged = Math.sqrt( - Math.pow(mouseTracker.mouseDownX - chartX, 2) + - Math.pow(mouseTracker.mouseDownY - chartY, 2) - ); - if (hasDragged > 10) { - var clickedInside = chart.isInsidePlot(mouseTracker.mouseDownX - chart.plotLeft, mouseTracker.mouseDownY - chart.plotTop); - - // make a selection - if (chart.hasCartesianSeries && (mouseTracker.zoomX || mouseTracker.zoomY) && clickedInside) { - if (!mouseTracker.selectionMarker) { - mouseTracker.selectionMarker = chart.renderer.rect( - chart.plotLeft, - chart.plotTop, - zoomHor ? 1 : chart.plotWidth, - zoomVert ? 1 : chart.plotHeight, - 0 - ) - .attr({ - fill: mouseTracker.options.chart.selectionMarkerFill || 'rgba(69,114,167,0.25)', - zIndex: 7 - }) - .add(); - } - } - - // adjust the width of the selection marker - if (mouseTracker.selectionMarker && zoomHor) { - var xSize = chartX - mouseTracker.mouseDownX; - mouseTracker.selectionMarker.attr({ - width: mathAbs(xSize), - x: (xSize > 0 ? 0 : xSize) + mouseTracker.mouseDownX - }); - } - // adjust the height of the selection marker - if (mouseTracker.selectionMarker && zoomVert) { - var ySize = chartY - mouseTracker.mouseDownY; - mouseTracker.selectionMarker.attr({ - height: mathAbs(ySize), - y: (ySize > 0 ? 0 : ySize) + mouseTracker.mouseDownY - }); - } - - // panning - if (clickedInside && !mouseTracker.selectionMarker && mouseTracker.options.chart.panning) { - chart.pan(chartX); - } - } - - } - - // Show the tooltip and run mouse over events (#977) - if (!isOutsidePlot) { - mouseTracker.onmousemove(e); - } - - lastWasOutsidePlot = isOutsidePlot; - - // when outside plot, allow touch-drag by returning true - return isOutsidePlot || !chart.hasCartesianSeries; - }; - - /* - * When the mouse enters the container, run mouseMove - */ - container.onmousemove = mouseMove; - - /* - * When the mouse leaves the container, hide the tracking (tooltip). - */ - addEvent(container, 'mouseleave', mouseTracker.hideTooltipOnMouseLeave); - - // issue #149 workaround - // The mouseleave event above does not always fire. Whenever the mouse is moving - // outside the plotarea, hide the tooltip - addEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove); - - container.ontouchstart = function (e) { - // For touch devices, use touchmove to zoom - if (mouseTracker.zoomX || mouseTracker.zoomY) { - container.onmousedown(e); - } - // Show tooltip and prevent the lower mouse pseudo event - mouseMove(e); - }; - - /* - * Allow dragging the finger over the chart to read the values on touch - * devices - */ - container.ontouchmove = mouseMove; - - /* - * Allow dragging the finger over the chart to read the values on touch - * devices - */ - container.ontouchend = function () { - if (hasDragged) { - mouseTracker.resetTracker(); - } - }; - - - // MooTools 1.2.3 doesn't fire this in IE when using addEvent - container.onclick = function (e) { - var hoverPoint = chart.hoverPoint, - plotX, - plotY; - e = mouseTracker.normalizeMouseEvent(e); - - e.cancelBubble = true; // IE specific - - - if (!chart.cancelClick) { - // Detect clicks on trackers or tracker groups, #783 - if (hoverPoint && (attr(e.target, 'isTracker') || attr(e.target.parentNode, 'isTracker'))) { - plotX = hoverPoint.plotX; - plotY = hoverPoint.plotY; - - // add page position info - extend(hoverPoint, { - pageX: mouseTracker.chartPosition.left + chart.plotLeft + - (chart.inverted ? chart.plotWidth - plotY : plotX), - pageY: mouseTracker.chartPosition.top + chart.plotTop + - (chart.inverted ? chart.plotHeight - plotX : plotY) - }); - - // the series click event - fireEvent(hoverPoint.series, 'click', extend(e, { - point: hoverPoint - })); - - // the point click event - hoverPoint.firePointEvent('click', e); - - } else { - extend(e, mouseTracker.getMouseCoordinates(e)); - - // fire a click event in the chart - if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { - fireEvent(chart, 'click', e); - } - } - - - } - }; - - }, - - /** - * Destroys the MouseTracker object and disconnects DOM events. - */ - destroy: function () { - var mouseTracker = this, - chart = mouseTracker.chart, - container = chart.container; - - // Destroy the tracker group element - if (chart.trackerGroup) { - chart.trackerGroup = chart.trackerGroup.destroy(); - } - - removeEvent(container, 'mouseleave', mouseTracker.hideTooltipOnMouseLeave); - removeEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove); - container.onclick = container.onmousedown = container.onmousemove = container.ontouchstart = container.ontouchend = container.ontouchmove = null; - - // memory and CPU leak - clearInterval(this.tooltipTimeout); - }, - - // Run MouseTracker - init: function (chart, options) { - if (!chart.trackerGroup) { - chart.trackerGroup = chart.renderer.g('tracker') - .attr({ zIndex: 9 }) - .add(); - } - - if (options.enabled) { - chart.tooltip = new Tooltip(chart, options); - } - - this.setDOMEvents(); - } -}; -/** - * The overview of the chart's series - */ -function Legend(chart) { - - this.init(chart); -} - -Legend.prototype = { - - /** - * Initialize the legend - */ - init: function (chart) { - var legend = this, - options = legend.options = chart.options.legend; - - if (!options.enabled) { - return; - } - - var //style = options.style || {}, // deprecated - itemStyle = options.itemStyle, - padding = pick(options.padding, 8), - itemMarginTop = options.itemMarginTop || 0; - - legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype - legend.itemStyle = itemStyle; - legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle); - legend.itemMarginTop = itemMarginTop; - legend.padding = padding; - legend.initialItemX = padding; - legend.initialItemY = padding - 5; // 5 is the number of pixels above the text - legend.maxItemWidth = 0; - legend.chart = chart; - //legend.allItems = UNDEFINED; - //legend.legendWidth = UNDEFINED; - //legend.legendHeight = UNDEFINED; - //legend.offsetWidth = UNDEFINED; - legend.itemHeight = 0; - legend.lastLineHeight = 0; - //legend.itemX = UNDEFINED; - //legend.itemY = UNDEFINED; - //legend.lastItemY = UNDEFINED; - - // Elements - //legend.group = UNDEFINED; - //legend.box = UNDEFINED; - - // run legend - legend.render(); - - // move checkboxes - addEvent(legend.chart, 'endResize', function () { legend.positionCheckboxes(); }); - -/* // expose - return { - colorizeItem: colorizeItem, - destroyItem: destroyItem, - render: render, - destroy: destroy, - getLegendWidth: getLegendWidth, - getLegendHeight: getLegendHeight - };*/ - }, - - /** - * Set the colors for the legend item - * @param {Object} item A Series or Point instance - * @param {Object} visible Dimmed or colored - */ - colorizeItem: function (item, visible) { - var legend = this, - options = legend.options, - legendItem = item.legendItem, - legendLine = item.legendLine, - legendSymbol = item.legendSymbol, - hiddenColor = legend.itemHiddenStyle.color, - textColor = visible ? options.itemStyle.color : hiddenColor, - symbolColor = visible ? item.color : hiddenColor, - markerOptions = item.options && item.options.marker, - symbolAttr = { - stroke: symbolColor, - fill: symbolColor - }, - key, - val; - - - if (legendItem) { - legendItem.css({ fill: textColor }); - } - if (legendLine) { - legendLine.attr({ stroke: symbolColor }); - } - - if (legendSymbol) { - - // Apply marker options - if (markerOptions) { - markerOptions = item.convertAttribs(markerOptions); - for (key in markerOptions) { - val = markerOptions[key]; - if (val !== UNDEFINED) { - symbolAttr[key] = val; - } - } - } - - legendSymbol.attr(symbolAttr); - } - }, - - /** - * Position the legend item - * @param {Object} item A Series or Point instance - */ - positionItem: function (item) { - var legend = this, - options = legend.options, - symbolPadding = options.symbolPadding, - ltr = !options.rtl, - legendItemPos = item._legendItemPos, - itemX = legendItemPos[0], - itemY = legendItemPos[1], - checkbox = item.checkbox; - - if (item.legendGroup) { - item.legendGroup.translate( - ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4, - itemY - ); - } - - if (checkbox) { - checkbox.x = itemX; - checkbox.y = itemY; - } - }, - - /** - * Destroy a single legend item - * @param {Object} item The series or point - */ - destroyItem: function (item) { - var checkbox = item.checkbox; - - // destroy SVG elements - each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) { - if (item[key]) { - item[key].destroy(); - } - }); - - if (checkbox) { - discardElement(item.checkbox); - } - }, - - /** - * Destroys the legend. - */ - destroy: function () { - var legend = this, - legendGroup = legend.group, - box = legend.box; - - if (box) { - legend.box = box.destroy(); - } - - if (legendGroup) { - legend.group = legendGroup.destroy(); - } - }, - - /** - * Position the checkboxes after the width is determined - */ - positionCheckboxes: function () { - var legend = this; - - each(legend.allItems, function (item) { - var checkbox = item.checkbox, - alignAttr = legend.group.alignAttr; - if (checkbox) { - css(checkbox, { - left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX, - top: (alignAttr.translateY + checkbox.y + 3) + PX - }); - } - }); - }, - - /** - * Render a single specific legend item - * @param {Object} item A series or point - */ - renderItem: function (item) { - var legend = this, - chart = legend.chart, - renderer = chart.renderer, - options = legend.options, - horizontal = options.layout === 'horizontal', - symbolWidth = options.symbolWidth, - symbolPadding = options.symbolPadding, - itemStyle = legend.itemStyle, - itemHiddenStyle = legend.itemHiddenStyle, - padding = legend.padding, - ltr = !options.rtl, - itemHeight, - widthOption = options.width, - itemMarginBottom = options.itemMarginBottom || 0, - itemMarginTop = legend.itemMarginTop, - initialItemX = legend.initialItemX, - bBox, - itemWidth, - li = item.legendItem, - series = item.series || item, - itemOptions = series.options, - showCheckbox = itemOptions.showCheckbox; - - if (!li) { // generate it once, later move it - - // Generate the group box - // A group to hold the symbol and text. Text is to be appended in Legend class. - item.legendGroup = renderer.g('legend-item') - .attr({ zIndex: 1 }) - .add(legend.scrollGroup); - - // Draw the legend symbol inside the group box - series.drawLegendSymbol(legend, item); - - // Generate the list item text and add it to the group - item.legendItem = li = renderer.text( - options.labelFormatter.call(item), - ltr ? symbolWidth + symbolPadding : -symbolPadding, - legend.baseline, - options.useHTML - ) - .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021) - .attr({ - align: ltr ? 'left' : 'right', - zIndex: 2 - }) - .add(item.legendGroup); - - // Set the events on the item group - item.legendGroup.on('mouseover', function () { - item.setState(HOVER_STATE); - li.css(legend.options.itemHoverStyle); - }) - .on('mouseout', function () { - li.css(item.visible ? itemStyle : itemHiddenStyle); - item.setState(); - }) - .on('click', function (event) { - var strLegendItemClick = 'legendItemClick', - fnLegendItemClick = function () { - item.setVisible(); - }; - - // Pass over the click/touch event. #4. - event = { - browserEvent: event - }; - - // click the name or symbol - if (item.firePointEvent) { // point - item.firePointEvent(strLegendItemClick, event, fnLegendItemClick); - } else { - fireEvent(item, strLegendItemClick, event, fnLegendItemClick); - } - }); - - // Colorize the items - legend.colorizeItem(item, item.visible); - - // add the HTML checkbox on top - if (itemOptions && showCheckbox) { - item.checkbox = createElement('input', { - type: 'checkbox', - checked: item.selected, - defaultChecked: item.selected // required by IE7 - }, options.itemCheckboxStyle, chart.container); - - addEvent(item.checkbox, 'click', function (event) { - var target = event.target; - fireEvent(item, 'checkboxClick', { - checked: target.checked - }, - function () { - item.select(); - } - ); - }); - } - } - - // calculate the positions for the next line - bBox = li.getBBox(); - - itemWidth = item.legendItemWidth = - options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding + - (showCheckbox ? 20 : 0); - legend.itemHeight = itemHeight = bBox.height; - - // if the item exceeds the width, start a new line - if (horizontal && legend.itemX - initialItemX + itemWidth > - (widthOption || (chart.chartWidth - 2 * padding - initialItemX))) { - legend.itemX = initialItemX; - legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom; - legend.lastLineHeight = 0; // reset for next line - } - - // If the item exceeds the height, start a new column - /*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) { - legend.itemY = legend.initialItemY; - legend.itemX += legend.maxItemWidth; - legend.maxItemWidth = 0; - }*/ - - // Set the edge positions - legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth); - legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom; - legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915 - - // cache the position of the newly generated or reordered items - item._legendItemPos = [legend.itemX, legend.itemY]; - - // advance - if (horizontal) { - legend.itemX += itemWidth; - - } else { - legend.itemY += itemMarginTop + itemHeight + itemMarginBottom; - legend.lastLineHeight = itemHeight; - } - - // the width of the widest item - legend.offsetWidth = widthOption || mathMax( - horizontal ? legend.itemX - initialItemX : itemWidth, - legend.offsetWidth - ); - }, - - /** - * Render the legend. This method can be called both before and after - * chart.render. If called after, it will only rearrange items instead - * of creating new ones. - */ - render: function () { - var legend = this, - chart = legend.chart, - renderer = chart.renderer, - legendGroup = legend.group, - allItems, - display, - legendWidth, - legendHeight, - box = legend.box, - options = legend.options, - padding = legend.padding, - legendBorderWidth = options.borderWidth, - legendBackgroundColor = options.backgroundColor; - - legend.itemX = legend.initialItemX; - legend.itemY = legend.initialItemY; - legend.offsetWidth = 0; - legend.lastItemY = 0; - - if (!legendGroup) { - legend.group = legendGroup = renderer.g('legend') - // #414, #759. Trackers will be drawn above the legend, but we have - // to sacrifice that because tooltips need to be above the legend - // and trackers above tooltips - .attr({ zIndex: 7 }) - .add(); - legend.contentGroup = renderer.g() - .attr({ zIndex: 1 }) // above background - .add(legendGroup); - legend.scrollGroup = renderer.g() - .add(legend.contentGroup); - legend.clipRect = renderer.clipRect(0, 0, 9999, chart.chartHeight); - legend.contentGroup.clip(legend.clipRect); - } - - // add each series or point - allItems = []; - each(chart.series, function (serie) { - var seriesOptions = serie.options; - - if (!seriesOptions.showInLegend) { - return; - } - - // use points or series for the legend item depending on legendType - allItems = allItems.concat( - serie.legendItems || - (seriesOptions.legendType === 'point' ? - serie.data : - serie) - ); - }); - - // sort by legendIndex - stableSort(allItems, function (a, b) { - return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0); - }); - - // reversed legend - if (options.reversed) { - allItems.reverse(); - } - - legend.allItems = allItems; - legend.display = display = !!allItems.length; - - // render the items - each(allItems, function (item) { - legend.renderItem(item); - }); - - // Draw the border - legendWidth = options.width || legend.offsetWidth; - legendHeight = legend.lastItemY + legend.lastLineHeight; - - - legendHeight = legend.handleOverflow(legendHeight); - - if (legendBorderWidth || legendBackgroundColor) { - legendWidth += padding; - legendHeight += padding; - - if (!box) { - legend.box = box = renderer.rect( - 0, - 0, - legendWidth, - legendHeight, - options.borderRadius, - legendBorderWidth || 0 - ).attr({ - stroke: options.borderColor, - 'stroke-width': legendBorderWidth || 0, - fill: legendBackgroundColor || NONE - }) - .add(legendGroup) - .shadow(options.shadow); - box.isNew = true; - - } else if (legendWidth > 0 && legendHeight > 0) { - box[box.isNew ? 'attr' : 'animate']( - box.crisp(null, null, null, legendWidth, legendHeight) - ); - box.isNew = false; - } - - // hide the border if no items - box[display ? 'show' : 'hide'](); - } - - legend.legendWidth = legendWidth; - legend.legendHeight = legendHeight; - - // Now that the legend width and height are established, put the items in the - // final position - each(allItems, function (item) { - legend.positionItem(item); - }); - - // 1.x compatibility: positioning based on style - /*var props = ['left', 'right', 'top', 'bottom'], - prop, - i = 4; - while (i--) { - prop = props[i]; - if (options.style[prop] && options.style[prop] !== 'auto') { - options[i < 2 ? 'align' : 'verticalAlign'] = prop; - options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1); - } - }*/ - - if (display) { - legendGroup.align(extend({ - width: legendWidth, - height: legendHeight - }, options), true, chart.spacingBox); - } - - if (!chart.isResizing) { - this.positionCheckboxes(); - } - }, - - /** - * Set up the overflow handling by adding navigation with up and down arrows below the - * legend. - */ - handleOverflow: function (legendHeight) { - var legend = this, - chart = this.chart, - renderer = chart.renderer, - pageCount, - options = this.options, - optionsY = options.y, - alignTop = options.verticalAlign === 'top', - spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding, - maxHeight = options.maxHeight, - clipHeight, - clipRect = this.clipRect, - navOptions = options.navigation, - animation = pick(navOptions.animation, true), - arrowSize = navOptions.arrowSize || 12, - nav = this.nav; - - // Adjust the height - if (options.layout === 'horizontal') { - spaceHeight /= 2; - } - if (maxHeight) { - spaceHeight = mathMin(spaceHeight, maxHeight); - } - - // Reset the legend height and adjust the clipping rectangle - if (legendHeight > spaceHeight) { - - this.clipHeight = clipHeight = spaceHeight - 20; - this.pageCount = pageCount = mathCeil(legendHeight / clipHeight); - this.currentPage = pick(this.currentPage, 1); - this.fullHeight = legendHeight; - - clipRect.attr({ - height: clipHeight - }); - - // Add navigation elements - if (!nav) { - this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group); - this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize) - .on('click', function () { - legend.scroll(-1, animation); - }) - .add(nav); - this.pager = renderer.text('', 15, 10) - .css(navOptions.style) - .add(nav); - this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize) - .on('click', function () { - legend.scroll(1, animation); - }) - .add(nav); - } - - // Set initial position - legend.scroll(0); - - legendHeight = spaceHeight; - - } else if (nav) { - clipRect.attr({ - height: chart.chartHeight - }); - nav.hide(); - this.scrollGroup.attr({ - translateY: 1 - }); - } - - return legendHeight; - }, - - /** - * Scroll the legend by a number of pages - * @param {Object} scrollBy - * @param {Object} animation - */ - scroll: function (scrollBy, animation) { - var pageCount = this.pageCount, - currentPage = this.currentPage + scrollBy, - clipHeight = this.clipHeight, - navOptions = this.options.navigation, - activeColor = navOptions.activeColor, - inactiveColor = navOptions.inactiveColor, - pager = this.pager, - padding = this.padding; - - // When resizing while looking at the last page - if (currentPage > pageCount) { - currentPage = pageCount; - } - - if (currentPage > 0) { - - if (animation !== UNDEFINED) { - setAnimation(animation, this.chart); - } - - this.nav.attr({ - translateX: padding, - translateY: clipHeight + 7, - visibility: VISIBLE - }); - this.up.attr({ - fill: currentPage === 1 ? inactiveColor : activeColor - }) - .css({ - cursor: currentPage === 1 ? 'default' : 'pointer' - }); - pager.attr({ - text: currentPage + '/' + this.pageCount - }); - this.down.attr({ - x: 18 + this.pager.getBBox().width, // adjust to text width - fill: currentPage === pageCount ? inactiveColor : activeColor - }) - .css({ - cursor: currentPage === pageCount ? 'default' : 'pointer' - }); - - this.scrollGroup.animate({ - translateY: -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1 - }); - pager.attr({ - text: currentPage + '/' + pageCount - }); - - - this.currentPage = currentPage; - } - - } - -}; - - -/** - * The chart class - * @param {Object} options - * @param {Function} callback Function to run when the chart has loaded - */ -function Chart(userOptions, callback) { - // Handle regular options - var options, - seriesOptions = userOptions.series; // skip merging data points to increase performance - userOptions.series = null; - options = merge(defaultOptions, userOptions); // do the merge - options.series = userOptions.series = seriesOptions; // set back the series data - - var optionsChart = options.chart, - optionsMargin = optionsChart.margin, - margin = isObject(optionsMargin) ? - optionsMargin : - [optionsMargin, optionsMargin, optionsMargin, optionsMargin]; - - this.optionsMarginTop = pick(optionsChart.marginTop, margin[0]); - this.optionsMarginRight = pick(optionsChart.marginRight, margin[1]); - this.optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]); - this.optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]); - - var chartEvents = optionsChart.events; - - this.runChartClick = chartEvents && !!chartEvents.click; - this.callback = callback; - this.isResizing = 0; - this.options = options; - //chartTitleOptions = UNDEFINED; - //chartSubtitleOptions = UNDEFINED; - - this.axes = []; - this.series = []; - this.hasCartesianSeries = optionsChart.showAxes; - //this.axisOffset = UNDEFINED; - //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes - //this.inverted = UNDEFINED; - //this.loadingShown = UNDEFINED; - //this.container = UNDEFINED; - //this.chartWidth = UNDEFINED; - //this.chartHeight = UNDEFINED; - //this.marginRight = UNDEFINED; - //this.marginBottom = UNDEFINED; - //this.containerWidth = UNDEFINED; - //this.containerHeight = UNDEFINED; - //this.oldChartWidth = UNDEFINED; - //this.oldChartHeight = UNDEFINED; - - //this.renderTo = UNDEFINED; - //this.renderToClone = UNDEFINED; - //this.tracker = UNDEFINED; - - //this.spacingBox = UNDEFINED - - //this.legend = UNDEFINED; - - // Elements - //this.chartBackground = UNDEFINED; - //this.plotBackground = UNDEFINED; - //this.plotBGImage = UNDEFINED; - //this.plotBorder = UNDEFINED; - //this.loadingDiv = UNDEFINED; - //this.loadingSpan = UNDEFINED; - - this.init(chartEvents); -} - -Chart.prototype = { - - /** - * Initialize an individual series, called internally before render time - */ - initSeries: function (options) { - var chart = this, - optionsChart = chart.options.chart, - type = options.type || optionsChart.type || optionsChart.defaultSeriesType, - series = new seriesTypes[type](); - - series.init(this, options); - return series; - }, - - /** - * Add a series dynamically after time - * - * @param {Object} options The config options - * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true. - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * - * @return {Object} series The newly created series object - */ - addSeries: function (options, redraw, animation) { - var series, - chart = this; - - if (options) { - setAnimation(animation, chart); - redraw = pick(redraw, true); // defaults to true - - fireEvent(chart, 'addSeries', { options: options }, function () { - series = chart.initSeries(options); - - chart.isDirtyLegend = true; // the series array is out of sync with the display - if (redraw) { - chart.redraw(); - } - }); - } - - return series; - }, - - /** - * Check whether a given point is within the plot area - * - * @param {Number} plotX Pixel x relative to the plot area - * @param {Number} plotY Pixel y relative to the plot area - * @param {Boolean} inverted Whether the chart is inverted - */ - isInsidePlot: function (plotX, plotY, inverted) { - var x = inverted ? plotY : plotX, - y = inverted ? plotX : plotY; - - return x >= 0 && - x <= this.plotWidth && - y >= 0 && - y <= this.plotHeight; - }, - - /** - * Adjust all axes tick amounts - */ - adjustTickAmounts: function () { - if (this.options.chart.alignTicks !== false) { - each(this.axes, function (axis) { - axis.adjustTickAmount(); - }); - } - this.maxTicks = null; - }, - - /** - * Redraw legend, axes or series based on updated data - * - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - redraw: function (animation) { - var chart = this, - axes = chart.axes, - series = chart.series, - tracker = chart.tracker, - legend = chart.legend, - redrawLegend = chart.isDirtyLegend, - hasStackedSeries, - isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed? - seriesLength = series.length, - i = seriesLength, - serie, - renderer = chart.renderer, - isHiddenChart = renderer.isHidden(), - afterRedraw = []; - - setAnimation(animation, chart); - - if (isHiddenChart) { - chart.cloneRenderTo(); - } - - // link stacked series - while (i--) { - serie = series[i]; - if (serie.isDirty && serie.options.stacking) { - hasStackedSeries = true; - break; - } - } - if (hasStackedSeries) { // mark others as dirty - i = seriesLength; - while (i--) { - serie = series[i]; - if (serie.options.stacking) { - serie.isDirty = true; - } - } - } - - // handle updated data in the series - each(series, function (serie) { - if (serie.isDirty) { // prepare the data so axis can read it - if (serie.options.legendType === 'point') { - redrawLegend = true; - } - } - }); - - // handle added or removed series - if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed - // draw legend graphics - legend.render(); - - chart.isDirtyLegend = false; - } - - - if (chart.hasCartesianSeries) { - if (!chart.isResizing) { - - // reset maxTicks - chart.maxTicks = null; - - // set axes scales - each(axes, function (axis) { - axis.setScale(); - }); - } - chart.adjustTickAmounts(); - chart.getMargins(); - - // redraw axes - each(axes, function (axis) { - - // Fire 'afterSetExtremes' only if extremes are set - if (axis.isDirtyExtremes) { // #821 - axis.isDirtyExtremes = false; - afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119) - fireEvent(axis, 'afterSetExtremes', axis.getExtremes()); // #747, #751 - }); - } - - if (axis.isDirty || isDirtyBox || hasStackedSeries) { - axis.redraw(); - isDirtyBox = true; // #792 - } - }); - - - } - - // the plot areas size has changed - if (isDirtyBox) { - chart.drawChartBox(); - } - - - // redraw affected series - each(series, function (serie) { - if (serie.isDirty && serie.visible && - (!serie.isCartesian || serie.xAxis)) { // issue #153 - serie.redraw(); - } - }); - - - // move tooltip or reset - if (tracker && tracker.resetTracker) { - tracker.resetTracker(true); - } - - // redraw if canvas - renderer.draw(); - - // fire the event - fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw - - if (isHiddenChart) { - chart.cloneRenderTo(true); - } - - // Fire callbacks that are put on hold until after the redraw - each(afterRedraw, function (callback) { - callback.call(); - }); - }, - - - - /** - * Dim the chart and show a loading text or symbol - * @param {String} str An optional text to show in the loading label instead of the default one - */ - showLoading: function (str) { - var chart = this, - options = chart.options, - loadingDiv = chart.loadingDiv; - - var loadingOptions = options.loading; - - // create the layer at the first call - if (!loadingDiv) { - chart.loadingDiv = loadingDiv = createElement(DIV, { - className: PREFIX + 'loading' - }, extend(loadingOptions.style, { - left: chart.plotLeft + PX, - top: chart.plotTop + PX, - width: chart.plotWidth + PX, - height: chart.plotHeight + PX, - zIndex: 10, - display: NONE - }), chart.container); - - chart.loadingSpan = createElement( - 'span', - null, - loadingOptions.labelStyle, - loadingDiv - ); - - } - - // update text - chart.loadingSpan.innerHTML = str || options.lang.loading; - - // show it - if (!chart.loadingShown) { - css(loadingDiv, { opacity: 0, display: '' }); - animate(loadingDiv, { - opacity: loadingOptions.style.opacity - }, { - duration: loadingOptions.showDuration || 0 - }); - chart.loadingShown = true; - } - }, - - /** - * Hide the loading layer - */ - hideLoading: function () { - var options = this.options, - loadingDiv = this.loadingDiv; - - if (loadingDiv) { - animate(loadingDiv, { - opacity: 0 - }, { - duration: options.loading.hideDuration || 100, - complete: function () { - css(loadingDiv, { display: NONE }); - } - }); - } - this.loadingShown = false; - }, - - /** - * Get an axis, series or point object by id. - * @param id {String} The id as given in the configuration options - */ - get: function (id) { - var chart = this, - axes = chart.axes, - series = chart.series; - - var i, - j, - points; - - // search axes - for (i = 0; i < axes.length; i++) { - if (axes[i].options.id === id) { - return axes[i]; - } - } - - // search series - for (i = 0; i < series.length; i++) { - if (series[i].options.id === id) { - return series[i]; - } - } - - // search points - for (i = 0; i < series.length; i++) { - points = series[i].points || []; - for (j = 0; j < points.length; j++) { - if (points[j].id === id) { - return points[j]; - } - } - } - return null; - }, - - /** - * Create the Axis instances based on the config options - */ - getAxes: function () { - var chart = this, - options = this.options; - - var xAxisOptions = options.xAxis || {}, - yAxisOptions = options.yAxis || {}, - optionsArray, - axis; - - // make sure the options are arrays and add some members - xAxisOptions = splat(xAxisOptions); - each(xAxisOptions, function (axis, i) { - axis.index = i; - axis.isX = true; - }); - - yAxisOptions = splat(yAxisOptions); - each(yAxisOptions, function (axis, i) { - axis.index = i; - }); - - // concatenate all axis options into one array - optionsArray = xAxisOptions.concat(yAxisOptions); - - each(optionsArray, function (axisOptions) { - axis = new Axis(chart, axisOptions); - }); - - chart.adjustTickAmounts(); - }, - - - /** - * Get the currently selected points from all series - */ - getSelectedPoints: function () { - var points = []; - each(this.series, function (serie) { - points = points.concat(grep(serie.points, function (point) { - return point.selected; - })); - }); - return points; - }, - - /** - * Get the currently selected series - */ - getSelectedSeries: function () { - return grep(this.series, function (serie) { - return serie.selected; - }); - }, - - /** - * Display the zoom button - */ - showResetZoom: function () { - var chart = this, - lang = defaultOptions.lang, - btnOptions = chart.options.chart.resetZoomButton, - theme = btnOptions.theme, - states = theme.states, - alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox'; - - this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover) - .attr({ - align: btnOptions.position.align, - title: lang.resetZoomTitle - }) - .add() - .align(btnOptions.position, false, chart[alignTo]); - this.resetZoomButton.alignTo = alignTo; - - }, - - /** - * Zoom out to 1:1 - */ - zoomOut: function () { - var chart = this, - resetZoomButton = chart.resetZoomButton; - - fireEvent(chart, 'selection', { resetSelection: true }, function () { chart.zoom(); }); - if (resetZoomButton) { - chart.resetZoomButton = resetZoomButton.destroy(); - } - }, - - /** - * Zoom into a given portion of the chart given by axis coordinates - * @param {Object} event - */ - zoom: function (event) { - var chart = this, - hasZoomed; - - // if zoom is called with no arguments, reset the axes - if (!event || event.resetSelection) { - each(chart.axes, function (axis) { - hasZoomed = axis.zoom(); - }); - } else { // else, zoom in on all axes - each(event.xAxis.concat(event.yAxis), function (axisData) { - var axis = axisData.axis; - - // don't zoom more than minRange - if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) { - hasZoomed = axis.zoom(axisData.min, axisData.max); - } - }); - } - - // Show the Reset zoom button - if (!chart.resetZoomButton) { - chart.showResetZoom(); - } - - - // Redraw - if (hasZoomed) { - chart.redraw( - pick(chart.options.chart.animation, chart.pointCount < 100) // animation - ); - } - }, - - /** - * Pan the chart by dragging the mouse across the pane. This function is called - * on mouse move, and the distance to pan is computed from chartX compared to - * the first chartX position in the dragging operation. - */ - pan: function (chartX) { - var chart = this; - - var xAxis = chart.xAxis[0], - mouseDownX = chart.mouseDownX, - halfPointRange = xAxis.pointRange / 2, - extremes = xAxis.getExtremes(), - newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange, - newMax = xAxis.translate(mouseDownX + chart.plotWidth - chartX, true) - halfPointRange, - hoverPoints = chart.hoverPoints; - - // remove active points for shared tooltip - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - if (xAxis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) { - xAxis.setExtremes(newMin, newMax, true, false, { trigger: 'pan' }); - } - - chart.mouseDownX = chartX; // set new reference for next run - css(chart.container, { cursor: 'move' }); - }, - - /** - * Show the title and subtitle of the chart - * - * @param titleOptions {Object} New title options - * @param subtitleOptions {Object} New subtitle options - * - */ - setTitle: function (titleOptions, subtitleOptions) { - var chart = this, - options = chart.options, - chartTitleOptions, - chartSubtitleOptions; - - chart.chartTitleOptions = chartTitleOptions = merge(options.title, titleOptions); - chart.chartSubtitleOptions = chartSubtitleOptions = merge(options.subtitle, subtitleOptions); - - // add title and subtitle - each([ - ['title', titleOptions, chartTitleOptions], - ['subtitle', subtitleOptions, chartSubtitleOptions] - ], function (arr) { - var name = arr[0], - title = chart[name], - titleOptions = arr[1], - chartTitleOptions = arr[2]; - - if (title && titleOptions) { - chart[name] = title = title.destroy(); // remove old - } - - if (chartTitleOptions && chartTitleOptions.text && !title) { - chart[name] = chart.renderer.text( - chartTitleOptions.text, - 0, - 0, - chartTitleOptions.useHTML - ) - .attr({ - align: chartTitleOptions.align, - 'class': PREFIX + name, - zIndex: chartTitleOptions.zIndex || 4 - }) - .css(chartTitleOptions.style) - .add() - .align(chartTitleOptions, false, chart.spacingBox); - } - }); - - }, - - /** - * Get chart width and height according to options and container size - */ - getChartSize: function () { - var chart = this, - optionsChart = chart.options.chart, - renderTo = chart.renderToClone || chart.renderTo; - - // get inner width and height from jQuery (#824) - chart.containerWidth = adapterRun(renderTo, 'width'); - chart.containerHeight = adapterRun(renderTo, 'height'); - - chart.chartWidth = optionsChart.width || chart.containerWidth || 600; - chart.chartHeight = optionsChart.height || - // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7: - (chart.containerHeight > 19 ? chart.containerHeight : 400); - }, - - /** - * Create a clone of the chart's renderTo div and place it outside the viewport to allow - * size computation on chart.render and chart.redraw - */ - cloneRenderTo: function (revert) { - var clone = this.renderToClone, - container = this.container; - - // Destroy the clone and bring the container back to the real renderTo div - if (revert) { - if (clone) { - this.renderTo.appendChild(container); - discardElement(clone); - delete this.renderToClone; - } - - // Set up the clone - } else { - if (container) { - this.renderTo.removeChild(container); // do not clone this - } - this.renderToClone = clone = this.renderTo.cloneNode(0); - css(clone, { - position: ABSOLUTE, - top: '-9999px', - display: 'block' // #833 - }); - doc.body.appendChild(clone); - if (container) { - clone.appendChild(container); - } - } - }, - - /** - * Get the containing element, determine the size and create the inner container - * div to hold the chart - */ - getContainer: function () { - var chart = this, - container, - optionsChart = chart.options.chart, - chartWidth, - chartHeight, - renderTo, - containerId; - - chart.renderTo = renderTo = optionsChart.renderTo; - containerId = PREFIX + idCounter++; - - if (isString(renderTo)) { - chart.renderTo = renderTo = doc.getElementById(renderTo); - } - - // Display an error if the renderTo is wrong - if (!renderTo) { - error(13, true); - } - - // remove previous chart - renderTo.innerHTML = ''; - - // If the container doesn't have an offsetWidth, it has or is a child of a node - // that has display:none. We need to temporarily move it out to a visible - // state to determine the size, else the legend and tooltips won't render - // properly - if (!renderTo.offsetWidth) { - chart.cloneRenderTo(); - } - - // get the width and height - chart.getChartSize(); - chartWidth = chart.chartWidth; - chartHeight = chart.chartHeight; - - // create the inner container - chart.container = container = createElement(DIV, { - className: PREFIX + 'container' + - (optionsChart.className ? ' ' + optionsChart.className : ''), - id: containerId - }, extend({ - position: RELATIVE, - overflow: HIDDEN, // needed for context menu (avoid scrollbars) and - // content overflow in IE - width: chartWidth + PX, - height: chartHeight + PX, - textAlign: 'left', - lineHeight: 'normal', // #427 - zIndex: 0 // #1072 - }, optionsChart.style), - chart.renderToClone || renderTo - ); - - chart.renderer = - optionsChart.forExport ? // force SVG, used for SVG export - new SVGRenderer(container, chartWidth, chartHeight, true) : - new Renderer(container, chartWidth, chartHeight); - - if (useCanVG) { - // If we need canvg library, extend and configure the renderer - // to get the tracker for translating mouse events - chart.renderer.create(chart, container, chartWidth, chartHeight); - } - }, - - /** - * Calculate margins by rendering axis labels in a preliminary position. Title, - * subtitle and legend have already been rendered at this stage, but will be - * moved into their final positions - */ - getMargins: function () { - var chart = this, - optionsChart = chart.options.chart, - spacingTop = optionsChart.spacingTop, - spacingRight = optionsChart.spacingRight, - spacingBottom = optionsChart.spacingBottom, - spacingLeft = optionsChart.spacingLeft, - axisOffset, - legend = chart.legend, - optionsMarginTop = chart.optionsMarginTop, - optionsMarginLeft = chart.optionsMarginLeft, - optionsMarginRight = chart.optionsMarginRight, - optionsMarginBottom = chart.optionsMarginBottom, - chartTitleOptions = chart.chartTitleOptions, - chartSubtitleOptions = chart.chartSubtitleOptions, - legendOptions = chart.options.legend, - legendMargin = pick(legendOptions.margin, 10), - legendX = legendOptions.x, - legendY = legendOptions.y, - align = legendOptions.align, - verticalAlign = legendOptions.verticalAlign, - titleOffset; - - chart.resetMargins(); - axisOffset = chart.axisOffset; - - // adjust for title and subtitle - if ((chart.title || chart.subtitle) && !defined(chart.optionsMarginTop)) { - titleOffset = mathMax( - (chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0, - (chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0 - ); - if (titleOffset) { - chart.plotTop = mathMax(chart.plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop); - } - } - // adjust for legend - if (legend.display && !legendOptions.floating) { - if (align === 'right') { // horizontal alignment handled first - if (!defined(optionsMarginRight)) { - chart.marginRight = mathMax( - chart.marginRight, - legend.legendWidth - legendX + legendMargin + spacingRight - ); - } - } else if (align === 'left') { - if (!defined(optionsMarginLeft)) { - chart.plotLeft = mathMax( - chart.plotLeft, - legend.legendWidth + legendX + legendMargin + spacingLeft - ); - } - - } else if (verticalAlign === 'top') { - if (!defined(optionsMarginTop)) { - chart.plotTop = mathMax( - chart.plotTop, - legend.legendHeight + legendY + legendMargin + spacingTop - ); - } - - } else if (verticalAlign === 'bottom') { - if (!defined(optionsMarginBottom)) { - chart.marginBottom = mathMax( - chart.marginBottom, - legend.legendHeight - legendY + legendMargin + spacingBottom - ); - } - } - } - - // adjust for scroller - if (chart.extraBottomMargin) { - chart.marginBottom += chart.extraBottomMargin; - } - if (chart.extraTopMargin) { - chart.plotTop += chart.extraTopMargin; - } - - // pre-render axes to get labels offset width - if (chart.hasCartesianSeries) { - each(chart.axes, function (axis) { - axis.getOffset(); - }); - } - - if (!defined(optionsMarginLeft)) { - chart.plotLeft += axisOffset[3]; - } - if (!defined(optionsMarginTop)) { - chart.plotTop += axisOffset[0]; - } - if (!defined(optionsMarginBottom)) { - chart.marginBottom += axisOffset[2]; - } - if (!defined(optionsMarginRight)) { - chart.marginRight += axisOffset[1]; - } - - chart.setChartSize(); - - }, - - /** - * Add the event handlers necessary for auto resizing - * - */ - initReflow: function () { - var chart = this, - optionsChart = chart.options.chart, - renderTo = chart.renderTo, - reflowTimeout; - - function reflow(e) { - var width = optionsChart.width || adapterRun(renderTo, 'width'), - height = optionsChart.height || adapterRun(renderTo, 'height'), - target = e ? e.target : win; // #805 - MooTools doesn't supply e - - // Width and height checks for display:none. Target is doc in IE8 and Opera, - // win in Firefox, Chrome and IE9. - if (width && height && (target === win || target === doc)) { - - if (width !== chart.containerWidth || height !== chart.containerHeight) { - clearTimeout(reflowTimeout); - chart.reflowTimeout = reflowTimeout = setTimeout(function () { - if (chart.container) { // It may have been destroyed in the meantime (#1257) - chart.resize(width, height, false); - } - }, 100); - } - chart.containerWidth = width; - chart.containerHeight = height; - } - } - addEvent(win, 'resize', reflow); - addEvent(chart, 'destroy', function () { - removeEvent(win, 'resize', reflow); - }); - }, - - /** - * Resize the chart to a given width and height - * @param {Number} width - * @param {Number} height - * @param {Object|Boolean} animation - */ - // TODO: This method is called setSize in the api - resize: function (width, height, animation) { - var chart = this, - chartWidth, - chartHeight, - spacingBox, - resetZoomButton = chart.resetZoomButton, - chartTitle = chart.title, - chartSubtitle = chart.subtitle, - fireEndResize; - - // Handle the isResizing counter - chart.isResizing += 1; - fireEndResize = function () { - if (chart) { - fireEvent(chart, 'endResize', null, function () { - chart.isResizing -= 1; - }); - } - }; - - // set the animation for the current process - setAnimation(animation, chart); - - chart.oldChartHeight = chart.chartHeight; - chart.oldChartWidth = chart.chartWidth; - if (defined(width)) { - chart.chartWidth = chartWidth = mathRound(width); - } - if (defined(height)) { - chart.chartHeight = chartHeight = mathRound(height); - } - - css(chart.container, { - width: chartWidth + PX, - height: chartHeight + PX - }); - chart.renderer.setSize(chartWidth, chartHeight, animation); - - // update axis lengths for more correct tick intervals: - chart.plotWidth = chartWidth - chart.plotLeft - chart.marginRight; - chart.plotHeight = chartHeight - chart.plotTop - chart.marginBottom; - - // handle axes - chart.maxTicks = null; - each(chart.axes, function (axis) { - axis.isDirty = true; - axis.setScale(); - }); - - // make sure non-cartesian series are also handled - each(chart.series, function (serie) { - serie.isDirty = true; - }); - - chart.isDirtyLegend = true; // force legend redraw - chart.isDirtyBox = true; // force redraw of plot and chart border - - chart.getMargins(); - - // move titles - spacingBox = chart.spacingBox; - if (chartTitle) { - chartTitle.align(null, null, spacingBox); - } - if (chartSubtitle) { - chartSubtitle.align(null, null, spacingBox); - } - - // Move resize button (#1115) - if (resetZoomButton && resetZoomButton.align) { - resetZoomButton.align(null, null, chart[resetZoomButton.alignTo]); - } - - chart.redraw(animation); - - - chart.oldChartHeight = null; - fireEvent(chart, 'resize'); - - // fire endResize and set isResizing back - // If animation is disabled, fire without delay - if (globalAnimation === false) { - fireEndResize(); - } else { // else set a timeout with the animation duration - setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500); - } - }, - - /** - * Set the public chart properties. This is done before and after the pre-render - * to determine margin sizes - */ - setChartSize: function () { - var chart = this, - inverted = chart.inverted, - chartWidth = chart.chartWidth, - chartHeight = chart.chartHeight, - optionsChart = chart.options.chart, - spacingTop = optionsChart.spacingTop, - spacingRight = optionsChart.spacingRight, - spacingBottom = optionsChart.spacingBottom, - spacingLeft = optionsChart.spacingLeft, - plotLeft, - plotTop, - plotWidth, - plotHeight, - plotBorderWidth; - - chart.plotLeft = plotLeft = mathRound(chart.plotLeft); - chart.plotTop = plotTop = mathRound(chart.plotTop); - chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - chart.marginRight); - chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - chart.marginBottom); - - chart.plotSizeX = inverted ? plotHeight : plotWidth; - chart.plotSizeY = inverted ? plotWidth : plotHeight; - - chart.plotBorderWidth = plotBorderWidth = optionsChart.plotBorderWidth || 0; - - // Set boxes used for alignment - chart.spacingBox = { - x: spacingLeft, - y: spacingTop, - width: chartWidth - spacingLeft - spacingRight, - height: chartHeight - spacingTop - spacingBottom - }; - chart.plotBox = { - x: plotLeft, - y: plotTop, - width: plotWidth, - height: plotHeight - }; - chart.clipBox = { - x: plotBorderWidth / 2, - y: plotBorderWidth / 2, - width: chart.plotSizeX - plotBorderWidth, - height: chart.plotSizeY - plotBorderWidth - }; - - each(chart.axes, function (axis) { - axis.setAxisSize(); - axis.setAxisTranslation(); - }); - }, - - /** - * Initial margins before auto size margins are applied - */ - resetMargins: function () { - var chart = this, - optionsChart = chart.options.chart, - spacingTop = optionsChart.spacingTop, - spacingRight = optionsChart.spacingRight, - spacingBottom = optionsChart.spacingBottom, - spacingLeft = optionsChart.spacingLeft; - - chart.plotTop = pick(chart.optionsMarginTop, spacingTop); - chart.marginRight = pick(chart.optionsMarginRight, spacingRight); - chart.marginBottom = pick(chart.optionsMarginBottom, spacingBottom); - chart.plotLeft = pick(chart.optionsMarginLeft, spacingLeft); - chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left - }, - - /** - * Draw the borders and backgrounds for chart and plot area - */ - drawChartBox: function () { - var chart = this, - optionsChart = chart.options.chart, - renderer = chart.renderer, - chartWidth = chart.chartWidth, - chartHeight = chart.chartHeight, - chartBackground = chart.chartBackground, - plotBackground = chart.plotBackground, - plotBorder = chart.plotBorder, - plotBGImage = chart.plotBGImage, - chartBorderWidth = optionsChart.borderWidth || 0, - chartBackgroundColor = optionsChart.backgroundColor, - plotBackgroundColor = optionsChart.plotBackgroundColor, - plotBackgroundImage = optionsChart.plotBackgroundImage, - plotBorderWidth = optionsChart.plotBorderWidth || 0, - mgn, - bgAttr, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop, - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - plotBox = chart.plotBox, - clipRect = chart.clipRect, - clipBox = chart.clipBox; - - // Chart area - mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0); - - if (chartBorderWidth || chartBackgroundColor) { - if (!chartBackground) { - - bgAttr = { - fill: chartBackgroundColor || NONE - }; - if (chartBorderWidth) { // #980 - bgAttr.stroke = optionsChart.borderColor; - bgAttr['stroke-width'] = chartBorderWidth; - } - chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn, - optionsChart.borderRadius, chartBorderWidth) - .attr(bgAttr) - .add() - .shadow(optionsChart.shadow); - - } else { // resize - chartBackground.animate( - chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn) - ); - } - } - - - // Plot background - if (plotBackgroundColor) { - if (!plotBackground) { - chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0) - .attr({ - fill: plotBackgroundColor - }) - .add() - .shadow(optionsChart.plotShadow); - } else { - plotBackground.animate(plotBox); - } - } - if (plotBackgroundImage) { - if (!plotBGImage) { - chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight) - .add(); - } else { - plotBGImage.animate(plotBox); - } - } - - // Plot clip - if (!clipRect) { - chart.clipRect = renderer.clipRect(clipBox); - } else { - clipRect.animate({ - width: clipBox.width, - height: clipBox.height - }); - } - - // Plot area border - if (plotBorderWidth) { - if (!plotBorder) { - chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, plotBorderWidth) - .attr({ - stroke: optionsChart.plotBorderColor, - 'stroke-width': plotBorderWidth, - zIndex: 1 - }) - .add(); - } else { - plotBorder.animate( - plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight) - ); - } - } - - // reset - chart.isDirtyBox = false; - }, - - /** - * Detect whether a certain chart property is needed based on inspecting its options - * and series. This mainly applies to the chart.invert property, and in extensions to - * the chart.angular and chart.polar properties. - */ - propFromSeries: function () { - var chart = this, - optionsChart = chart.options.chart, - klass, - seriesOptions = chart.options.series, - i, - value; - - - each(['inverted', 'angular', 'polar'], function (key) { - - // The default series type's class - klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType]; - - // Get the value from available chart-wide properties - value = ( - chart[key] || // 1. it is set before - optionsChart[key] || // 2. it is set in the options - (klass && klass.prototype[key]) // 3. it's default series class requires it - ); - - // 4. Check if any the chart's series require it - i = seriesOptions && seriesOptions.length; - while (!value && i--) { - klass = seriesTypes[seriesOptions[i].type]; - if (klass && klass.prototype[key]) { - value = true; - } - } - - // Set the chart property - chart[key] = value; - }); - - }, - - /** - * Render all graphics for the chart - */ - render: function () { - var chart = this, - axes = chart.axes, - renderer = chart.renderer, - options = chart.options; - - var labels = options.labels, - credits = options.credits, - creditsHref; - - // Title - chart.setTitle(); - - - // Legend - chart.legend = new Legend(chart); - - // Get margins by pre-rendering axes - // set axes scales - each(axes, function (axis) { - axis.setScale(); - }); - chart.getMargins(); - - chart.maxTicks = null; // reset for second pass - each(axes, function (axis) { - axis.setTickPositions(true); // update to reflect the new margins - axis.setMaxTicks(); - }); - chart.adjustTickAmounts(); - chart.getMargins(); // second pass to check for new labels - - - // Draw the borders and backgrounds - chart.drawChartBox(); - - - // Axes - if (chart.hasCartesianSeries) { - each(axes, function (axis) { - axis.render(); - }); - } - - // The series - if (!chart.seriesGroup) { - chart.seriesGroup = renderer.g('series-group') - .attr({ zIndex: 3 }) - .add(); - } - each(chart.series, function (serie) { - serie.translate(); - serie.setTooltipPoints(); - serie.render(); - }); - - // Labels - if (labels.items) { - each(labels.items, function (label) { - var style = extend(labels.style, label.style), - x = pInt(style.left) + chart.plotLeft, - y = pInt(style.top) + chart.plotTop + 12; - - // delete to prevent rewriting in IE - delete style.left; - delete style.top; - - renderer.text( - label.html, - x, - y - ) - .attr({ zIndex: 2 }) - .css(style) - .add(); - - }); - } - - // Credits - if (credits.enabled && !chart.credits) { - creditsHref = credits.href; - chart.credits = renderer.text( - credits.text, - 0, - 0 - ) - .on('click', function () { - if (creditsHref) { - location.href = creditsHref; - } - }) - .attr({ - align: credits.position.align, - zIndex: 8 - }) - .css(credits.style) - .add() - .align(credits.position); - } - - // Set flag - chart.hasRendered = true; - - }, - - /** - * Clean up memory usage - */ - destroy: function () { - var chart = this, - axes = chart.axes, - series = chart.series, - container = chart.container, - i, - parentNode = container && container.parentNode; - - // fire the chart.destoy event - fireEvent(chart, 'destroy'); - - // remove events - removeEvent(chart); - - // ==== Destroy collections: - // Destroy axes - i = axes.length; - while (i--) { - axes[i] = axes[i].destroy(); - } - - // Destroy each series - i = series.length; - while (i--) { - series[i] = series[i].destroy(); - } - - // ==== Destroy chart properties: - each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'tracker', 'scroller', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) { - var prop = chart[name]; - - if (prop && prop.destroy) { - chart[name] = prop.destroy(); - } - }); - - // remove container and all SVG - if (container) { // can break in IE when destroyed before finished loading - container.innerHTML = ''; - removeEvent(container); - if (parentNode) { - discardElement(container); - } - - } - - // clean it all up - for (i in chart) { - delete chart[i]; - } - - }, - - /** - * Prepare for first rendering after all data are loaded - */ - firstRender: function () { - var chart = this, - options = chart.options, - callback = chart.callback; - - // VML namespaces can't be added until after complete. Listening - // for Perini's doScroll hack is not enough. - var ONREADYSTATECHANGE = 'onreadystatechange', - COMPLETE = 'complete'; - // Note: in spite of JSLint's complaints, win == win.top is required - /*jslint eqeq: true*/ - if ((!hasSVG && (win == win.top && doc.readyState !== COMPLETE)) || (useCanVG && !win.canvg)) { - /*jslint eqeq: false*/ - if (useCanVG) { - // Delay rendering until canvg library is downloaded and ready - CanVGController.push(function () { chart.firstRender(); }, options.global.canvasToolsURL); - } else { - doc.attachEvent(ONREADYSTATECHANGE, function () { - doc.detachEvent(ONREADYSTATECHANGE, chart.firstRender); - if (doc.readyState === COMPLETE) { - chart.firstRender(); - } - }); - } - return; - } - - // create the container - chart.getContainer(); - - // Run an early event after the container and renderer are established - fireEvent(chart, 'init'); - - // Initialize range selector for stock charts - if (Highcharts.RangeSelector && options.rangeSelector.enabled) { - chart.rangeSelector = new Highcharts.RangeSelector(chart); - } - - chart.resetMargins(); - chart.setChartSize(); - - // Set the common chart properties (mainly invert) from the given series - chart.propFromSeries(); - - // get axes - chart.getAxes(); - - // Initialize the series - each(options.series || [], function (serieOptions) { - chart.initSeries(serieOptions); - }); - - // Run an event where series and axes can be added - //fireEvent(chart, 'beforeRender'); - - // Initialize scroller for stock charts - if (Highcharts.Scroller && (options.navigator.enabled || options.scrollbar.enabled)) { - chart.scroller = new Highcharts.Scroller(chart); - } - - // depends on inverted and on margins being set - chart.tracker = new MouseTracker(chart, options); - - chart.render(); - - // add canvas - chart.renderer.draw(); - // run callbacks - if (callback) { - callback.apply(chart, [chart]); - } - each(chart.callbacks, function (fn) { - fn.apply(chart, [chart]); - }); - - - // If the chart was rendered outside the top container, put it back in - chart.cloneRenderTo(true); - - fireEvent(chart, 'load'); - - }, - - init: function (chartEvents) { - var chart = this, - optionsChart = chart.options.chart, - eventType; - - // Run chart - - // Set up auto resize - if (optionsChart.reflow !== false) { - addEvent(chart, 'load', chart.initReflow); - } - - // Chart event handlers - if (chartEvents) { - for (eventType in chartEvents) { - addEvent(chart, eventType, chartEvents[eventType]); - } - } - - chart.xAxis = []; - chart.yAxis = []; - - // Expose methods and variables - chart.animation = useCanVG ? false : pick(optionsChart.animation, true); - chart.setSize = chart.resize; - chart.pointCount = 0; - chart.counters = new ChartCounters(); - /* - if ($) $(function () { - $container = $('#container'); - var origChartWidth, - origChartHeight; - if ($container) { - $('') - .insertBefore($container) - .click(function () { - if (origChartWidth === UNDEFINED) { - origChartWidth = chartWidth; - origChartHeight = chartHeight; - } - chart.resize(chartWidth *= 1.1, chartHeight *= 1.1); - }); - $('') - .insertBefore($container) - .click(function () { - if (origChartWidth === UNDEFINED) { - origChartWidth = chartWidth; - origChartHeight = chartHeight; - } - chart.resize(chartWidth *= 0.9, chartHeight *= 0.9); - }); - $('') - .insertBefore($container) - .click(function () { - if (origChartWidth === UNDEFINED) { - origChartWidth = chartWidth; - origChartHeight = chartHeight; - } - chart.resize(origChartWidth, origChartHeight); - }); - } - }) - */ - - chart.firstRender(); - } -}; // end Chart - -// Hook for exporting module -Chart.prototype.callbacks = []; -/** - * The Point object and prototype. Inheritable and used as base for PiePoint - */ -var Point = function () {}; -Point.prototype = { - - /** - * Initialize the point - * @param {Object} series The series object containing this point - * @param {Object} options The data in either number, array or object format - */ - init: function (series, options, x) { - var point = this, - counters = series.chart.counters, - defaultColors; - point.series = series; - point.applyOptions(options, x); - point.pointAttr = {}; - - if (series.options.colorByPoint) { - defaultColors = series.chart.options.colors; - point.color = point.color || defaultColors[counters.color++]; - - // loop back to zero - counters.wrapColor(defaultColors.length); - } - - series.chart.pointCount++; - return point; - }, - /** - * Apply the options containing the x and y data and possible some extra properties. - * This is called on point init or from point.update. - * - * @param {Object} options - */ - applyOptions: function (options, x) { - var point = this, - series = point.series, - optionsType = typeof options; - - point.config = options; - - // onedimensional array input - if (optionsType === 'number' || options === null) { - point.y = options; - } else if (typeof options[0] === 'number') { // two-dimentional array - point.x = options[0]; - point.y = options[1]; - } else if (optionsType === 'object' && typeof options.length !== 'number') { // object input - // copy options directly to point - extend(point, options); - point.options = options; - - // This is the fastest way to detect if there are individual point dataLabels that need - // to be considered in drawDataLabels. These can only occur in object configs. - if (options.dataLabels) { - series._hasPointLabels = true; - } - - // Same approach as above for markers - if (options.marker) { - series._hasPointMarkers = true; - } - } else if (typeof options[0] === 'string') { // categorized data with name in first position - point.name = options[0]; - point.y = options[1]; - } - - /* - * If no x is set by now, get auto incremented value. All points must have an - * x value, however the y value can be null to create a gap in the series - */ - // todo: skip this? It is only used in applyOptions, in translate it should not be used - if (point.x === UNDEFINED) { - point.x = x === UNDEFINED ? series.autoIncrement() : x; - } - - }, - - /** - * Destroy a point to clear memory. Its reference still stays in series.data. - */ - destroy: function () { - var point = this, - series = point.series, - chart = series.chart, - hoverPoints = chart.hoverPoints, - prop; - - chart.pointCount--; - - if (hoverPoints) { - point.setState(); - erase(hoverPoints, point); - if (!hoverPoints.length) { - chart.hoverPoints = null; - } - - } - if (point === chart.hoverPoint) { - point.onMouseOut(); - } - - // remove all events - if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive - removeEvent(point); - point.destroyElements(); - } - - if (point.legendItem) { // pies have legend items - chart.legend.destroyItem(point); - } - - for (prop in point) { - point[prop] = null; - } - - - }, - - /** - * Destroy SVG elements associated with the point - */ - destroyElements: function () { - var point = this, - props = ['graphic', 'tracker', 'dataLabel', 'group', 'connector', 'shadowGroup'], - prop, - i = 6; - while (i--) { - prop = props[i]; - if (point[prop]) { - point[prop] = point[prop].destroy(); - } - } - }, - - /** - * Return the configuration hash needed for the data label and tooltip formatters - */ - getLabelConfig: function () { - var point = this; - return { - x: point.category, - y: point.y, - key: point.name || point.category, - series: point.series, - point: point, - percentage: point.percentage, - total: point.total || point.stackTotal - }; - }, - - /** - * Toggle the selection status of a point - * @param {Boolean} selected Whether to select or unselect the point. - * @param {Boolean} accumulate Whether to add to the previous selection. By default, - * this happens if the control key (Cmd on Mac) was pressed during clicking. - */ - select: function (selected, accumulate) { - var point = this, - series = point.series, - chart = series.chart; - - selected = pick(selected, !point.selected); - - // fire the event with the defalut handler - point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { - point.selected = selected; - point.setState(selected && SELECT_STATE); - - // unselect all other points unless Ctrl or Cmd + click - if (!accumulate) { - each(chart.getSelectedPoints(), function (loopPoint) { - if (loopPoint.selected && loopPoint !== point) { - loopPoint.selected = false; - loopPoint.setState(NORMAL_STATE); - loopPoint.firePointEvent('unselect'); - } - }); - } - }); - }, - - onMouseOver: function () { - var point = this, - series = point.series, - chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - - // set normal state to previous series - if (hoverPoint && hoverPoint !== point) { - hoverPoint.onMouseOut(); - } - - // trigger the event - point.firePointEvent('mouseOver'); - - // update the tooltip - if (tooltip && (!tooltip.shared || series.noSharedTooltip)) { - tooltip.refresh(point); - } - - // hover this - point.setState(HOVER_STATE); - chart.hoverPoint = point; - }, - - onMouseOut: function () { - var chart = this.series.chart, - hoverPoints = chart.hoverPoints; - - if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887 - this.firePointEvent('mouseOut'); - - this.setState(); - chart.hoverPoint = null; - } - }, - - /** - * Extendable method for formatting each point's tooltip line - * - * @return {String} A string to be concatenated in to the common tooltip text - */ - tooltipFormatter: function (pointFormat) { - var point = this, - series = point.series, - seriesTooltipOptions = series.tooltipOptions, - match = pointFormat.match(/\{(series|point)\.[a-zA-Z]+\}/g), - splitter = /[{\.}]/, - obj, - key, - replacement, - repOptionKey, - parts, - prop, - i, - cfg = { - y: 0, // 0: use 'value' for repOptionKey - open: 0, - high: 0, - low: 0, - close: 0, - percentage: 1, // 1: use the self name for repOptionKey - total: 1 - }; - - // Backwards compatibility to y naming in early Highstock - seriesTooltipOptions.valuePrefix = seriesTooltipOptions.valuePrefix || seriesTooltipOptions.yPrefix; - seriesTooltipOptions.valueDecimals = seriesTooltipOptions.valueDecimals || seriesTooltipOptions.yDecimals; - seriesTooltipOptions.valueSuffix = seriesTooltipOptions.valueSuffix || seriesTooltipOptions.ySuffix; - - // loop over the variables defined on the form {series.name}, {point.y} etc - for (i in match) { - key = match[i]; - if (isString(key) && key !== pointFormat) { // IE matches more than just the variables - - // Split it further into parts - parts = (' ' + key).split(splitter); // add empty string because IE and the rest handles it differently - obj = { 'point': point, 'series': series }[parts[1]]; - prop = parts[2]; - - // Add some preformatting - if (obj === point && cfg.hasOwnProperty(prop)) { - repOptionKey = cfg[prop] ? prop : 'value'; - replacement = (seriesTooltipOptions[repOptionKey + 'Prefix'] || '') + - numberFormat(point[prop], pick(seriesTooltipOptions[repOptionKey + 'Decimals'], -1)) + - (seriesTooltipOptions[repOptionKey + 'Suffix'] || ''); - - // Automatic replacement - } else { - replacement = obj[prop]; - } - - pointFormat = pointFormat.replace(key, replacement); - } - } - - return pointFormat; - }, - - /** - * Update the point with new options (typically x/y data) and optionally redraw the series. - * - * @param {Object} options Point options as defined in the series.data array - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * - */ - update: function (options, redraw, animation) { - var point = this, - series = point.series, - graphic = point.graphic, - i, - data = series.data, - dataLength = data.length, - chart = series.chart; - - redraw = pick(redraw, true); - - // fire the event with a default handler of doing the update - point.firePointEvent('update', { options: options }, function () { - - point.applyOptions(options); - - // update visuals - if (isObject(options)) { - series.getAttribs(); - if (graphic) { - graphic.attr(point.pointAttr[series.state]); - } - } - - // record changes in the parallel arrays - for (i = 0; i < dataLength; i++) { - if (data[i] === point) { - series.xData[i] = point.x; - series.yData[i] = point.y; - series.options.data[i] = options; - break; - } - } - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - chart.redraw(animation); - } - }); - }, - - /** - * Remove a point and optionally redraw the series and if necessary the axes - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - remove: function (redraw, animation) { - var point = this, - series = point.series, - chart = series.chart, - i, - data = series.data, - dataLength = data.length; - - setAnimation(animation, chart); - redraw = pick(redraw, true); - - // fire the event with a default handler of removing the point - point.firePointEvent('remove', null, function () { - - //erase(series.data, point); - - for (i = 0; i < dataLength; i++) { - if (data[i] === point) { - - // splice all the parallel arrays - data.splice(i, 1); - series.options.data.splice(i, 1); - series.xData.splice(i, 1); - series.yData.splice(i, 1); - break; - } - } - - point.destroy(); - - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - chart.redraw(); - } - }); - - - }, - - /** - * Fire an event on the Point object. Must not be renamed to fireEvent, as this - * causes a name clash in MooTools - * @param {String} eventType - * @param {Object} eventArgs Additional event arguments - * @param {Function} defaultFunction Default event handler - */ - firePointEvent: function (eventType, eventArgs, defaultFunction) { - var point = this, - series = this.series, - seriesOptions = series.options; - - // load event handlers on demand to save time on mouseover/out - if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) { - this.importEvents(); - } - - // add default handler if in selection mode - if (eventType === 'click' && seriesOptions.allowPointSelect) { - defaultFunction = function (event) { - // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera - point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); - }; - } - - fireEvent(this, eventType, eventArgs, defaultFunction); - }, - /** - * Import events from the series' and point's options. Only do it on - * demand, to save processing time on hovering. - */ - importEvents: function () { - if (!this.hasImportedEvents) { - var point = this, - options = merge(point.series.options.point, point.options), - events = options.events, - eventType; - - point.events = events; - - for (eventType in events) { - addEvent(point, eventType, events[eventType]); - } - this.hasImportedEvents = true; - - } - }, - - /** - * Set the point's state - * @param {String} state - */ - setState: function (state) { - var point = this, - plotX = point.plotX, - plotY = point.plotY, - series = point.series, - stateOptions = series.options.states, - markerOptions = defaultPlotOptions[series.type].marker && series.options.marker, - normalDisabled = markerOptions && !markerOptions.enabled, - markerStateOptions = markerOptions && markerOptions.states[state], - stateDisabled = markerStateOptions && markerStateOptions.enabled === false, - stateMarkerGraphic = series.stateMarkerGraphic, - chart = series.chart, - radius, - pointAttr = point.pointAttr; - - state = state || NORMAL_STATE; // empty string - - if ( - // already has this state - state === point.state || - // selected points don't respond to hover - (point.selected && state !== SELECT_STATE) || - // series' state options is disabled - (stateOptions[state] && stateOptions[state].enabled === false) || - // point marker's state options is disabled - (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled))) - - ) { - return; - } - - // apply hover styles to the existing point - if (point.graphic) { - radius = markerOptions && point.graphic.symbolName && pointAttr[state].r; - point.graphic.attr(merge( - pointAttr[state], - radius ? { // new symbol attributes (#507, #612) - x: plotX - radius, - y: plotY - radius, - width: 2 * radius, - height: 2 * radius - } : {} - )); - } else { - // if a graphic is not applied to each point in the normal state, create a shared - // graphic for the hover state - if (state && markerStateOptions) { - radius = markerStateOptions.radius; - if (!stateMarkerGraphic) { // add - series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol( - series.symbol, - plotX - radius, - plotY - radius, - 2 * radius, - 2 * radius - ) - .attr(pointAttr[state]) - .add(series.markerGroup); - - } else { // update - stateMarkerGraphic.attr({ // #1054 - x: plotX - radius, - y: plotY - radius - }); - } - } - - if (stateMarkerGraphic) { - stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide'](); - } - } - - point.state = state; - } -}; - -/** - * @classDescription The base function which all other series types inherit from. The data in the series is stored - * in various arrays. - * - * - First, series.options.data contains all the original config options for - * each point whether added by options or methods like series.addPoint. - * - Next, series.data contains those values converted to points, but in case the series data length - * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It - * only contains the points that have been created on demand. - * - Then there's series.points that contains all currently visible point objects. In case of cropping, - * the cropped-away points are not part of this array. The series.points array starts at series.cropStart - * compared to series.data and series.options.data. If however the series data is grouped, these can't - * be correlated one to one. - * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points. - * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points. - * - * @param {Object} chart - * @param {Object} options - */ -var Series = function () {}; - -Series.prototype = { - - isCartesian: true, - type: 'line', - pointClass: Point, - sorted: true, // requires the data to be sorted - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'lineColor', - 'stroke-width': 'lineWidth', - fill: 'fillColor', - r: 'radius' - }, - init: function (chart, options) { - var series = this, - eventType, - events; - - series.chart = chart; - series.options = options = series.setOptions(options); // merge with plotOptions - - // bind the axes - series.bindAxes(); - - // set some variables - extend(series, { - name: options.name, - state: NORMAL_STATE, - pointAttr: {}, - visible: options.visible !== false, // true by default - selected: options.selected === true // false by default - }); - - // special - if (useCanVG) { - options.animation = false; - } - - // register event listeners - events = options.events; - for (eventType in events) { - addEvent(series, eventType, events[eventType]); - } - if ( - (events && events.click) || - (options.point && options.point.events && options.point.events.click) || - options.allowPointSelect - ) { - chart.runTrackerClick = true; - } - - series.getColor(); - series.getSymbol(); - - // set the data - series.setData(options.data, false); - - // Mark cartesian - if (series.isCartesian) { - chart.hasCartesianSeries = true; - } - - // Register it in the chart - chart.series.push(series); - - // Sort series according to index option (#248, #1123) - stableSort(chart.series, function (a, b) { - return (a.options.index || 0) - (b.options.index || 0); - }); - each(chart.series, function (series, i) { - series.index = i; - series.name = series.name || 'Series ' + (i + 1); - }); - }, - - /** - * Set the xAxis and yAxis properties of cartesian series, and register the series - * in the axis.series array - */ - bindAxes: function () { - var series = this, - seriesOptions = series.options, - chart = series.chart, - axisOptions; - - if (series.isCartesian) { - - each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis - - each(chart[AXIS], function (axis) { // loop through the chart's axis objects - - axisOptions = axis.options; - - // apply if the series xAxis or yAxis option mathches the number of the - // axis, or if undefined, use the first axis - if ((seriesOptions[AXIS] === axisOptions.index) || - (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) { - - // register this series in the axis.series lookup - axis.series.push(series); - - // set this series.xAxis or series.yAxis reference - series[AXIS] = axis; - - // mark dirty for redraw - axis.isDirty = true; - } - }); - - }); - } - }, - - - /** - * Return an auto incremented x value based on the pointStart and pointInterval options. - * This is only used if an x value is not given for the point that calls autoIncrement. - */ - autoIncrement: function () { - var series = this, - options = series.options, - xIncrement = series.xIncrement; - - xIncrement = pick(xIncrement, options.pointStart, 0); - - series.pointInterval = pick(series.pointInterval, options.pointInterval, 1); - - series.xIncrement = xIncrement + series.pointInterval; - return xIncrement; - }, - - /** - * Divide the series data into segments divided by null values. - */ - getSegments: function () { - var series = this, - lastNull = -1, - segments = [], - i, - points = series.points, - pointsLength = points.length; - - if (pointsLength) { // no action required for [] - - // if connect nulls, just remove null points - if (series.options.connectNulls) { - i = pointsLength; - while (i--) { - if (points[i].y === null) { - points.splice(i, 1); - } - } - if (points.length) { - segments = [points]; - } - - // else, split on null points - } else { - each(points, function (point, i) { - if (point.y === null) { - if (i > lastNull + 1) { - segments.push(points.slice(lastNull + 1, i)); - } - lastNull = i; - } else if (i === pointsLength - 1) { // last value - segments.push(points.slice(lastNull + 1, i + 1)); - } - }); - } - } - - // register it - series.segments = segments; - }, - /** - * Set the series options by merging from the options tree - * @param {Object} itemOptions - */ - setOptions: function (itemOptions) { - var chart = this.chart, - chartOptions = chart.options, - plotOptions = chartOptions.plotOptions, - typeOptions = plotOptions[this.type], - data = itemOptions.data, - options; - - itemOptions.data = null; // remove from merge to prevent looping over the data set - - options = merge( - typeOptions, - plotOptions.series, - itemOptions - ); - - // Re-insert the data array to the options and the original config (#717) - options.data = itemOptions.data = data; - - // the tooltip options are merged between global and series specific options - this.tooltipOptions = merge(chartOptions.tooltip, options.tooltip); - - // Delte marker object if not allowed (#1125) - if (typeOptions.marker === null) { - delete options.marker; - } - - return options; - - }, - /** - * Get the series' color - */ - getColor: function () { - var options = this.options, - defaultColors = this.chart.options.colors, - counters = this.chart.counters; - this.color = options.color || - (!options.colorByPoint && defaultColors[counters.color++]) || 'gray'; - counters.wrapColor(defaultColors.length); - }, - /** - * Get the series' symbol - */ - getSymbol: function () { - var series = this, - seriesMarkerOption = series.options.marker, - chart = series.chart, - defaultSymbols = chart.options.symbols, - counters = chart.counters; - series.symbol = seriesMarkerOption.symbol || defaultSymbols[counters.symbol++]; - - // don't substract radius in image symbols (#604) - if (/^url/.test(series.symbol)) { - seriesMarkerOption.radius = 0; - } - counters.wrapSymbol(defaultSymbols.length); - }, - - /** - * Get the series' symbol in the legend. This method should be overridable to create custom - * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols. - * - * @param {Object} legend The legend object - */ - drawLegendSymbol: function (legend) { - - var options = this.options, - markerOptions = options.marker, - radius, - legendOptions = legend.options, - legendSymbol, - symbolWidth = legendOptions.symbolWidth, - renderer = this.chart.renderer, - legendItemGroup = this.legendGroup, - baseline = legend.baseline, - attr; - - // Draw the line - if (options.lineWidth) { - attr = { - 'stroke-width': options.lineWidth - }; - if (options.dashStyle) { - attr.dashstyle = options.dashStyle; - } - this.legendLine = renderer.path([ - M, - 0, - baseline - 4, - L, - symbolWidth, - baseline - 4 - ]) - .attr(attr) - .add(legendItemGroup); - } - - // Draw the marker - if (markerOptions && markerOptions.enabled) { - radius = markerOptions.radius; - this.legendSymbol = legendSymbol = renderer.symbol( - this.symbol, - (symbolWidth / 2) - radius, - baseline - 4 - radius, - 2 * radius, - 2 * radius - ) - .add(legendItemGroup); - } - }, - - /** - * Add a point dynamically after chart load time - * @param {Object} options Point options as given in series.data - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean} shift If shift is true, a point is shifted off the start - * of the series as one is appended to the end. - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - addPoint: function (options, redraw, shift, animation) { - var series = this, - data = series.data, - graph = series.graph, - area = series.area, - chart = series.chart, - xData = series.xData, - yData = series.yData, - currentShift = (graph && graph.shift) || 0, - dataOptions = series.options.data, - point, - proto = series.pointClass.prototype; - - setAnimation(animation, chart); - - // Make graph animate sideways - if (graph && shift) { - graph.shift = currentShift + 1; - } - if (area) { - if (shift) { // #780 - area.shift = currentShift + 1; - } - area.isArea = true; // needed in animation, both with and without shift - } - - // Optional redraw, defaults to true - redraw = pick(redraw, true); - - // Get options and push the point to xData, yData and series.options. In series.generatePoints - // the Point instance will be created on demand and pushed to the series.data array. - point = { series: series }; - proto.applyOptions.apply(point, [options]); - xData.push(point.x); - yData.push(proto.toYData ? proto.toYData.call(point) : point.y); - dataOptions.push(options); - - - // Shift the first point off the parallel arrays - // todo: consider series.removePoint(i) method - if (shift) { - if (data[0] && data[0].remove) { - data[0].remove(false); - } else { - data.shift(); - xData.shift(); - yData.shift(); - dataOptions.shift(); - } - } - series.getAttribs(); - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - chart.redraw(); - } - }, - - /** - * Replace the series data with a new set of data - * @param {Object} data - * @param {Object} redraw - */ - setData: function (data, redraw) { - var series = this, - oldData = series.points, - options = series.options, - initialColor = series.initialColor, - chart = series.chart, - firstPoint = null, - xAxis = series.xAxis, - i, - pointProto = series.pointClass.prototype; - - // reset properties - series.xIncrement = null; - series.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange; - - if (defined(initialColor)) { // reset colors for pie - chart.counters.color = initialColor; - } - - // parallel arrays - var xData = [], - yData = [], - dataLength = data ? data.length : [], - turboThreshold = options.turboThreshold || 1000, - pt, - pointArrayMap = series.pointArrayMap, - valueCount = pointArrayMap && pointArrayMap.length; - - // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The - // first value is tested, and we assume that all the rest are defined the same - // way. Although the 'for' loops are similar, they are repeated inside each - // if-else conditional for max performance. - if (dataLength > turboThreshold) { - - // find the first non-null point - i = 0; - while (firstPoint === null && i < dataLength) { - firstPoint = data[i]; - i++; - } - - - if (isNumber(firstPoint)) { // assume all points are numbers - var x = pick(options.pointStart, 0), - pointInterval = pick(options.pointInterval, 1); - - for (i = 0; i < dataLength; i++) { - xData[i] = x; - yData[i] = data[i]; - x += pointInterval; - } - series.xIncrement = x; - } else if (isArray(firstPoint)) { // assume all points are arrays - if (valueCount) { // [x, low, high] or [x, o, h, l, c] - for (i = 0; i < dataLength; i++) { - pt = data[i]; - xData[i] = pt[0]; - yData[i] = pt.slice(1, valueCount + 1); - } - } else { // [x, y] - for (i = 0; i < dataLength; i++) { - pt = data[i]; - xData[i] = pt[0]; - yData[i] = pt[1]; - } - } - } /* else { - error(12); // Highcharts expects configs to be numbers or arrays in turbo mode - }*/ - } else { - for (i = 0; i < dataLength; i++) { - pt = { series: series }; - pointProto.applyOptions.apply(pt, [data[i]]); - xData[i] = pt.x; - yData[i] = pointProto.toYData ? pointProto.toYData.call(pt) : pt.y; - } - } - - // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON - if (isString(yData[0])) { - error(14, true); - } - - series.data = []; - series.options.data = data; - series.xData = xData; - series.yData = yData; - - // destroy old points - i = (oldData && oldData.length) || 0; - while (i--) { - if (oldData[i] && oldData[i].destroy) { - oldData[i].destroy(); - } - } - - // reset minRange (#878) - if (xAxis) { - xAxis.minRange = xAxis.userMinRange; - } - - // redraw - series.isDirty = series.isDirtyData = chart.isDirtyBox = true; - if (pick(redraw, true)) { - chart.redraw(false); - } - }, - - /** - * Remove a series and optionally redraw the chart - * - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - - remove: function (redraw, animation) { - var series = this, - chart = series.chart; - redraw = pick(redraw, true); - - if (!series.isRemoving) { /* prevent triggering native event in jQuery - (calling the remove function from the remove event) */ - series.isRemoving = true; - - // fire the event with a default handler of removing the point - fireEvent(series, 'remove', null, function () { - - - // destroy elements - series.destroy(); - - - // redraw - chart.isDirtyLegend = chart.isDirtyBox = true; - if (redraw) { - chart.redraw(animation); - } - }); - - } - series.isRemoving = false; - }, - - /** - * Process the data by cropping away unused data points if the series is longer - * than the crop threshold. This saves computing time for lage series. - */ - processData: function (force) { - var series = this, - processedXData = series.xData, // copied during slice operation below - processedYData = series.yData, - dataLength = processedXData.length, - cropStart = 0, - cropEnd = dataLength, - cropped, - distance, - closestPointRange, - xAxis = series.xAxis, - i, // loop variable - options = series.options, - cropThreshold = options.cropThreshold, - isCartesian = series.isCartesian; - - // If the series data or axes haven't changed, don't go through this. Return false to pass - // the message on to override methods like in data grouping. - if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) { - return false; - } - - // optionally filter out points outside the plot area - if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) { - var extremes = xAxis.getExtremes(), - min = extremes.min, - max = extremes.max; - - // it's outside current extremes - if (processedXData[dataLength - 1] < min || processedXData[0] > max) { - processedXData = []; - processedYData = []; - - // only crop if it's actually spilling out - } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) { - - // iterate up to find slice start - for (i = 0; i < dataLength; i++) { - if (processedXData[i] >= min) { - cropStart = mathMax(0, i - 1); - break; - } - } - // proceed to find slice end - for (; i < dataLength; i++) { - if (processedXData[i] > max) { - cropEnd = i + 1; - break; - } - - } - processedXData = processedXData.slice(cropStart, cropEnd); - processedYData = processedYData.slice(cropStart, cropEnd); - cropped = true; - } - } - - - // Find the closest distance between processed points - for (i = processedXData.length - 1; i > 0; i--) { - distance = processedXData[i] - processedXData[i - 1]; - if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) { - closestPointRange = distance; - } - } - - // Record the properties - series.cropped = cropped; // undefined or true - series.cropStart = cropStart; - series.processedXData = processedXData; - series.processedYData = processedYData; - - if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC - series.pointRange = closestPointRange || 1; - } - series.closestPointRange = closestPointRange; - - }, - - /** - * Generate the data point after the data has been processed by cropping away - * unused points and optionally grouped in Highcharts Stock. - */ - generatePoints: function () { - var series = this, - options = series.options, - dataOptions = options.data, - data = series.data, - dataLength, - processedXData = series.processedXData, - processedYData = series.processedYData, - pointClass = series.pointClass, - processedDataLength = processedXData.length, - cropStart = series.cropStart || 0, - cursor, - hasGroupedData = series.hasGroupedData, - point, - points = [], - i; - - if (!data && !hasGroupedData) { - var arr = []; - arr.length = dataOptions.length; - data = series.data = arr; - } - - for (i = 0; i < processedDataLength; i++) { - cursor = cropStart + i; - if (!hasGroupedData) { - if (data[cursor]) { - point = data[cursor]; - } else if (dataOptions[cursor] !== UNDEFINED) { // #970 - data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]); - } - points[i] = point; - } else { - // splat the y data in case of ohlc data array - points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i]))); - } - } - - // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when - // swithching view from non-grouped data to grouped data (#637) - if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) { - for (i = 0; i < dataLength; i++) { - if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points - i += processedDataLength; - } - if (data[i]) { - data[i].destroyElements(); - data[i].plotX = UNDEFINED; // #1003 - } - } - } - - series.data = data; - series.points = points; - }, - - /** - * Translate data points from raw data values to chart specific positioning data - * needed later in drawPoints, drawGraph and drawTracker. - */ - translate: function () { - if (!this.processedXData) { // hidden series - this.processData(); - } - this.generatePoints(); - var series = this, - chart = series.chart, - options = series.options, - stacking = options.stacking, - xAxis = series.xAxis, - categories = xAxis.categories, - yAxis = series.yAxis, - points = series.points, - dataLength = points.length, - hasModifyValue = !!series.modifyValue, - isBottomSeries, - allStackSeries = yAxis.series, - i = allStackSeries.length, - placeBetween = options.pointPlacement === 'between'; - //nextSeriesDown; - - // Is it the last visible series? - while (i--) { - if (allStackSeries[i].visible) { - if (allStackSeries[i] === series) { // #809 - isBottomSeries = true; - } - break; - } - } - - // Translate each point - for (i = 0; i < dataLength; i++) { - var point = points[i], - xValue = point.x, - yValue = point.y, - yBottom = point.low, - stack = yAxis.stacks[(yValue < options.threshold ? '-' : '') + series.stackKey], - pointStack, - pointStackTotal; - - // get the plotX translation - //point.plotX = mathRound(xAxis.translate(xValue, 0, 0, 0, 1) * 10) / 10; // Math.round fixes #591 - point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, placeBetween); // Math.round fixes #591 - - // calculate the bottom y value for stacked series - if (stacking && series.visible && stack && stack[xValue]) { - pointStack = stack[xValue]; - pointStackTotal = pointStack.total; - pointStack.cum = yBottom = pointStack.cum - yValue; // start from top - yValue = yBottom + yValue; - - if (isBottomSeries) { - yBottom = pick(options.threshold, yAxis.min); - } - - if (yAxis.isLog && yBottom <= 0) { // #1200, #1232 - yBottom = null; - } - - if (stacking === 'percent') { - yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0; - yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0; - } - - point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0; - point.total = point.stackTotal = pointStackTotal; - point.stackY = yValue; - } - - // Set translated yBottom or remove it - point.yBottom = defined(yBottom) ? - yAxis.translate(yBottom, 0, 1, 0, 1) : - null; - - // general hook, used for Highstock compare mode - if (hasModifyValue) { - yValue = series.modifyValue(yValue, point); - } - - // Set the the plotY value, reset it for redraws - point.plotY = (typeof yValue === 'number') ? - mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591 - UNDEFINED; - - // set client related positions for mouse tracking - point.clientX = chart.inverted ? - chart.plotHeight - point.plotX : - point.plotX; // for mouse tracking - - // some API data - point.category = categories && categories[point.x] !== UNDEFINED ? - categories[point.x] : point.x; - - - } - - // now that we have the cropped data, build the segments - series.getSegments(); - }, - /** - * Memoize tooltip texts and positions - */ - setTooltipPoints: function (renew) { - var series = this, - points = [], - pointsLength, - low, - high, - xAxis = series.xAxis, - axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar - plotX = (xAxis && xAxis.tooltipPosName) || 'plotX', - point, - i, - tooltipPoints = []; // a lookup array for each pixel in the x dimension - - // don't waste resources if tracker is disabled - if (series.options.enableMouseTracking === false) { - return; - } - - // renew - if (renew) { - series.tooltipPoints = null; - } - - // concat segments to overcome null values - each(series.segments || series.points, function (segment) { - points = points.concat(segment); - }); - - // Reverse the points in case the X axis is reversed - if (xAxis && xAxis.reversed) { - points = points.reverse(); - } - - // Assign each pixel position to the nearest point - pointsLength = points.length; - for (i = 0; i < pointsLength; i++) { - point = points[i]; - // Set this range's low to the last range's high plus one - low = points[i - 1] ? high + 1 : 0; - // Now find the new high - high = points[i + 1] ? - mathMax(0, mathFloor((point[plotX] + (points[i + 1] ? points[i + 1][plotX] : axisLength)) / 2)) : - axisLength; - - while (low >= 0 && low <= high) { - tooltipPoints[low++] = point; - } - } - series.tooltipPoints = tooltipPoints; - }, - - /** - * Format the header of the tooltip - */ - tooltipHeaderFormatter: function (key) { - var series = this, - tooltipOptions = series.tooltipOptions, - xDateFormat = tooltipOptions.xDateFormat, - xAxis = series.xAxis, - isDateTime = xAxis && xAxis.options.type === 'datetime', - n; - - // Guess the best date format based on the closest point distance (#568) - if (isDateTime && !xDateFormat) { - for (n in timeUnits) { - if (timeUnits[n] >= xAxis.closestPointRange) { - xDateFormat = tooltipOptions.dateTimeLabelFormats[n]; - break; - } - } - } - - return tooltipOptions.headerFormat - .replace('{point.key}', isDateTime && isNumber(key) ? dateFormat(xDateFormat, key) : key) - .replace('{series.name}', series.name) - .replace('{series.color}', series.color); - }, - - /** - * Series mouse over handler - */ - onMouseOver: function () { - var series = this, - chart = series.chart, - hoverSeries = chart.hoverSeries; - - /*if (!hasTouch && chart.mouseIsDown) { - return; - }*/ - - // set normal state to previous series - if (hoverSeries && hoverSeries !== series) { - hoverSeries.onMouseOut(); - } - - // trigger the event, but to save processing time, - // only if defined - if (series.options.events.mouseOver) { - fireEvent(series, 'mouseOver'); - } - - // hover this - series.setState(HOVER_STATE); - chart.hoverSeries = series; - }, - - /** - * Series mouse out handler - */ - onMouseOut: function () { - // trigger the event only if listeners exist - var series = this, - options = series.options, - chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - - // trigger mouse out on the point, which must be in this series - if (hoverPoint) { - hoverPoint.onMouseOut(); - } - - // fire the mouse out event - if (series && options.events.mouseOut) { - fireEvent(series, 'mouseOut'); - } - - - // hide the tooltip - if (tooltip && !options.stickyTracking && !tooltip.shared) { - tooltip.hide(); - } - - // set normal state - series.setState(); - chart.hoverSeries = null; - }, - - /** - * Animate in the series - */ - animate: function (init) { - var series = this, - chart = series.chart, - renderer = chart.renderer, - clipRect, - markerClipRect, - animation = series.options.animation, - clipBox = chart.clipBox, - inverted = chart.inverted, - sharedClipKey; - - // Animation option is set to true - if (animation && !isObject(animation)) { - animation = defaultPlotOptions[series.type].animation; - } - sharedClipKey = '_sharedClip' + animation.duration + animation.easing; - - // Initialize the animation. Set up the clipping rectangle. - if (init) { - - // If a clipping rectangle with the same properties is currently present in the chart, use that. - clipRect = chart[sharedClipKey]; - markerClipRect = chart[sharedClipKey + 'm']; - if (!clipRect) { - chart[sharedClipKey] = clipRect = renderer.clipRect( - extend(clipBox, { width: 0 }) - ); - - chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect( - -99, // include the width of the first marker - inverted ? -chart.plotLeft : -chart.plotTop, - 99, - inverted ? chart.chartWidth : chart.chartHeight - ); - } - series.group.clip(clipRect); - series.markerGroup.clip(markerClipRect); - series.sharedClipKey = sharedClipKey; - - // Run the animation - } else { - clipRect = chart[sharedClipKey]; - if (clipRect) { - clipRect.animate({ - width: chart.plotSizeX - }, animation); - chart[sharedClipKey + 'm'].animate({ - width: chart.plotSizeX + 99 - }, animation); - } - - // Delete this function to allow it only once - series.animate = null; - - // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option - // which should be available to the user). - series.animationTimeout = setTimeout(function () { - series.afterAnimate(); - }, animation.duration); - } - }, - - /** - * This runs after animation to land on the final plot clipping - */ - afterAnimate: function () { - var chart = this.chart, - sharedClipKey = this.sharedClipKey, - group = this.group; - - if (group && this.options.clip !== false) { - group.clip(chart.clipRect); - this.markerGroup.clip(); // no clip - } - - // Remove the shared clipping rectancgle when all series are shown - setTimeout(function () { - if (sharedClipKey && chart[sharedClipKey]) { - chart[sharedClipKey] = chart[sharedClipKey].destroy(); - chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy(); - } - }, 100); - }, - - /** - * Draw the markers - */ - drawPoints: function () { - var series = this, - pointAttr, - points = series.points, - chart = series.chart, - plotX, - plotY, - i, - point, - radius, - symbol, - isImage, - graphic, - options = series.options, - seriesMarkerOptions = options.marker, - pointMarkerOptions, - enabled, - isInside, - markerGroup = series.markerGroup; - - if (seriesMarkerOptions.enabled || series._hasPointMarkers) { - - i = points.length; - while (i--) { - point = points[i]; - plotX = point.plotX; - plotY = point.plotY; - graphic = point.graphic; - pointMarkerOptions = point.marker || {}; - enabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled; - isInside = chart.isInsidePlot(plotX, plotY, chart.inverted); - - // only draw the point if y is defined - if (enabled && plotY !== UNDEFINED && !isNaN(plotY)) { - - // shortcuts - pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]; - radius = pointAttr.r; - symbol = pick(pointMarkerOptions.symbol, series.symbol); - isImage = symbol.indexOf('url') === 0; - - if (graphic) { // update - graphic - .attr({ // Since the marker group isn't clipped, each individual marker must be toggled - visibility: isInside ? (hasSVG ? 'inherit' : VISIBLE) : HIDDEN - }) - .animate(extend({ - x: plotX - radius, - y: plotY - radius - }, graphic.symbolName ? { // don't apply to image symbols #507 - width: 2 * radius, - height: 2 * radius - } : {})); - } else if (isInside && (radius > 0 || isImage)) { - point.graphic = graphic = chart.renderer.symbol( - symbol, - plotX - radius, - plotY - radius, - 2 * radius, - 2 * radius - ) - .attr(pointAttr) - .add(markerGroup); - } - - } else if (graphic) { - point.graphic = graphic.destroy(); // #1269 - } - } - } - - }, - - /** - * Convert state properties from API naming conventions to SVG attributes - * - * @param {Object} options API options object - * @param {Object} base1 SVG attribute object to inherit from - * @param {Object} base2 Second level SVG attribute object to inherit from - */ - convertAttribs: function (options, base1, base2, base3) { - var conversion = this.pointAttrToOptions, - attr, - option, - obj = {}; - - options = options || {}; - base1 = base1 || {}; - base2 = base2 || {}; - base3 = base3 || {}; - - for (attr in conversion) { - option = conversion[attr]; - obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]); - } - return obj; - }, - - /** - * Get the state attributes. Each series type has its own set of attributes - * that are allowed to change on a point's state change. Series wide attributes are stored for - * all series, and additionally point specific attributes are stored for all - * points with individual marker options. If such options are not defined for the point, - * a reference to the series wide attributes is stored in point.pointAttr. - */ - getAttribs: function () { - var series = this, - normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options, - stateOptions = normalOptions.states, - stateOptionsHover = stateOptions[HOVER_STATE], - pointStateOptionsHover, - seriesColor = series.color, - normalDefaults = { - stroke: seriesColor, - fill: seriesColor - }, - points = series.points || [], // #927 - i, - point, - seriesPointAttr = [], - pointAttr, - pointAttrToOptions = series.pointAttrToOptions, - hasPointSpecificOptions, - key; - - // series type specific modifications - if (series.options.marker) { // line, spline, area, areaspline, scatter - - // if no hover radius is given, default to normal radius + 2 - stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2; - stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1; - - } else { // column, bar, pie - - // if no hover color is given, brighten the normal color - stateOptionsHover.color = stateOptionsHover.color || - Color(stateOptionsHover.color || seriesColor) - .brighten(stateOptionsHover.brightness).get(); - } - - // general point attributes for the series normal state - seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults); - - // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius - each([HOVER_STATE, SELECT_STATE], function (state) { - seriesPointAttr[state] = - series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]); - }); - - // set it - series.pointAttr = seriesPointAttr; - - - // Generate the point-specific attribute collections if specific point - // options are given. If not, create a referance to the series wide point - // attributes - i = points.length; - while (i--) { - point = points[i]; - normalOptions = (point.options && point.options.marker) || point.options; - if (normalOptions && normalOptions.enabled === false) { - normalOptions.radius = 0; - } - hasPointSpecificOptions = series.options.colorByPoint; // #868 - - // check if the point has specific visual options - if (point.options) { - for (key in pointAttrToOptions) { - if (defined(normalOptions[pointAttrToOptions[key]])) { - hasPointSpecificOptions = true; - } - } - } - - - - // a specific marker config object is defined for the individual point: - // create it's own attribute collection - if (hasPointSpecificOptions) { - normalOptions = normalOptions || {}; - pointAttr = []; - stateOptions = normalOptions.states || {}; // reassign for individual point - pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {}; - - // Handle colors for column and pies - if (!series.options.marker) { // column, bar, point - // if no hover color is given, brighten the normal color - pointStateOptionsHover.color = - Color(pointStateOptionsHover.color || point.color) - .brighten(pointStateOptionsHover.brightness || - stateOptionsHover.brightness).get(); - - } - - // normal point state inherits series wide normal state - pointAttr[NORMAL_STATE] = series.convertAttribs(extend({ - color: point.color // #868 - }, normalOptions), seriesPointAttr[NORMAL_STATE]); - - // inherit from point normal and series hover - pointAttr[HOVER_STATE] = series.convertAttribs( - stateOptions[HOVER_STATE], - seriesPointAttr[HOVER_STATE], - pointAttr[NORMAL_STATE] - ); - // inherit from point normal and series hover - pointAttr[SELECT_STATE] = series.convertAttribs( - stateOptions[SELECT_STATE], - seriesPointAttr[SELECT_STATE], - pointAttr[NORMAL_STATE] - ); - - - - // no marker config object is created: copy a reference to the series-wide - // attribute collection - } else { - pointAttr = seriesPointAttr; - } - - point.pointAttr = pointAttr; - - } - - }, - - - /** - * Clear DOM objects and free up memory - */ - destroy: function () { - var series = this, - chart = series.chart, - issue134 = /AppleWebKit\/533/.test(userAgent), - destroy, - i, - data = series.data || [], - point, - prop, - axis; - - // add event hook - fireEvent(series, 'destroy'); - - // remove all events - removeEvent(series); - - // erase from axes - each(['xAxis', 'yAxis'], function (AXIS) { - axis = series[AXIS]; - if (axis) { - erase(axis.series, series); - axis.isDirty = true; - } - }); - - // remove legend items - if (series.legendItem) { - series.chart.legend.destroyItem(series); - } - - // destroy all points with their elements - i = data.length; - while (i--) { - point = data[i]; - if (point && point.destroy) { - point.destroy(); - } - } - series.points = null; - - // Clear the animation timeout if we are destroying the series during initial animation - clearTimeout(series.animationTimeout); - - // destroy all SVGElements associated to the series - each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker', 'trackerGroup'], function (prop) { - if (series[prop]) { - - // issue 134 workaround - destroy = issue134 && prop === 'group' ? - 'hide' : - 'destroy'; - - series[prop][destroy](); - } - }); - - // remove from hoverSeries - if (chart.hoverSeries === series) { - chart.hoverSeries = null; - } - erase(chart.series, series); - - // clear all members - for (prop in series) { - delete series[prop]; - } - }, - - /** - * Draw the data labels - */ - drawDataLabels: function () { - - var series = this, - seriesOptions = series.options, - options = seriesOptions.dataLabels, - points = series.points, - pointOptions, - generalOptions, - str, - dataLabelsGroup; - - if (options.enabled || series._hasPointLabels) { - - // Process default alignment of data labels for columns - if (series.dlProcessOptions) { - series.dlProcessOptions(options); - } - - // Create a separate group for the data labels to avoid rotation - dataLabelsGroup = series.plotGroup( - 'dataLabelsGroup', - 'data-labels', - series.visible ? VISIBLE : HIDDEN, - 6 - ); - - // Make the labels for each point - generalOptions = options; - each(points, function (point) { - - var enabled, - dataLabel = point.dataLabel, - attr, - name, - rotation, - isNew = true; - - // Determine if each data label is enabled - pointOptions = point.options && point.options.dataLabels; - enabled = generalOptions.enabled || (pointOptions && pointOptions.enabled); - - - // If the point is outside the plot area, destroy it. #678, #820 - if (dataLabel && !enabled) { - point.dataLabel = dataLabel.destroy(); - - // Individual labels are disabled if the are explicitly disabled - // in the point options, or if they fall outside the plot area. - } else if (enabled) { - - rotation = options.rotation; - - // Create individual options structure that can be extended without - // affecting others - options = merge(generalOptions, pointOptions); - - // Get the string - str = options.formatter.call(point.getLabelConfig(), options); - - // Determine the color - options.style.color = pick(options.color, options.style.color, series.color, 'black'); - - - // update existing label - if (dataLabel) { - // vertically centered - dataLabel - .attr({ - text: str - }); - isNew = false; - // create new label - } else if (defined(str)) { - attr = { - //align: align, - fill: options.backgroundColor, - stroke: options.borderColor, - 'stroke-width': options.borderWidth, - r: options.borderRadius || 0, - rotation: rotation, - padding: options.padding, - zIndex: 1 - }; - // Remove unused attributes (#947) - for (name in attr) { - if (attr[name] === UNDEFINED) { - delete attr[name]; - } - } - - dataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation - str, - 0, - -999, - null, - null, - null, - options.useHTML - ) - .attr(attr) - .css(options.style) - .add(dataLabelsGroup) - .shadow(options.shadow); - - } - - // Now the data label is created and placed at 0,0, so we need to align it - if (dataLabel) { - series.alignDataLabel(point, dataLabel, options, null, isNew); - } - } - }); - } - }, - - /** - * Align each individual data label - */ - alignDataLabel: function (point, dataLabel, options, alignTo, isNew) { - var chart = this.chart, - inverted = chart.inverted, - plotX = pick(point.plotX, -999), - plotY = pick(point.plotY, -999), - bBox = dataLabel.getBBox(), - alignAttr; // the final position; - - // The alignment box is a singular point - alignTo = extend({ - x: inverted ? chart.plotWidth - plotY : plotX, - y: mathRound(inverted ? chart.plotHeight - plotX : plotY), - width: 0, - height: 0 - }, alignTo); - - // Add the text size for alignment calculation - extend(options, { - width: bBox.width, - height: bBox.height - }); - - // Allow a hook for changing alignment in the last moment, then do the alignment - if (options.rotation) { // Fancy box alignment isn't supported for rotated text - alignAttr = { - align: options.align, - x: alignTo.x + options.x + alignTo.width / 2, - y: alignTo.y + options.y + alignTo.height / 2 - }; - dataLabel[isNew ? 'attr' : 'animate'](alignAttr); - } else { - dataLabel.align(options, null, alignTo); - alignAttr = dataLabel.alignAttr; - } - - // Show or hide based on the final aligned position - dataLabel.attr({ - visibility: options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) || chart.isInsidePlot(plotX, plotY, inverted) ? - (hasSVG ? 'inherit' : VISIBLE) : - HIDDEN - }); - - }, - - /** - * Return the graph path of a segment - */ - getSegmentPath: function (segment) { - var series = this, - segmentPath = []; - - // build the segment line - each(segment, function (point, i) { - - if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object - segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i)); - - } else { - - // moveTo or lineTo - segmentPath.push(i ? L : M); - - // step line? - if (i && series.options.step) { - var lastPoint = segment[i - 1]; - segmentPath.push( - point.plotX, - lastPoint.plotY - ); - } - - // normal line to next point - segmentPath.push( - point.plotX, - point.plotY - ); - } - }); - - return segmentPath; - }, - - /** - * Get the graph path - */ - getGraphPath: function () { - var series = this, - graphPath = [], - segmentPath, - singlePoints = []; // used in drawTracker - - // Divide into segments and build graph and area paths - each(series.segments, function (segment) { - - segmentPath = series.getSegmentPath(segment); - - // add the segment to the graph, or a single point for tracking - if (segment.length > 1) { - graphPath = graphPath.concat(segmentPath); - } else { - singlePoints.push(segment[0]); - } - }); - - // Record it for use in drawGraph and drawTracker, and return graphPath - series.singlePoints = singlePoints; - series.graphPath = graphPath; - - return graphPath; - - }, - - /** - * Draw the actual graph - */ - drawGraph: function () { - var options = this.options, - graph = this.graph, - group = this.group, - color = options.lineColor || this.color, - lineWidth = options.lineWidth, - dashStyle = options.dashStyle, - attribs, - graphPath = this.getGraphPath(); - - - // draw the graph - if (graph) { - stop(graph); // cancel running animations, #459 - graph.animate({ d: graphPath }); - - } else { - if (lineWidth) { - attribs = { - stroke: color, - 'stroke-width': lineWidth, - zIndex: 1 // #1069 - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - - this.graph = this.chart.renderer.path(graphPath) - .attr(attribs).add(group).shadow(options.shadow); - } - } - }, - - /** - * Initialize and perform group inversion on series.group and series.trackerGroup - */ - invertGroups: function () { - var series = this, - chart = series.chart; - - // A fixed size is needed for inversion to work - function setInvert() { - var size = { - width: series.yAxis.len, - height: series.xAxis.len - }; - - each(['group', 'trackerGroup', 'markerGroup'], function (groupName) { - if (series[groupName]) { - series[groupName].attr(size).invert(); - } - }); - } - - addEvent(chart, 'resize', setInvert); // do it on resize - addEvent(series, 'destroy', function () { - removeEvent(chart, 'resize', setInvert); - }); - - // Do it now - setInvert(); // do it now - - // On subsequent render and redraw, just do setInvert without setting up events again - series.invertGroups = setInvert; - }, - - /** - * General abstraction for creating plot groups like series.group, series.trackerGroup, series.dataLabelsGroup and - * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size. - */ - plotGroup: function (prop, name, visibility, zIndex, parent) { - var group = this[prop], - chart = this.chart, - xAxis = this.xAxis, - yAxis = this.yAxis; - - // Generate it on first call - if (!group) { - this[prop] = group = chart.renderer.g(name) - .attr({ - visibility: visibility, - zIndex: zIndex || 0.1 // IE8 needs this - }) - .add(parent); - } - // Place it on first and subsequent (redraw) calls - group.translate( - xAxis ? xAxis.left : chart.plotLeft, - yAxis ? yAxis.top : chart.plotTop - ); - - return group; - - }, - - /** - * Render the graph and markers - */ - render: function () { - var series = this, - chart = series.chart, - group, - options = series.options, - animation = options.animation, - doAnimation = animation && !!series.animate, - visibility = series.visible ? VISIBLE : HIDDEN, - zIndex = options.zIndex, - hasRendered = series.hasRendered, - chartSeriesGroup = chart.seriesGroup; - - // the group - group = series.plotGroup( - 'group', - 'series', - visibility, - zIndex, - chartSeriesGroup - ); - - series.markerGroup = series.plotGroup( - 'markerGroup', - 'markers', - visibility, - zIndex, - chartSeriesGroup - ); - - // initiate the animation - if (doAnimation) { - series.animate(true); - } - - // cache attributes for shapes - series.getAttribs(); - - // SVGRenderer needs to know this before drawing elements (#1089) - group.inverted = chart.inverted; - - // draw the graph if any - if (series.drawGraph) { - series.drawGraph(); - } - - // draw the points - series.drawPoints(); - - // draw the data labels - series.drawDataLabels(); - - - // draw the mouse tracking area - if (series.options.enableMouseTracking !== false) { - series.drawTracker(); - } - - // Handle inverted series and tracker groups - if (chart.inverted) { - series.invertGroups(); - } - - // Initial clipping, must be defined after inverting groups for VML - if (options.clip !== false && !series.sharedClipKey && !hasRendered) { - group.clip(chart.clipRect); - if (this.trackerGroup) { - this.trackerGroup.clip(chart.clipRect); - } - } - - // Run the animation - if (doAnimation) { - series.animate(); - } else if (!hasRendered) { - series.afterAnimate(); - } - - series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - series.hasRendered = true; - }, - - /** - * Redraw the series after an update in the axes. - */ - redraw: function () { - var series = this, - chart = series.chart, - wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after - group = series.group; - - // reposition on resize - if (group) { - if (chart.inverted) { - group.attr({ - width: chart.plotWidth, - height: chart.plotHeight - }); - } - - group.animate({ - translateX: series.xAxis.left, - translateY: series.yAxis.top - }); - } - - series.translate(); - series.setTooltipPoints(true); - - series.render(); - if (wasDirtyData) { - fireEvent(series, 'updatedData'); - } - }, - - /** - * Set the state of the graph - */ - setState: function (state) { - var series = this, - options = series.options, - graph = series.graph, - stateOptions = options.states, - lineWidth = options.lineWidth; - - state = state || NORMAL_STATE; - - if (series.state !== state) { - series.state = state; - - if (stateOptions[state] && stateOptions[state].enabled === false) { - return; - } - - if (state) { - lineWidth = stateOptions[state].lineWidth || lineWidth + 1; - } - - if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML - graph.attr({ // use attr because animate will cause any other animation on the graph to stop - 'stroke-width': lineWidth - }, state ? 0 : 500); - } - } - }, - - /** - * Set the visibility of the graph - * - * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED, - * the visibility is toggled. - */ - setVisible: function (vis, redraw) { - var series = this, - chart = series.chart, - legendItem = series.legendItem, - seriesGroup = series.group, - seriesTracker = series.tracker, - dataLabelsGroup = series.dataLabelsGroup, - markerGroup = series.markerGroup, - showOrHide, - i, - points = series.points, - point, - ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, - oldVisibility = series.visible; - - // if called without an argument, toggle visibility - series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis; - showOrHide = vis ? 'show' : 'hide'; - - // show or hide series - if (seriesGroup) { // pies don't have one - seriesGroup[showOrHide](); - } - if (markerGroup) { - markerGroup[showOrHide](); - } - - // show or hide trackers - if (seriesTracker) { - seriesTracker[showOrHide](); - } else if (points) { - i = points.length; - while (i--) { - point = points[i]; - if (point.tracker) { - point.tracker[showOrHide](); - } - } - } - - - if (dataLabelsGroup) { - dataLabelsGroup[showOrHide](); - } - - if (legendItem) { - chart.legend.colorizeItem(series, vis); - } - - - // rescale or adapt to resized chart - series.isDirty = true; - // in a stack, all other series are affected - if (series.options.stacking) { - each(chart.series, function (otherSeries) { - if (otherSeries.options.stacking && otherSeries.visible) { - otherSeries.isDirty = true; - } - }); - } - - if (ignoreHiddenSeries) { - chart.isDirtyBox = true; - } - if (redraw !== false) { - chart.redraw(); - } - - fireEvent(series, showOrHide); - }, - - /** - * Show the graph - */ - show: function () { - this.setVisible(true); - }, - - /** - * Hide the graph - */ - hide: function () { - this.setVisible(false); - }, - - - /** - * Set the selected state of the graph - * - * @param selected {Boolean} True to select the series, false to unselect. If - * UNDEFINED, the selection state is toggled. - */ - select: function (selected) { - var series = this; - // if called without an argument, toggle - series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected; - - if (series.checkbox) { - series.checkbox.checked = selected; - } - - fireEvent(series, selected ? 'select' : 'unselect'); - }, - - /** - * Draw the tracker object that sits above all data labels and markers to - * track mouse events on the graph or points. For the line type charts - * the tracker uses the same graphPath, but with a greater stroke width - * for better control. - */ - drawTracker: function () { - var series = this, - options = series.options, - trackByArea = options.trackByArea, - trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath), - trackerPathLength = trackerPath.length, - chart = series.chart, - renderer = chart.renderer, - snap = chart.options.tooltip.snap, - tracker = series.tracker, - cursor = options.cursor, - css = cursor && { cursor: cursor }, - singlePoints = series.singlePoints, - trackerGroup = this.isCartesian && this.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup), - singlePoint, - i; - - // Extend end points. A better way would be to use round linecaps, - // but those are not clickable in VML. - if (trackerPathLength && !trackByArea) { - i = trackerPathLength + 1; - while (i--) { - if (trackerPath[i] === M) { // extend left side - trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L); - } - if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side - trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]); - } - } - } - - // handle single points - for (i = 0; i < singlePoints.length; i++) { - singlePoint = singlePoints[i]; - trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY, - L, singlePoint.plotX + snap, singlePoint.plotY); - } - - - - // draw the tracker - if (tracker) { - tracker.attr({ d: trackerPath }); - - } else { // create - - series.tracker = renderer.path(trackerPath) - .attr({ - isTracker: true, - 'stroke-linejoin': 'bevel', - visibility: series.visible ? VISIBLE : HIDDEN, - stroke: TRACKER_FILL, - fill: trackByArea ? TRACKER_FILL : NONE, - 'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap) - }) - .on(hasTouch ? 'touchstart' : 'mouseover', function () { - if (chart.hoverSeries !== series) { - series.onMouseOver(); - } - }) - .on('mouseout', function () { - if (!options.stickyTracking) { - series.onMouseOut(); - } - }) - .css(css) - .add(trackerGroup); - } - - } - -}; // end Series prototype - - -/** - * LineSeries object - */ -var LineSeries = extendClass(Series); -seriesTypes.line = LineSeries; - -/** - * Set the default options for area - */ -defaultPlotOptions.area = merge(defaultSeriesOptions, { - threshold: 0 - // trackByArea: false, - // lineColor: null, // overrides color, but lets fillColor be unaltered - // fillOpacity: 0.75, - // fillColor: null -}); - -/** - * AreaSeries object - */ -var AreaSeries = extendClass(Series, { - type: 'area', - - /** - * Extend the base Series getSegmentPath method by adding the path for the area. - * This path is pushed to the series.areaPath property. - */ - getSegmentPath: function (segment) { - - var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method - areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path - i, - options = this.options, - segLength = segmentPath.length; - - if (segLength === 3) { // for animation from 1 to two points - areaSegmentPath.push(L, segmentPath[1], segmentPath[2]); - } - if (options.stacking && !this.closedStacks) { - - // Follow stack back. Todo: implement areaspline. A general solution could be to - // reverse the entire graphPath of the previous series, though may be hard with - // splines and with series with different extremes - for (i = segment.length - 1; i >= 0; i--) { - - // step line? - if (i < segment.length - 1 && options.step) { - areaSegmentPath.push(segment[i + 1].plotX, segment[i].yBottom); - } - - areaSegmentPath.push(segment[i].plotX, segment[i].yBottom); - } - - } else { // follow zero line back - this.closeSegment(areaSegmentPath, segment); - } - this.areaPath = this.areaPath.concat(areaSegmentPath); - - return segmentPath; - }, - - /** - * Extendable method to close the segment path of an area. This is overridden in polar - * charts. - */ - closeSegment: function (path, segment) { - var translatedThreshold = this.yAxis.getThreshold(this.options.threshold); - path.push( - L, - segment[segment.length - 1].plotX, - translatedThreshold, - L, - segment[0].plotX, - translatedThreshold - ); - }, - - /** - * Draw the graph and the underlying area. This method calls the Series base - * function and adds the area. The areaPath is calculated in the getSegmentPath - * method called from Series.prototype.drawGraph. - */ - drawGraph: function () { - - // Define or reset areaPath - this.areaPath = []; - - // Call the base method - Series.prototype.drawGraph.apply(this); - - // Define local variables - var areaPath = this.areaPath, - options = this.options, - area = this.area; - - // Create or update the area - if (area) { // update - area.animate({ d: areaPath }); - - } else { // create - this.area = this.chart.renderer.path(areaPath) - .attr({ - fill: pick( - options.fillColor, - Color(this.color).setOpacity(options.fillOpacity || 0.75).get() - ), - zIndex: 0 // #1069 - }).add(this.group); - } - }, - - /** - * Get the series' symbol in the legend - * - * @param {Object} legend The legend object - * @param {Object} item The series (this) or point - */ - drawLegendSymbol: function (legend, item) { - - item.legendSymbol = this.chart.renderer.rect( - 0, - legend.baseline - 11, - legend.options.symbolWidth, - 12, - 2 - ).attr({ - zIndex: 3 - }).add(item.legendGroup); - - } -}); - -seriesTypes.area = AreaSeries;/** - * Set the default options for spline - */ -defaultPlotOptions.spline = merge(defaultSeriesOptions); - -/** - * SplineSeries object - */ -var SplineSeries = extendClass(Series, { - type: 'spline', - - /** - * Get the spline segment from a given point's previous neighbour to the given point - */ - getPointSpline: function (segment, point, i) { - var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc - denom = smoothing + 1, - plotX = point.plotX, - plotY = point.plotY, - lastPoint = segment[i - 1], - nextPoint = segment[i + 1], - leftContX, - leftContY, - rightContX, - rightContY, - ret; - - // find control points - if (lastPoint && nextPoint) { - - var lastX = lastPoint.plotX, - lastY = lastPoint.plotY, - nextX = nextPoint.plotX, - nextY = nextPoint.plotY, - correction; - - leftContX = (smoothing * plotX + lastX) / denom; - leftContY = (smoothing * plotY + lastY) / denom; - rightContX = (smoothing * plotX + nextX) / denom; - rightContY = (smoothing * plotY + nextY) / denom; - - // have the two control points make a straight line through main point - correction = ((rightContY - leftContY) * (rightContX - plotX)) / - (rightContX - leftContX) + plotY - rightContY; - - leftContY += correction; - rightContY += correction; - - // to prevent false extremes, check that control points are between - // neighbouring points' y values - if (leftContY > lastY && leftContY > plotY) { - leftContY = mathMax(lastY, plotY); - rightContY = 2 * plotY - leftContY; // mirror of left control point - } else if (leftContY < lastY && leftContY < plotY) { - leftContY = mathMin(lastY, plotY); - rightContY = 2 * plotY - leftContY; - } - if (rightContY > nextY && rightContY > plotY) { - rightContY = mathMax(nextY, plotY); - leftContY = 2 * plotY - rightContY; - } else if (rightContY < nextY && rightContY < plotY) { - rightContY = mathMin(nextY, plotY); - leftContY = 2 * plotY - rightContY; - } - - // record for drawing in next point - point.rightContX = rightContX; - point.rightContY = rightContY; - - } - - // Visualize control points for debugging - /* - if (leftContX) { - this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2) - .attr({ - stroke: 'red', - 'stroke-width': 1, - fill: 'none' - }) - .add(); - this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, - 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) - .attr({ - stroke: 'red', - 'stroke-width': 1 - }) - .add(); - this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2) - .attr({ - stroke: 'green', - 'stroke-width': 1, - fill: 'none' - }) - .add(); - this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, - 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) - .attr({ - stroke: 'green', - 'stroke-width': 1 - }) - .add(); - } - // */ - - // moveTo or lineTo - if (!i) { - ret = [M, plotX, plotY]; - } else { // curve from last point to this - ret = [ - 'C', - lastPoint.rightContX || lastPoint.plotX, - lastPoint.rightContY || lastPoint.plotY, - leftContX || plotX, - leftContY || plotY, - plotX, - plotY - ]; - lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later - } - return ret; - } -}); -seriesTypes.spline = SplineSeries; - -/** - * Set the default options for areaspline - */ -defaultPlotOptions.areaspline = merge(defaultPlotOptions.area); - -/** - * AreaSplineSeries object - */ -var areaProto = AreaSeries.prototype, - AreaSplineSeries = extendClass(SplineSeries, { - type: 'areaspline', - closedStacks: true, // instead of following the previous graph back, follow the threshold back - - // Mix in methods from the area series - getSegmentPath: areaProto.getSegmentPath, - closeSegment: areaProto.closeSegment, - drawGraph: areaProto.drawGraph - }); -seriesTypes.areaspline = AreaSplineSeries; - -/** - * Set the default options for column - */ -defaultPlotOptions.column = merge(defaultSeriesOptions, { - borderColor: '#FFFFFF', - borderWidth: 1, - borderRadius: 0, - //colorByPoint: undefined, - groupPadding: 0.2, - //grouping: true, - marker: null, // point options are specified in the base options - pointPadding: 0.1, - //pointWidth: null, - minPointLength: 0, - cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes - pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories - states: { - hover: { - brightness: 0.1, - shadow: false - }, - select: { - color: '#C0C0C0', - borderColor: '#000000', - shadow: false - } - }, - dataLabels: { - align: null, // auto - verticalAlign: null, // auto - y: null - }, - threshold: 0 -}); - -/** - * ColumnSeries object - */ -var ColumnSeries = extendClass(Series, { - type: 'column', - tooltipOutsidePlot: true, - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'borderColor', - 'stroke-width': 'borderWidth', - fill: 'color', - r: 'borderRadius' - }, - - /** - * Initialize the series - */ - init: function () { - Series.prototype.init.apply(this, arguments); - - var series = this, - chart = series.chart; - - // if the series is added dynamically, force redraw of other - // series affected by a new column - if (chart.hasRendered) { - each(chart.series, function (otherSeries) { - if (otherSeries.type === series.type) { - otherSeries.isDirty = true; - } - }); - } - }, - - /** - * Translate each point to the plot area coordinate system and find shape positions - */ - translate: function () { - var series = this, - chart = series.chart, - options = series.options, - stacking = options.stacking, - borderWidth = options.borderWidth, - columnCount = 0, - xAxis = series.xAxis, - reversedXAxis = xAxis.reversed, - stackGroups = {}, - stackKey, - columnIndex; - - Series.prototype.translate.apply(series); - - // Get the total number of column type series. - // This is called on every series. Consider moving this logic to a - // chart.orderStacks() function and call it on init, addSeries and removeSeries - if (options.grouping === false) { - columnCount = 1; - - } else { - each(chart.series, function (otherSeries) { - var otherOptions = otherSeries.options; - if (otherSeries.type === series.type && otherSeries.visible && - series.options.group === otherOptions.group) { // used in Stock charts navigator series - if (otherOptions.stacking) { - stackKey = otherSeries.stackKey; - if (stackGroups[stackKey] === UNDEFINED) { - stackGroups[stackKey] = columnCount++; - } - columnIndex = stackGroups[stackKey]; - } else if (otherOptions.grouping !== false) { // #1162 - columnIndex = columnCount++; - } - otherSeries.columnIndex = columnIndex; - } - }); - } - - // calculate the width and position of each column based on - // the number of column series in the plot, the groupPadding - // and the pointPadding options - var points = series.points, - categoryWidth = mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1), - groupPadding = categoryWidth * options.groupPadding, - groupWidth = categoryWidth - 2 * groupPadding, - pointOffsetWidth = groupWidth / columnCount, - optionPointWidth = options.pointWidth, - pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 : - pointOffsetWidth * options.pointPadding, - pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts - barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width - colIndex = (reversedXAxis ? columnCount - - series.columnIndex : series.columnIndex) || 0, - pointXOffset = pointPadding + (groupPadding + colIndex * - pointOffsetWidth - (categoryWidth / 2)) * - (reversedXAxis ? -1 : 1), - threshold = options.threshold, - translatedThreshold = series.translatedThreshold = series.yAxis.getThreshold(threshold), - minPointLength = pick(options.minPointLength, 5); - - // record the new values - each(points, function (point) { - var plotY = point.plotY, - yBottom = pick(point.yBottom, translatedThreshold), - barX = point.plotX + pointXOffset, - barY = mathCeil(mathMin(plotY, yBottom)), - barH = mathCeil(mathMax(plotY, yBottom) - barY), - stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey], - shapeArgs; - - // Record the offset'ed position and width of the bar to be able to align the stacking total correctly - if (stacking && series.visible && stack && stack[point.x]) { - stack[point.x].setOffset(pointXOffset, barW); - } - - // handle options.minPointLength - if (mathAbs(barH) < minPointLength) { - if (minPointLength) { - barH = minPointLength; - barY = - mathAbs(barY - translatedThreshold) > minPointLength ? // stacked - yBottom - minPointLength : // keep position - translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0); - } - } - - point.barX = barX; - point.pointWidth = pointWidth; - - // create shape type and shape args that are reused in drawPoints and drawTracker - point.shapeType = 'rect'; - point.shapeArgs = shapeArgs = chart.renderer.Element.prototype.crisp.call(0, borderWidth, barX, barY, barW, barH); - - if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border - shapeArgs.y -= 1; - shapeArgs.height += 1; - } - - // make small columns responsive to mouse - point.trackerArgs = mathAbs(barH) < 3 && merge(point.shapeArgs, { - height: 6, - y: barY - 3 - }); - }); - - }, - - getSymbol: noop, - - /** - * Use a solid rectangle like the area series types - */ - drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol, - - - /** - * Columns have no graph - */ - drawGraph: noop, - - /** - * Draw the columns. For bars, the series.group is rotated, so the same coordinates - * apply for columns and bars. This method is inherited by scatter series. - * - */ - drawPoints: function () { - var series = this, - options = series.options, - renderer = series.chart.renderer, - shapeArgs; - - - // draw the columns - each(series.points, function (point) { - var plotY = point.plotY, - graphic = point.graphic; - if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { - shapeArgs = point.shapeArgs; - if (graphic) { // update - stop(graphic); - graphic.animate(merge(shapeArgs)); - - } else { - point.graphic = graphic = renderer[point.shapeType](shapeArgs) - .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]) - .add(series.group) - .shadow(options.shadow, null, options.stacking && !options.borderRadius); - } - - } else if (graphic) { - point.graphic = graphic.destroy(); // #1269 - } - }); - }, - /** - * Draw the individual tracker elements. - * This method is inherited by scatter and pie charts too. - */ - drawTracker: function () { - var series = this, - chart = series.chart, - renderer = chart.renderer, - shapeArgs, - tracker, - trackerLabel = +new Date(), - options = series.options, - cursor = options.cursor, - css = cursor && { cursor: cursor }, - trackerGroup = series.isCartesian && series.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup), - rel, - plotY, - validPlotY; - - each(series.points, function (point) { - tracker = point.tracker; - shapeArgs = point.trackerArgs || point.shapeArgs; - plotY = point.plotY; - validPlotY = !series.isCartesian || (plotY !== UNDEFINED && !isNaN(plotY)); - delete shapeArgs.strokeWidth; - if (point.y !== null && validPlotY) { - if (tracker) {// update - tracker.attr(shapeArgs); - - } else { - point.tracker = - renderer[point.shapeType](shapeArgs) - .attr({ - isTracker: trackerLabel, - fill: TRACKER_FILL, - visibility: series.visible ? VISIBLE : HIDDEN - }) - .on(hasTouch ? 'touchstart' : 'mouseover', function (event) { - rel = event.relatedTarget || event.fromElement; - if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) { - series.onMouseOver(); - } - point.onMouseOver(); - - }) - .on('mouseout', function (event) { - if (!options.stickyTracking) { - rel = event.relatedTarget || event.toElement; - if (attr(rel, 'isTracker') !== trackerLabel) { - series.onMouseOut(); - } - } - }) - .css(css) - .add(point.group || trackerGroup); // pies have point group - see issue #118 - } - } - }); - }, - - /** - * Override the basic data label alignment by adjusting for the position of the column - */ - alignDataLabel: function (point, dataLabel, options, alignTo, isNew) { - var chart = this.chart, - inverted = chart.inverted, - below = point.below || (point.plotY > (this.translatedThreshold || chart.plotSizeY)), - inside = (this.options.stacking || options.inside); // draw it inside the box? - - // Align to the column itself, or the top of it - if (point.shapeArgs) { // Area range uses this method but not alignTo - alignTo = merge(point.shapeArgs); - if (inverted) { - alignTo = { - x: chart.plotWidth - alignTo.y - alignTo.height, - y: chart.plotHeight - alignTo.x - alignTo.width, - width: alignTo.height, - height: alignTo.width - }; - } - - // Compute the alignment box - if (!inside) { - if (inverted) { - alignTo.x += below ? 0 : alignTo.width; - alignTo.width = 0; - } else { - alignTo.y += below ? alignTo.height : 0; - alignTo.height = 0; - } - } - } - - // When alignment is undefined (typically columns and bars), display the individual - // point below or above the point depending on the threshold - options.align = pick( - options.align, - !inverted || inside ? 'center' : below ? 'right' : 'left' - ); - options.verticalAlign = pick( - options.verticalAlign, - inverted || inside ? 'middle' : below ? 'top' : 'bottom' - ); - - // Call the parent method - Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); - }, - - - /** - * Animate the column heights one by one from zero - * @param {Boolean} init Whether to initialize the animation or run it - */ - animate: function (init) { - var series = this, - points = series.points, - options = series.options; - - if (!init) { // run the animation - /* - * Note: Ideally the animation should be initialized by calling - * series.group.hide(), and then calling series.group.show() - * after the animation was started. But this rendered the shadows - * invisible in IE8 standards mode. If the columns flicker on large - * datasets, this is the cause. - */ - - each(points, function (point) { - var graphic = point.graphic, - shapeArgs = point.shapeArgs, - yAxis = series.yAxis, - threshold = options.threshold; - - if (graphic) { - // start values - graphic.attr({ - height: 0, - y: defined(threshold) ? - yAxis.getThreshold(threshold) : - yAxis.translate(yAxis.getExtremes().min, 0, 1, 0, 1) - }); - - // animate - graphic.animate({ - height: shapeArgs.height, - y: shapeArgs.y - }, options.animation); - } - }); - - - // delete this function to allow it only once - series.animate = null; - } - - }, - /** - * Remove this series from the chart - */ - remove: function () { - var series = this, - chart = series.chart; - - // column and bar series affects other series of the same type - // as they are either stacked or grouped - if (chart.hasRendered) { - each(chart.series, function (otherSeries) { - if (otherSeries.type === series.type) { - otherSeries.isDirty = true; - } - }); - } - - Series.prototype.remove.apply(series, arguments); - } -}); -seriesTypes.column = ColumnSeries; -/** - * Set the default options for bar - */ -defaultPlotOptions.bar = merge(defaultPlotOptions.column); -/** - * The Bar series class - */ -var BarSeries = extendClass(ColumnSeries, { - type: 'bar', - inverted: true -}); -seriesTypes.bar = BarSeries; - -/** - * Set the default options for scatter - */ -defaultPlotOptions.scatter = merge(defaultSeriesOptions, { - lineWidth: 0, - states: { - hover: { - lineWidth: 0 - } - }, - tooltip: { - headerFormat: '{series.name}
', - pointFormat: 'x: {point.x}
y: {point.y}
' - } -}); - -/** - * The scatter series class - */ -var ScatterSeries = extendClass(Series, { - type: 'scatter', - sorted: false, - /** - * Extend the base Series' translate method by adding shape type and - * arguments for the point trackers - */ - translate: function () { - var series = this; - - Series.prototype.translate.apply(series); - - each(series.points, function (point) { - point.shapeType = 'circle'; - point.shapeArgs = { - x: point.plotX, - y: point.plotY, - r: series.chart.options.tooltip.snap - }; - }); - }, - - /** - * Add tracking event listener to the series group, so the point graphics - * themselves act as trackers - */ - drawTracker: function () { - var series = this, - cursor = series.options.cursor, - css = cursor && { cursor: cursor }, - points = series.points, - i = points.length, - graphic; - - // Set an expando property for the point index, used below - while (i--) { - graphic = points[i].graphic; - if (graphic) { // doesn't exist for null points - graphic.element._i = i; - } - } - - // Add the event listeners, we need to do this only once - if (!series._hasTracking) { - series.markerGroup - .attr({ - isTracker: true - }) - .on(hasTouch ? 'touchstart' : 'mouseover', function (e) { - series.onMouseOver(); - if (e.target._i !== UNDEFINED) { // undefined on graph in scatterchart - points[e.target._i].onMouseOver(); - } - }) - .on('mouseout', function () { - if (!series.options.stickyTracking) { - series.onMouseOut(); - } - }) - .css(css); - } else { - series._hasTracking = true; - } - } -}); -seriesTypes.scatter = ScatterSeries; - -/** - * Set the default options for pie - */ -defaultPlotOptions.pie = merge(defaultSeriesOptions, { - borderColor: '#FFFFFF', - borderWidth: 1, - center: ['50%', '50%'], - colorByPoint: true, // always true for pies - dataLabels: { - // align: null, - // connectorWidth: 1, - // connectorColor: point.color, - // connectorPadding: 5, - distance: 30, - enabled: true, - formatter: function () { - return this.point.name; - } - // softConnector: true, - //y: 0 - }, - //innerSize: 0, - legendType: 'point', - marker: null, // point options are specified in the base options - size: '75%', - showInLegend: false, - slicedOffset: 10, - states: { - hover: { - brightness: 0.1, - shadow: false - } - } -}); - -/** - * Extended point object for pies - */ -var PiePoint = extendClass(Point, { - /** - * Initiate the pie slice - */ - init: function () { - - Point.prototype.init.apply(this, arguments); - - var point = this, - toggleSlice; - - //visible: options.visible !== false, - extend(point, { - visible: point.visible !== false, - name: pick(point.name, 'Slice') - }); - - // add event listener for select - toggleSlice = function () { - point.slice(); - }; - addEvent(point, 'select', toggleSlice); - addEvent(point, 'unselect', toggleSlice); - - return point; - }, - - /** - * Toggle the visibility of the pie slice - * @param {Boolean} vis Whether to show the slice or not. If undefined, the - * visibility is toggled - */ - setVisible: function (vis) { - var point = this, - series = point.series, - chart = series.chart, - tracker = point.tracker, - dataLabel = point.dataLabel, - connector = point.connector, - shadowGroup = point.shadowGroup, - method; - - // if called without an argument, toggle visibility - point.visible = vis = vis === UNDEFINED ? !point.visible : vis; - - method = vis ? 'show' : 'hide'; - - point.group[method](); - if (tracker) { - tracker[method](); - } - if (dataLabel) { - dataLabel[method](); - } - if (connector) { - connector[method](); - } - if (shadowGroup) { - shadowGroup[method](); - } - if (point.legendItem) { - chart.legend.colorizeItem(point, vis); - } - - // Handle ignore hidden slices - if (!series.isDirty && series.options.ignoreHiddenPoint) { - series.isDirty = true; - chart.redraw(); - } - }, - - /** - * Set or toggle whether the slice is cut out from the pie - * @param {Boolean} sliced When undefined, the slice state is toggled - * @param {Boolean} redraw Whether to redraw the chart. True by default. - */ - slice: function (sliced, redraw, animation) { - var point = this, - series = point.series, - chart = series.chart, - slicedTranslation = point.slicedTranslation, - translation; - - setAnimation(animation, chart); - - // redraw is true by default - redraw = pick(redraw, true); - - // if called without an argument, toggle - sliced = point.sliced = defined(sliced) ? sliced : !point.sliced; - - translation = { - translateX: (sliced ? slicedTranslation[0] : chart.plotLeft), - translateY: (sliced ? slicedTranslation[1] : chart.plotTop) - }; - point.group.animate(translation); - if (point.shadowGroup) { - point.shadowGroup.animate(translation); - } - - } -}); - -/** - * The Pie series class - */ -var PieSeries = { - type: 'pie', - isCartesian: false, - pointClass: PiePoint, - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'borderColor', - 'stroke-width': 'borderWidth', - fill: 'color' - }, - - /** - * Pies have one color each point - */ - getColor: function () { - // record first color for use in setData - this.initialColor = this.chart.counters.color; - }, - - /** - * Animate the pies in - */ - animate: function () { - var series = this, - points = series.points; - - each(points, function (point) { - var graphic = point.graphic, - args = point.shapeArgs, - up = -mathPI / 2; - - if (graphic) { - // start values - graphic.attr({ - r: 0, - start: up, - end: up - }); - - // animate - graphic.animate({ - r: args.r, - start: args.start, - end: args.end - }, series.options.animation); - } - }); - - // delete this function to allow it only once - series.animate = null; - - }, - - /** - * Extend the basic setData method by running processData and generatePoints immediately, - * in order to access the points from the legend. - */ - setData: function (data, redraw) { - Series.prototype.setData.call(this, data, false); - this.processData(); - this.generatePoints(); - if (pick(redraw, true)) { - this.chart.redraw(); - } - }, - - /** - * Get the center of the pie based on the size and center options relative to the - * plot area. Borrowed by the polar and gauge series types. - */ - getCenter: function () { - - var options = this.options, - chart = this.chart, - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - positions = options.center.concat([options.size, options.innerSize || 0]), - smallestSize = mathMin(plotWidth, plotHeight), - isPercent; - - return map(positions, function (length, i) { - - isPercent = /%$/.test(length); - return isPercent ? - // i == 0: centerX, relative to width - // i == 1: centerY, relative to height - // i == 2: size, relative to smallestSize - // i == 4: innerSize, relative to smallestSize - [plotWidth, plotHeight, smallestSize, smallestSize][i] * - pInt(length) / 100 : - length; - }); - }, - - /** - * Do translation for pie slices - */ - translate: function () { - this.generatePoints(); - - var total = 0, - series = this, - cumulative = -0.25, // start at top - precision = 1000, // issue #172 - options = series.options, - slicedOffset = options.slicedOffset, - connectorOffset = slicedOffset + options.borderWidth, - positions, - chart = series.chart, - start, - end, - angle, - points = series.points, - circ = 2 * mathPI, - fraction, - radiusX, // the x component of the radius vector for a given point - radiusY, - labelDistance = options.dataLabels.distance, - ignoreHiddenPoint = options.ignoreHiddenPoint, - i, - len = points.length, - point; - - // get positions - either an integer or a percentage string must be given - series.center = positions = series.getCenter(); - - // utility for getting the x value from a given y, used for anticollision logic in data labels - series.getX = function (y, left) { - - angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance)); - - return positions[0] + - (left ? -1 : 1) * - (mathCos(angle) * (positions[2] / 2 + labelDistance)); - }; - - // get the total sum - for (i = 0; i < len; i++) { - point = points[i]; - total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y; - } - - // Calculate the geometry for each point - for (i = 0; i < len; i++) { - - point = points[i]; - - // set start and end angle - fraction = total ? point.y / total : 0; - start = mathRound(cumulative * circ * precision) / precision; - if (!ignoreHiddenPoint || point.visible) { - cumulative += fraction; - } - end = mathRound(cumulative * circ * precision) / precision; - - // set the shape - point.shapeType = 'arc'; - point.shapeArgs = { - x: positions[0], - y: positions[1], - r: positions[2] / 2, - innerR: positions[3] / 2, - start: start, - end: end - }; - - // center for the sliced out slice - angle = (end + start) / 2; - point.slicedTranslation = map([ - mathCos(angle) * slicedOffset + chart.plotLeft, - mathSin(angle) * slicedOffset + chart.plotTop - ], mathRound); - - // set the anchor point for tooltips - radiusX = mathCos(angle) * positions[2] / 2; - radiusY = mathSin(angle) * positions[2] / 2; - point.tooltipPos = [ - positions[0] + radiusX * 0.7, - positions[1] + radiusY * 0.7 - ]; - - // set the anchor point for data labels - point.labelPos = [ - positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector - positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a - positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie - positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a - positions[0] + radiusX, // landing point for connector - positions[1] + radiusY, // a/a - labelDistance < 0 ? // alignment - 'center' : - angle < circ / 4 ? 'left' : 'right', // alignment - angle // center angle - ]; - - // API properties - point.percentage = fraction * 100; - point.total = total; - - } - - - this.setTooltipPoints(); - }, - - /** - * Render the slices - */ - render: function () { - var series = this; - - // cache attributes for shapes - series.getAttribs(); - - this.drawPoints(); - - // draw the mouse tracking area - if (series.options.enableMouseTracking !== false) { - series.drawTracker(); - } - - this.drawDataLabels(); - - if (series.options.animation && series.animate) { - series.animate(); - } - - // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - series.isDirty = false; // means data is in accordance with what you see - }, - - /** - * Draw the data points - */ - drawPoints: function () { - var series = this, - chart = series.chart, - renderer = chart.renderer, - groupTranslation, - //center, - graphic, - group, - shadow = series.options.shadow, - shadowGroup, - shapeArgs; - - // draw the slices - each(series.points, function (point) { - graphic = point.graphic; - shapeArgs = point.shapeArgs; - group = point.group; - shadowGroup = point.shadowGroup; - - // put the shadow behind all points - if (shadow && !shadowGroup) { - shadowGroup = point.shadowGroup = renderer.g('shadow') - .attr({ zIndex: 4 }) - .add(); - } - - // create the group the first time - if (!group) { - group = point.group = renderer.g('point') - .attr({ zIndex: 5 }) - .add(); - } - - // if the point is sliced, use special translation, else use plot area traslation - groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop]; - group.translate(groupTranslation[0], groupTranslation[1]); - if (shadowGroup) { - shadowGroup.translate(groupTranslation[0], groupTranslation[1]); - } - - // draw the slice - if (graphic) { - graphic.animate(shapeArgs); - } else { - point.graphic = graphic = renderer.arc(shapeArgs) - .setRadialReference(series.center) - .attr(extend( - point.pointAttr[NORMAL_STATE], - { 'stroke-linejoin': 'round' } - )) - .add(point.group) - .shadow(shadow, shadowGroup); - - } - - // detect point specific visibility - if (point.visible === false) { - point.setVisible(false); - } - - }); - - }, - - /** - * Override the base drawDataLabels method by pie specific functionality - */ - drawDataLabels: function () { - var series = this, - data = series.data, - point, - chart = series.chart, - options = series.options.dataLabels, - connectorPadding = pick(options.connectorPadding, 10), - connectorWidth = pick(options.connectorWidth, 1), - connector, - connectorPath, - softConnector = pick(options.softConnector, true), - distanceOption = options.distance, - seriesCenter = series.center, - radius = seriesCenter[2] / 2, - centerY = seriesCenter[1], - outside = distanceOption > 0, - dataLabel, - labelPos, - labelHeight, - halves = [// divide the points into right and left halves for anti collision - [], // right - [] // left - ], - x, - y, - visibility, - rankArr, - sort, - i = 2, - j; - - // get out if not enabled - if (!options.enabled && !series._hasPointLabels) { - return; - } - - // run parent method - Series.prototype.drawDataLabels.apply(series); - - // arrange points for detection collision - each(data, function (point) { - if (point.dataLabel) { // it may have been cancelled in the base method (#407) - halves[ - point.labelPos[7] < mathPI / 2 ? 0 : 1 - ].push(point); - } - }); - halves[1].reverse(); - - // define the sorting algorithm - sort = function (a, b) { - return b.y - a.y; - }; - - // assume equal label heights - labelHeight = halves[0][0] && halves[0][0].dataLabel && (halves[0][0].dataLabel.getBBox().height || 21); // 21 is for #968 - - /* Loop over the points in each half, starting from the top and bottom - * of the pie to detect overlapping labels. - */ - while (i--) { - - var slots = [], - slotsLength, - usedSlots = [], - points = halves[i], - pos, - length = points.length, - slotIndex; - - // Only do anti-collision when we are outside the pie and have connectors (#856) - if (distanceOption > 0) { - - // build the slots - for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) { - slots.push(pos); - // visualize the slot - /* - var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0), - slotY = pos + chart.plotTop; - if (!isNaN(slotX)) { - chart.renderer.rect(slotX, slotY - 7, 100, labelHeight) - .attr({ - 'stroke-width': 1, - stroke: 'silver' - }) - .add(); - chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4) - .attr({ - fill: 'silver' - }).add(); - } - // */ - } - slotsLength = slots.length; - - // if there are more values than available slots, remove lowest values - if (length > slotsLength) { - // create an array for sorting and ranking the points within each quarter - rankArr = [].concat(points); - rankArr.sort(sort); - j = length; - while (j--) { - rankArr[j].rank = j; - } - j = length; - while (j--) { - if (points[j].rank >= slotsLength) { - points.splice(j, 1); - } - } - length = points.length; - } - - // The label goes to the nearest open slot, but not closer to the edge than - // the label's index. - for (j = 0; j < length; j++) { - - point = points[j]; - labelPos = point.labelPos; - - var closest = 9999, - distance, - slotI; - - // find the closest slot index - for (slotI = 0; slotI < slotsLength; slotI++) { - distance = mathAbs(slots[slotI] - labelPos[1]); - if (distance < closest) { - closest = distance; - slotIndex = slotI; - } - } - - // if that slot index is closer to the edges of the slots, move it - // to the closest appropriate slot - if (slotIndex < j && slots[j] !== null) { // cluster at the top - slotIndex = j; - } else if (slotsLength < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom - slotIndex = slotsLength - length + j; - while (slots[slotIndex] === null) { // make sure it is not taken - slotIndex++; - } - } else { - // Slot is taken, find next free slot below. In the next run, the next slice will find the - // slot above these, because it is the closest one - while (slots[slotIndex] === null) { // make sure it is not taken - slotIndex++; - } - } - - usedSlots.push({ i: slotIndex, y: slots[slotIndex] }); - slots[slotIndex] = null; // mark as taken - } - // sort them in order to fill in from the top - usedSlots.sort(sort); - } - - // now the used slots are sorted, fill them up sequentially - for (j = 0; j < length; j++) { - - var slot, naturalY; - - point = points[j]; - labelPos = point.labelPos; - dataLabel = point.dataLabel; - visibility = point.visible === false ? HIDDEN : VISIBLE; - naturalY = labelPos[1]; - - if (distanceOption > 0) { - slot = usedSlots.pop(); - slotIndex = slot.i; - - // if the slot next to currrent slot is free, the y value is allowed - // to fall back to the natural position - y = slot.y; - if ((naturalY > y && slots[slotIndex + 1] !== null) || - (naturalY < y && slots[slotIndex - 1] !== null)) { - y = naturalY; - } - - } else { - y = naturalY; - } - - // get the x - use the natural x position for first and last slot, to prevent the top - // and botton slice connectors from touching each other on either side - x = options.justify ? - seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) : - series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i); - - // move or place the data label - dataLabel - .attr({ - visibility: visibility, - align: labelPos[6] - })[dataLabel.moved ? 'animate' : 'attr']({ - x: x + options.x + - ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0), - y: y + options.y - 10 // 10 is for the baseline (label vs text) - }); - dataLabel.moved = true; - - // draw the connector - if (outside && connectorWidth) { - connector = point.connector; - - connectorPath = softConnector ? [ - M, - x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label - 'C', - x, y, // first break, next to the label - 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5], - labelPos[2], labelPos[3], // second break - L, - labelPos[4], labelPos[5] // base - ] : [ - M, - x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label - L, - labelPos[2], labelPos[3], // second break - L, - labelPos[4], labelPos[5] // base - ]; - - if (connector) { - connector.animate({ d: connectorPath }); - connector.attr('visibility', visibility); - - } else { - point.connector = connector = series.chart.renderer.path(connectorPath).attr({ - 'stroke-width': connectorWidth, - stroke: options.connectorColor || point.color || '#606060', - visibility: visibility, - zIndex: 3 - }) - .translate(chart.plotLeft, chart.plotTop) - .add(); - } - } - } - } - }, - - alignDataLabel: noop, - - /** - * Draw point specific tracker objects. Inherit directly from column series. - */ - drawTracker: ColumnSeries.prototype.drawTracker, - - /** - * Use a simple symbol from column prototype - */ - drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol, - - /** - * Pies don't have point marker symbols - */ - getSymbol: function () {} - -}; -PieSeries = extendClass(Series, PieSeries); -seriesTypes.pie = PieSeries; - - -// global variables -extend(Highcharts, { - - // Constructors - Axis: Axis, - CanVGRenderer: CanVGRenderer, - Chart: Chart, - Color: Color, - Legend: Legend, - MouseTracker: MouseTracker, - Point: Point, - Tick: Tick, - Tooltip: Tooltip, - Renderer: Renderer, - Series: Series, - SVGRenderer: SVGRenderer, - VMLRenderer: VMLRenderer, - - // Various - dateFormat: dateFormat, - pathAnim: pathAnim, - getOptions: getOptions, - hasBidiBug: hasBidiBug, - numberFormat: numberFormat, - seriesTypes: seriesTypes, - setOptions: setOptions, - addEvent: addEvent, - removeEvent: removeEvent, - createElement: createElement, - discardElement: discardElement, - css: css, - each: each, - extend: extend, - map: map, - merge: merge, - pick: pick, - splat: splat, - extendClass: extendClass, - pInt: pInt, - wrap: wrap, - svg: hasSVG, - canvas: useCanVG, - vml: !hasSVG && !useCanVG, - product: 'Highcharts', - version: '2.3.3' -}); -}()); +// ==ClosureCompiler== +// @compilation_level SIMPLE_OPTIMIZATIONS + +/** + * @license Highcharts JS v4.0.1 (2014-04-24) + * + * (c) 2009-2014 Torstein Honsi + * + * License: www.highcharts.com/license + */ + +// JSLint options: +/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */ + +(function () { +// encapsulated variables +var UNDEFINED, + doc = document, + win = window, + math = Math, + mathRound = math.round, + mathFloor = math.floor, + mathCeil = math.ceil, + mathMax = math.max, + mathMin = math.min, + mathAbs = math.abs, + mathCos = math.cos, + mathSin = math.sin, + mathPI = math.PI, + deg2rad = mathPI * 2 / 360, + + + // some variables + userAgent = navigator.userAgent, + isOpera = win.opera, + isIE = /msie/i.test(userAgent) && !isOpera, + docMode8 = doc.documentMode === 8, + isWebKit = /AppleWebKit/.test(userAgent), + isFirefox = /Firefox/.test(userAgent), + isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent), + SVG_NS = 'http://www.w3.org/2000/svg', + hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect, + hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38 + useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext, + Renderer, + hasTouch, + symbolSizes = {}, + idCounter = 0, + garbageBin, + defaultOptions, + dateFormat, // function + globalAnimation, + pathAnim, + timeUnits, + noop = function () {}, + charts = [], + chartCount = 0, + PRODUCT = 'Highcharts', + VERSION = '4.0.1', + + // some constants for frequently used strings + DIV = 'div', + ABSOLUTE = 'absolute', + RELATIVE = 'relative', + HIDDEN = 'hidden', + PREFIX = 'highcharts-', + VISIBLE = 'visible', + PX = 'px', + NONE = 'none', + M = 'M', + L = 'L', + numRegex = /^[0-9]+$/, + NORMAL_STATE = '', + HOVER_STATE = 'hover', + SELECT_STATE = 'select', + MILLISECOND = 'millisecond', + SECOND = 'second', + MINUTE = 'minute', + HOUR = 'hour', + DAY = 'day', + WEEK = 'week', + MONTH = 'month', + YEAR = 'year', + + // Object for extending Axis + AxisPlotLineOrBandExtension, + + // constants for attributes + STROKE_WIDTH = 'stroke-width', + + // time methods, changed based on whether or not UTC is used + makeTime, + timezoneOffset, + getMinutes, + getHours, + getDay, + getDate, + getMonth, + getFullYear, + setMinutes, + setHours, + setDate, + setMonth, + setFullYear, + + + // lookup over the types and the associated classes + seriesTypes = {}; + +// The Highcharts namespace +var Highcharts = win.Highcharts = win.Highcharts ? error(16, true) : {}; + +/** + * Extend an object with the members of another + * @param {Object} a The object to be extended + * @param {Object} b The object to add to the first one + */ +function extend(a, b) { + var n; + if (!a) { + a = {}; + } + for (n in b) { + a[n] = b[n]; + } + return a; +} + +/** + * Deep merge two or more objects and return a third object. If the first argument is + * true, the contents of the second object is copied into the first object. + * Previously this function redirected to jQuery.extend(true), but this had two limitations. + * First, it deep merged arrays, which lead to workarounds in Highcharts. Second, + * it copied properties from extended prototypes. + */ +function merge() { + var i, + args = arguments, + len, + ret = {}, + doCopy = function (copy, original) { + var value, key; + + // An object is replacing a primitive + if (typeof copy !== 'object') { + copy = {}; + } + + for (key in original) { + if (original.hasOwnProperty(key)) { + value = original[key]; + + // Copy the contents of objects, but not arrays or DOM nodes + if (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]' + && key !== 'renderTo' && typeof value.nodeType !== 'number') { + copy[key] = doCopy(copy[key] || {}, value); + + // Primitives and arrays are copied over directly + } else { + copy[key] = original[key]; + } + } + } + return copy; + }; + + // If first argument is true, copy into the existing object. Used in setOptions. + if (args[0] === true) { + ret = args[1]; + args = Array.prototype.slice.call(args, 2); + } + + // For each argument, extend the return + len = args.length; + for (i = 0; i < len; i++) { + ret = doCopy(ret, args[i]); + } + + return ret; +} + +/** + * Take an array and turn into a hash with even number arguments as keys and odd numbers as + * values. Allows creating constants for commonly used style properties, attributes etc. + * Avoid it in performance critical situations like looping + */ +function hash() { + var i = 0, + args = arguments, + length = args.length, + obj = {}; + for (; i < length; i++) { + obj[args[i++]] = args[i]; + } + return obj; +} + +/** + * Shortcut for parseInt + * @param {Object} s + * @param {Number} mag Magnitude + */ +function pInt(s, mag) { + return parseInt(s, mag || 10); +} + +/** + * Check for string + * @param {Object} s + */ +function isString(s) { + return typeof s === 'string'; +} + +/** + * Check for object + * @param {Object} obj + */ +function isObject(obj) { + return typeof obj === 'object'; +} + +/** + * Check for array + * @param {Object} obj + */ +function isArray(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; +} + +/** + * Check for number + * @param {Object} n + */ +function isNumber(n) { + return typeof n === 'number'; +} + +function log2lin(num) { + return math.log(num) / math.LN10; +} +function lin2log(num) { + return math.pow(10, num); +} + +/** + * Remove last occurence of an item from an array + * @param {Array} arr + * @param {Mixed} item + */ +function erase(arr, item) { + var i = arr.length; + while (i--) { + if (arr[i] === item) { + arr.splice(i, 1); + break; + } + } + //return arr; +} + +/** + * Returns true if the object is not null or undefined. Like MooTools' $.defined. + * @param {Object} obj + */ +function defined(obj) { + return obj !== UNDEFINED && obj !== null; +} + +/** + * Set or get an attribute or an object of attributes. Can't use jQuery attr because + * it attempts to set expando properties on the SVG element, which is not allowed. + * + * @param {Object} elem The DOM element to receive the attribute(s) + * @param {String|Object} prop The property or an abject of key-value pairs + * @param {String} value The value if a single property is set + */ +function attr(elem, prop, value) { + var key, + ret; + + // if the prop is a string + if (isString(prop)) { + // set the value + if (defined(value)) { + elem.setAttribute(prop, value); + + // get the value + } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo... + ret = elem.getAttribute(prop); + } + + // else if prop is defined, it is a hash of key/value pairs + } else if (defined(prop) && isObject(prop)) { + for (key in prop) { + elem.setAttribute(key, prop[key]); + } + } + return ret; +} +/** + * Check if an element is an array, and if not, make it into an array. Like + * MooTools' $.splat. + */ +function splat(obj) { + return isArray(obj) ? obj : [obj]; +} + + +/** + * Return the first value that is defined. Like MooTools' $.pick. + */ +function pick() { + var args = arguments, + i, + arg, + length = args.length; + for (i = 0; i < length; i++) { + arg = args[i]; + if (typeof arg !== 'undefined' && arg !== null) { + return arg; + } + } +} + +/** + * Set CSS on a given element + * @param {Object} el + * @param {Object} styles Style object with camel case property names + */ +function css(el, styles) { + if (isIE && !hasSVG) { // #2686 + if (styles && styles.opacity !== UNDEFINED) { + styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')'; + } + } + extend(el.style, styles); +} + +/** + * Utility function to create element with attributes and styles + * @param {Object} tag + * @param {Object} attribs + * @param {Object} styles + * @param {Object} parent + * @param {Object} nopad + */ +function createElement(tag, attribs, styles, parent, nopad) { + var el = doc.createElement(tag); + if (attribs) { + extend(el, attribs); + } + if (nopad) { + css(el, {padding: 0, border: NONE, margin: 0}); + } + if (styles) { + css(el, styles); + } + if (parent) { + parent.appendChild(el); + } + return el; +} + +/** + * Extend a prototyped class by new members + * @param {Object} parent + * @param {Object} members + */ +function extendClass(parent, members) { + var object = function () {}; + object.prototype = new parent(); + extend(object.prototype, members); + return object; +} + +/** + * Format a number and return a string based on input settings + * @param {Number} number The input number to format + * @param {Number} decimals The amount of decimals + * @param {String} decPoint The decimal point, defaults to the one given in the lang options + * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options + */ +function numberFormat(number, decimals, decPoint, thousandsSep) { + var lang = defaultOptions.lang, + // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/ + n = +number || 0, + c = decimals === -1 ? + (n.toString().split('.')[1] || '').length : // preserve decimals + (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals), + d = decPoint === undefined ? lang.decimalPoint : decPoint, + t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, + s = n < 0 ? "-" : "", + i = String(pInt(n = mathAbs(n).toFixed(c))), + j = i.length > 3 ? i.length % 3 : 0; + + return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + + (c ? d + mathAbs(n - i).toFixed(c).slice(2) : ""); +} + +/** + * Pad a string to a given length by adding 0 to the beginning + * @param {Number} number + * @param {Number} length + */ +function pad(number, length) { + // Create an array of the remaining length +1 and join it with 0's + return new Array((length || 2) + 1 - String(number).length).join(0) + number; +} + +/** + * Wrap a method with extended functionality, preserving the original function + * @param {Object} obj The context object that the method belongs to + * @param {String} method The name of the method to extend + * @param {Function} func A wrapper function callback. This function is called with the same arguments + * as the original function, except that the original function is unshifted and passed as the first + * argument. + */ +function wrap(obj, method, func) { + var proceed = obj[method]; + obj[method] = function () { + var args = Array.prototype.slice.call(arguments); + args.unshift(proceed); + return func.apply(this, args); + }; +} + +/** + * Based on http://www.php.net/manual/en/function.strftime.php + * @param {String} format + * @param {Number} timestamp + * @param {Boolean} capitalize + */ +dateFormat = function (format, timestamp, capitalize) { + if (!defined(timestamp) || isNaN(timestamp)) { + return 'Invalid date'; + } + format = pick(format, '%Y-%m-%d %H:%M:%S'); + + var date = new Date(timestamp - timezoneOffset), + key, // used in for constuct below + // get the basic time values + hours = date[getHours](), + day = date[getDay](), + dayOfMonth = date[getDate](), + month = date[getMonth](), + fullYear = date[getFullYear](), + lang = defaultOptions.lang, + langWeekdays = lang.weekdays, + + // List all format keys. Custom formats can be added from the outside. + replacements = extend({ + + // Day + 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon' + 'A': langWeekdays[day], // Long weekday, like 'Monday' + 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31 + 'e': dayOfMonth, // Day of the month, 1 through 31 + + // Week (none implemented) + //'W': weekNumber(), + + // Month + 'b': lang.shortMonths[month], // Short month, like 'Jan' + 'B': lang.months[month], // Long month, like 'January' + 'm': pad(month + 1), // Two digit month number, 01 through 12 + + // Year + 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009 + 'Y': fullYear, // Four digits year, like 2009 + + // Time + 'H': pad(hours), // Two digits hours in 24h format, 00 through 23 + 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11 + 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12 + 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59 + 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM + 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM + 'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59 + 'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby) + }, Highcharts.dateFormats); + + + // do the replaces + for (key in replacements) { + while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster + format = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]); + } + } + + // Optionally capitalize the string and return + return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format; +}; + +/** + * Format a single variable. Similar to sprintf, without the % prefix. + */ +function formatSingle(format, val) { + var floatRegex = /f$/, + decRegex = /\.([0-9])/, + lang = defaultOptions.lang, + decimals; + + if (floatRegex.test(format)) { // float + decimals = format.match(decRegex); + decimals = decimals ? decimals[1] : -1; + if (val !== null) { + val = numberFormat( + val, + decimals, + lang.decimalPoint, + format.indexOf(',') > -1 ? lang.thousandsSep : '' + ); + } + } else { + val = dateFormat(format, val); + } + return val; +} + +/** + * Format a string according to a subset of the rules of Python's String.format method. + */ +function format(str, ctx) { + var splitter = '{', + isInside = false, + segment, + valueAndFormat, + path, + i, + len, + ret = [], + val, + index; + + while ((index = str.indexOf(splitter)) !== -1) { + + segment = str.slice(0, index); + if (isInside) { // we're on the closing bracket looking back + + valueAndFormat = segment.split(':'); + path = valueAndFormat.shift().split('.'); // get first and leave format + len = path.length; + val = ctx; + + // Assign deeper paths + for (i = 0; i < len; i++) { + val = val[path[i]]; + } + + // Format the replacement + if (valueAndFormat.length) { + val = formatSingle(valueAndFormat.join(':'), val); + } + + // Push the result and advance the cursor + ret.push(val); + + } else { + ret.push(segment); + + } + str = str.slice(index + 1); // the rest + isInside = !isInside; // toggle + splitter = isInside ? '}' : '{'; // now look for next matching bracket + } + ret.push(str); + return ret.join(''); +} + +/** + * Get the magnitude of a number + */ +function getMagnitude(num) { + return math.pow(10, mathFloor(math.log(num) / math.LN10)); +} + +/** + * 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 + */ +function normalizeTickInterval(interval, multiples, magnitude, options) { + var normalized, i; + + // round to a tenfold of 1, 2, 2.5 or 5 + magnitude = pick(magnitude, 1); + normalized = interval / magnitude; + + // multiples for a linear scale + if (!multiples) { + multiples = [1, 2, 2.5, 5, 10]; + + // the allowDecimals option + if (options && options.allowDecimals === false) { + if (magnitude === 1) { + multiples = [1, 2, 5, 10]; + } else if (magnitude <= 0.1) { + multiples = [1 / magnitude]; + } + } + } + + // normalize the interval to the nearest multiple + for (i = 0; i < multiples.length; i++) { + interval = multiples[i]; + if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) { + break; + } + } + + // multiply back to the correct magnitude + interval *= magnitude; + + return interval; +} + + +/** + * Helper class that contains variuos counters that are local to the chart. + */ +function ChartCounters() { + this.color = 0; + this.symbol = 0; +} + +ChartCounters.prototype = { + /** + * Wraps the color counter if it reaches the specified length. + */ + wrapColor: function (length) { + if (this.color >= length) { + this.color = 0; + } + }, + + /** + * Wraps the symbol counter if it reaches the specified length. + */ + wrapSymbol: function (length) { + if (this.symbol >= length) { + this.symbol = 0; + } + } +}; + + +/** + * Utility method that sorts an object array and keeping the order of equal items. + * ECMA script standard does not specify the behaviour when items are equal. + */ +function stableSort(arr, sortFunction) { + var length = arr.length, + sortValue, + i; + + // Add index to each item + for (i = 0; i < length; i++) { + arr[i].ss_i = i; // stable sort index + } + + arr.sort(function (a, b) { + sortValue = sortFunction(a, b); + return sortValue === 0 ? a.ss_i - b.ss_i : sortValue; + }); + + // Remove index from items + for (i = 0; i < length; i++) { + delete arr[i].ss_i; // stable sort index + } +} + +/** + * Non-recursive method to find the lowest member of an array. Math.min raises a maximum + * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This + * method is slightly slower, but safe. + */ +function arrayMin(data) { + var i = data.length, + min = data[0]; + + while (i--) { + if (data[i] < min) { + min = data[i]; + } + } + return min; +} + +/** + * Non-recursive method to find the lowest member of an array. Math.min raises a maximum + * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This + * method is slightly slower, but safe. + */ +function arrayMax(data) { + var i = data.length, + max = data[0]; + + while (i--) { + if (data[i] > max) { + max = data[i]; + } + } + return max; +} + +/** + * Utility method that destroys any SVGElement or VMLElement that are properties on the given object. + * It loops all properties and invokes destroy if there is a destroy method. The property is + * then delete'ed. + * @param {Object} The object to destroy properties on + * @param {Object} Exception, do not destroy this property, only delete it. + */ +function destroyObjectProperties(obj, except) { + var n; + for (n in obj) { + // If the object is non-null and destroy is defined + if (obj[n] && obj[n] !== except && obj[n].destroy) { + // Invoke the destroy + obj[n].destroy(); + } + + // Delete the property from the object. + delete obj[n]; + } +} + + +/** + * Discard an element by moving it to the bin and delete + * @param {Object} The HTML node to discard + */ +function discardElement(element) { + // create a garbage bin element, not part of the DOM + if (!garbageBin) { + garbageBin = createElement(DIV); + } + + // move the node and empty bin + if (element) { + garbageBin.appendChild(element); + } + garbageBin.innerHTML = ''; +} + +/** + * Provide error messages for debugging, with links to online explanation + */ +function error(code, stop) { + var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code; + if (stop) { + throw msg; + } else if (win.console) { + console.log(msg); + } +} + +/** + * Fix JS round off float errors + * @param {Number} num + */ +function correctFloat(num) { + return parseFloat( + num.toPrecision(14) + ); +} + +/** + * Set the global animation to either a given value, or fall back to the + * given chart's animation option + * @param {Object} animation + * @param {Object} chart + */ +function setAnimation(animation, chart) { + globalAnimation = pick(animation, chart.animation); +} + +/** + * The time unit lookup + */ +/*jslint white: true*/ +timeUnits = hash( + MILLISECOND, 1, + SECOND, 1000, + MINUTE, 60000, + HOUR, 3600000, + DAY, 24 * 3600000, + WEEK, 7 * 24 * 3600000, + MONTH, 31 * 24 * 3600000, + YEAR, 31556952000 +); +/*jslint white: false*/ +/** + * Path interpolation algorithm used across adapters + */ +pathAnim = { + /** + * Prepare start and end values so that the path can be animated one to one + */ + init: function (elem, fromD, toD) { + fromD = fromD || ''; + var shift = elem.shift, + bezier = fromD.indexOf('C') > -1, + numParams = bezier ? 7 : 3, + endLength, + slice, + i, + start = fromD.split(' '), + end = [].concat(toD), // copy + startBaseLine, + endBaseLine, + sixify = function (arr) { // in splines make move points have six parameters like bezier curves + i = arr.length; + while (i--) { + if (arr[i] === M) { + arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]); + } + } + }; + + if (bezier) { + sixify(start); + sixify(end); + } + + // pull out the base lines before padding + if (elem.isArea) { + startBaseLine = start.splice(start.length - 6, 6); + endBaseLine = end.splice(end.length - 6, 6); + } + + // if shifting points, prepend a dummy point to the end path + if (shift <= end.length / numParams && start.length === end.length) { + while (shift--) { + end = [].concat(end).splice(0, numParams).concat(end); + } + } + elem.shift = 0; // reset for following animations + + // copy and append last point until the length matches the end length + if (start.length) { + endLength = end.length; + while (start.length < endLength) { + + //bezier && sixify(start); + slice = [].concat(start).splice(start.length - numParams, numParams); + if (bezier) { // disable first control point + slice[numParams - 6] = slice[numParams - 2]; + slice[numParams - 5] = slice[numParams - 1]; + } + start = start.concat(slice); + } + } + + if (startBaseLine) { // append the base lines for areas + start = start.concat(startBaseLine); + end = end.concat(endBaseLine); + } + return [start, end]; + }, + + /** + * Interpolate each value of the path and return the array + */ + step: function (start, end, pos, complete) { + var ret = [], + i = start.length, + startVal; + + if (pos === 1) { // land on the final path without adjustment points appended in the ends + ret = complete; + + } else if (i === end.length && pos < 1) { + while (i--) { + startVal = parseFloat(start[i]); + ret[i] = + isNaN(startVal) ? // a letter instruction like M or L + start[i] : + pos * (parseFloat(end[i] - startVal)) + startVal; + + } + } else { // if animation is finished or length not matching, land on right value + ret = end; + } + return ret; + } +}; + +(function ($) { + /** + * The default HighchartsAdapter for jQuery + */ + win.HighchartsAdapter = win.HighchartsAdapter || ($ && { + + /** + * Initialize the adapter by applying some extensions to jQuery + */ + init: function (pathAnim) { + + // extend the animate function to allow SVG animations + var Fx = $.fx, + Step = Fx.step, + dSetter, + Tween = $.Tween, + propHooks = Tween && Tween.propHooks, + opacityHook = $.cssHooks.opacity; + + /*jslint unparam: true*//* allow unused param x in this function */ + $.extend($.easing, { + easeOutQuad: function (x, t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + } + }); + /*jslint unparam: false*/ + + // extend some methods to check for elem.attr, which means it is a Highcharts SVG object + $.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) { + var obj = Step, + base; + + // Handle different parent objects + if (fn === 'cur') { + obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype + + } else if (fn === '_default' && Tween) { // jQuery 1.8 model + obj = propHooks[fn]; + fn = 'set'; + } + + // Overwrite the method + base = obj[fn]; + if (base) { // step.width and step.height don't exist in jQuery < 1.7 + + // create the extended function replacement + obj[fn] = function (fx) { + + var elem; + + // Fx.prototype.cur does not use fx argument + fx = i ? fx : this; + + // Don't run animations on textual properties like align (#1821) + if (fx.prop === 'align') { + return; + } + + // shortcut + elem = fx.elem; + + // Fx.prototype.cur returns the current value. The other ones are setters + // and returning a value has no effect. + return elem.attr ? // is SVG element wrapper + elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method + base.apply(this, arguments); // use jQuery's built-in method + }; + } + }); + + // Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+ + wrap(opacityHook, 'get', function (proceed, elem, computed) { + return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed); + }); + + + // Define the setter function for d (path definitions) + dSetter = function (fx) { + var elem = fx.elem, + ends; + + // Normally start and end should be set in state == 0, but sometimes, + // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped + // in these cases + if (!fx.started) { + ends = pathAnim.init(elem, elem.d, elem.toD); + fx.start = ends[0]; + fx.end = ends[1]; + fx.started = true; + } + + + // interpolate each value of the path + elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD)); + }; + + // jQuery 1.8 style + if (Tween) { + propHooks.d = { + set: dSetter + }; + // pre 1.8 + } else { + // animate paths + Step.d = dSetter; + } + + /** + * Utility for iterating over an array. Parameters are reversed compared to jQuery. + * @param {Array} arr + * @param {Function} fn + */ + this.each = Array.prototype.forEach ? + function (arr, fn) { // modern browsers + return Array.prototype.forEach.call(arr, fn); + + } : + function (arr, fn) { // legacy + var i = 0, + len = arr.length; + for (; i < len; i++) { + if (fn.call(arr[i], arr[i], i, arr) === false) { + return i; + } + } + }; + + /** + * Register Highcharts as a plugin in the respective framework + */ + $.fn.highcharts = function () { + var constr = 'Chart', // default constructor + args = arguments, + options, + ret, + chart; + + if (this[0]) { + + if (isString(args[0])) { + constr = args[0]; + args = Array.prototype.slice.call(args, 1); + } + options = args[0]; + + // Create the chart + if (options !== UNDEFINED) { + /*jslint unused:false*/ + options.chart = options.chart || {}; + options.chart.renderTo = this[0]; + chart = new Highcharts[constr](options, args[1]); + ret = this; + /*jslint unused:true*/ + } + + // When called without parameters or with the return argument, get a predefined chart + if (options === UNDEFINED) { + ret = charts[attr(this[0], 'data-highcharts-chart')]; + } + } + + return ret; + }; + + }, + + + /** + * Downloads a script and executes a callback when done. + * @param {String} scriptLocation + * @param {Function} callback + */ + getScript: $.getScript, + + /** + * Return the index of an item in an array, or -1 if not found + */ + inArray: $.inArray, + + /** + * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method. + * @param {Object} elem The HTML element + * @param {String} method Which method to run on the wrapped element + */ + adapterRun: function (elem, method) { + return $(elem)[method](); + }, + + /** + * Filter an array + */ + grep: $.grep, + + /** + * Map an array + * @param {Array} arr + * @param {Function} fn + */ + map: function (arr, fn) { + //return jQuery.map(arr, fn); + var results = [], + i = 0, + len = arr.length; + for (; i < len; i++) { + results[i] = fn.call(arr[i], arr[i], i, arr); + } + return results; + + }, + + /** + * Get the position of an element relative to the top left of the page + */ + offset: function (el) { + return $(el).offset(); + }, + + /** + * Add an event listener + * @param {Object} el A HTML element or custom object + * @param {String} event The event type + * @param {Function} fn The event handler + */ + addEvent: function (el, event, fn) { + $(el).bind(event, fn); + }, + + /** + * Remove event added with addEvent + * @param {Object} el The object + * @param {String} eventType The event type. Leave blank to remove all events. + * @param {Function} handler The function to remove + */ + removeEvent: function (el, eventType, handler) { + // workaround for jQuery issue with unbinding custom events: + // http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2 + var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent'; + if (doc[func] && el && !el[func]) { + el[func] = function () {}; + } + + $(el).unbind(eventType, handler); + }, + + /** + * Fire an event on a custom object + * @param {Object} el + * @param {String} type + * @param {Object} eventArguments + * @param {Function} defaultFunction + */ + fireEvent: function (el, type, eventArguments, defaultFunction) { + var event = $.Event(type), + detachedType = 'detached' + type, + defaultPrevented; + + // Remove warnings in Chrome when accessing returnValue (#2790), layerX and layerY. Although Highcharts + // never uses these properties, Chrome includes them in the default click event and + // raises the warning when they are copied over in the extend statement below. + // + // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid + // testing if they are there (warning in chrome) the only option is to test if running IE. + if (!isIE && eventArguments) { + delete eventArguments.layerX; + delete eventArguments.layerY; + delete eventArguments.returnValue; + } + + extend(event, eventArguments); + + // Prevent jQuery from triggering the object method that is named the + // same as the event. For example, if the event is 'select', jQuery + // attempts calling el.select and it goes into a loop. + if (el[type]) { + el[detachedType] = el[type]; + el[type] = null; + } + + // Wrap preventDefault and stopPropagation in try/catch blocks in + // order to prevent JS errors when cancelling events on non-DOM + // objects. #615. + /*jslint unparam: true*/ + $.each(['preventDefault', 'stopPropagation'], function (i, fn) { + var base = event[fn]; + event[fn] = function () { + try { + base.call(event); + } catch (e) { + if (fn === 'preventDefault') { + defaultPrevented = true; + } + } + }; + }); + /*jslint unparam: false*/ + + // trigger it + $(el).trigger(event); + + // attach the method + if (el[detachedType]) { + el[type] = el[detachedType]; + el[detachedType] = null; + } + + if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) { + defaultFunction(event); + } + }, + + /** + * Extension method needed for MooTools + */ + washMouseEvent: function (e) { + var ret = e.originalEvent || e; + + // computed by jQuery, needed by IE8 + if (ret.pageX === UNDEFINED) { // #1236 + ret.pageX = e.pageX; + ret.pageY = e.pageY; + } + + return ret; + }, + + /** + * Animate a HTML element or SVG element wrapper + * @param {Object} el + * @param {Object} params + * @param {Object} options jQuery-like animation options: duration, easing, callback + */ + animate: function (el, params, options) { + var $el = $(el); + if (!el.style) { + el.style = {}; // #1881 + } + if (params.d) { + el.toD = params.d; // keep the array form for paths, used in $.fx.step.d + params.d = 1; // because in jQuery, animating to an array has a different meaning + } + + $el.stop(); + if (params.opacity !== UNDEFINED && el.attr) { + params.opacity += 'px'; // force jQuery to use same logic as width and height (#2161) + } + $el.animate(params, options); + + }, + /** + * Stop running animation + */ + stop: function (el) { + $(el).stop(); + } + }); +}(win.jQuery)); + + +// check for a custom HighchartsAdapter defined prior to this file +var globalAdapter = win.HighchartsAdapter, + adapter = globalAdapter || {}; + +// Initialize the adapter +if (globalAdapter) { + globalAdapter.init.call(globalAdapter, pathAnim); +} + + +// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object +// and all the utility functions will be null. In that case they are populated by the +// default adapters below. +var adapterRun = adapter.adapterRun, + getScript = adapter.getScript, + inArray = adapter.inArray, + each = adapter.each, + grep = adapter.grep, + offset = adapter.offset, + map = adapter.map, + addEvent = adapter.addEvent, + removeEvent = adapter.removeEvent, + fireEvent = adapter.fireEvent, + washMouseEvent = adapter.washMouseEvent, + animate = adapter.animate, + stop = adapter.stop; + + + +/* **************************************************************************** + * Handle the options * + *****************************************************************************/ +var + +defaultLabelOptions = { + enabled: true, + // rotation: 0, + // align: 'center', + x: 0, + y: 15, + /*formatter: function () { + return this.value; + },*/ + style: { + color: '#606060', + cursor: 'default', + fontSize: '11px' + } +}; + +defaultOptions = { + colors: ['#7cb5ec', '#434348', '#90ed7d', '#f7a35c', + '#8085e9', '#f15c80', '#e4d354', '#8085e8', '#8d4653', '#91e8e1'], // docs + symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'], + lang: { + loading: 'Loading...', + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', + 'August', 'September', 'October', 'November', 'December'], + shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + decimalPoint: '.', + numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels + resetZoom: 'Reset zoom', + resetZoomTitle: 'Reset zoom level 1:1', + thousandsSep: ',' + }, + global: { + useUTC: true, + //timezoneOffset: 0, + canvasToolsURL: 'http://code.highcharts.com/4.0.1/modules/canvas-tools.js', + VMLRadialGradientURL: 'http://code.highcharts.com/4.0.1/gfx/vml-radial-gradient.png' + }, + chart: { + //animation: true, + //alignTicks: false, + //reflow: true, + //className: null, + //events: { load, selection }, + //margin: [null], + //marginTop: null, + //marginRight: null, + //marginBottom: null, + //marginLeft: null, + borderColor: '#4572A7', + //borderWidth: 0, + borderRadius: 0, + defaultSeriesType: 'line', + ignoreHiddenSeries: true, + //inverted: false, + //shadow: false, + spacing: [10, 10, 15, 10], + //spacingTop: 10, + //spacingRight: 10, + //spacingBottom: 15, + //spacingLeft: 10, + //style: { + // fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font + // fontSize: '12px' + //}, + backgroundColor: '#FFFFFF', + //plotBackgroundColor: null, + plotBorderColor: '#C0C0C0', + //plotBorderWidth: 0, + //plotShadow: false, + //zoomType: '' + resetZoomButton: { + theme: { + zIndex: 20 + }, + position: { + align: 'right', + x: -10, + //verticalAlign: 'top', + y: 10 + } + // relativeTo: 'plot' + } + }, + title: { + text: 'Chart title', + align: 'center', + // floating: false, + margin: 15, + // x: 0, + // verticalAlign: 'top', + // y: null, + style: { + color: '#333333', // docs + fontSize: '18px' + } + + }, + subtitle: { + text: '', + align: 'center', + // floating: false + // x: 0, + // verticalAlign: 'top', + // y: null, + style: { + color: '#555555' // docs + } + }, + + plotOptions: { + line: { // base series options + allowPointSelect: false, + showCheckbox: false, + animation: { + duration: 1000 + }, + //connectNulls: false, + //cursor: 'default', + //clip: true, + //dashStyle: null, + //enableMouseTracking: true, + events: {}, + //legendIndex: 0, + //linecap: 'round', + lineWidth: 2, + //shadow: false, + // stacking: null, + marker: { + //enabled: true, + //symbol: null, + lineWidth: 0, + radius: 4, + lineColor: '#FFFFFF', + //fillColor: null, + states: { // states for a single point + hover: { + enabled: true + //radius: base + 2 + }, + select: { + fillColor: '#FFFFFF', + lineColor: '#000000', + lineWidth: 2 + } + } + }, + point: { + events: {} + }, + dataLabels: merge(defaultLabelOptions, { + align: 'center', + //defer: true, + enabled: false, + formatter: function () { + return this.y === null ? '' : numberFormat(this.y, -1); + }, + verticalAlign: 'bottom', // above singular point + y: 0 + // backgroundColor: undefined, + // borderColor: undefined, + // borderRadius: undefined, + // borderWidth: undefined, + // padding: 3, + // shadow: false + }), + cropThreshold: 300, // draw points outside the plot area when the number of points is less than this + pointRange: 0, + //pointStart: 0, + //pointInterval: 1, + //showInLegend: null, // auto: true for standalone series, false for linked series + states: { // states for the entire series + hover: { + //enabled: false, + //lineWidth: base + 1, + marker: { + // lineWidth: base + 1, + // radius: base + 1 + }, + halo: { + size: 10, + opacity: 0.25 + } + }, + select: { + marker: {} + } + }, + stickyTracking: true, + //tooltip: { + //pointFormat: '\u25CF {series.name}: {point.y}' + //valueDecimals: null, + //xDateFormat: '%A, %b %e, %Y', + //valuePrefix: '', + //ySuffix: '' + //} + turboThreshold: 1000 + // zIndex: null + } + }, + labels: { + //items: [], + style: { + //font: defaultFont, + position: ABSOLUTE, + color: '#3E576F' + } + }, + legend: { + enabled: true, + align: 'center', + //floating: false, + layout: 'horizontal', + labelFormatter: function () { + return this.name; + }, + //borderWidth: 0, + borderColor: '#909090', + borderRadius: 0, // docs + navigation: { + // animation: true, + activeColor: '#274b6d', + // arrowSize: 12 + inactiveColor: '#CCC' + // style: {} // text styles + }, + // margin: 20, + // reversed: false, + shadow: false, + // backgroundColor: null, + /*style: { + padding: '5px' + },*/ + itemStyle: { + color: '#333333', // docs + fontSize: '12px', + fontWeight: 'bold' // docs + }, + itemHoverStyle: { + //cursor: 'pointer', removed as of #601 + color: '#000' + }, + itemHiddenStyle: { + color: '#CCC' + }, + itemCheckboxStyle: { + position: ABSOLUTE, + width: '13px', // for IE precision + height: '13px' + }, + // itemWidth: undefined, + // symbolRadius: 0, + // symbolWidth: 16, + symbolPadding: 5, + verticalAlign: 'bottom', + // width: undefined, + x: 0, + y: 0, + title: { + //text: null, + style: { + fontWeight: 'bold' + } + } + }, + + loading: { + // hideDuration: 100, + labelStyle: { + fontWeight: 'bold', + position: RELATIVE, + top: '1em' + }, + // showDuration: 0, + style: { + position: ABSOLUTE, + backgroundColor: 'white', + opacity: 0.5, + textAlign: 'center' + } + }, + + tooltip: { + enabled: true, + animation: hasSVG, + //crosshairs: null, + backgroundColor: 'rgba(249, 249, 249, .85)', + borderWidth: 1, + borderRadius: 3, + dateTimeLabelFormats: { + millisecond: '%A, %b %e, %H:%M:%S.%L', + second: '%A, %b %e, %H:%M:%S', + minute: '%A, %b %e, %H:%M', + hour: '%A, %b %e, %H:%M', + day: '%A, %b %e, %Y', + week: 'Week from %A, %b %e, %Y', + month: '%B %Y', + year: '%Y' + }, + //formatter: defaultFormatter, + headerFormat: '{point.key}
', + pointFormat: '\u25CF {series.name}: {point.y}
', // docs + shadow: true, + //shape: 'calout', + //shared: false, + snap: isTouchDevice ? 25 : 10, + style: { + color: '#333333', + cursor: 'default', + fontSize: '12px', + padding: '8px', + whiteSpace: 'nowrap' + } + //xDateFormat: '%A, %b %e, %Y', + //valueDecimals: null, + //valuePrefix: '', + //valueSuffix: '' + }, + + credits: { + enabled: true, + text: 'Highcharts.com', + href: 'http://www.highcharts.com', + position: { + align: 'right', + x: -10, + verticalAlign: 'bottom', + y: -5 + }, + style: { + cursor: 'pointer', + color: '#909090', + fontSize: '9px' + } + } +}; + + + + +// Series defaults +var defaultPlotOptions = defaultOptions.plotOptions, + defaultSeriesOptions = defaultPlotOptions.line; + +// set the default time methods +setTimeMethods(); + + + +/** + * Set the time methods globally based on the useUTC option. Time method can be either + * local time or UTC (default). + */ +function setTimeMethods() { + var useUTC = defaultOptions.global.useUTC, + GET = useUTC ? 'getUTC' : 'get', + SET = useUTC ? 'setUTC' : 'set'; + + + timezoneOffset = ((useUTC && defaultOptions.global.timezoneOffset) || 0) * 60000; + makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) { + return new Date( + year, + month, + pick(date, 1), + pick(hours, 0), + pick(minutes, 0), + pick(seconds, 0) + ).getTime(); + }; + getMinutes = GET + 'Minutes'; + getHours = GET + 'Hours'; + getDay = GET + 'Day'; + getDate = GET + 'Date'; + getMonth = GET + 'Month'; + getFullYear = GET + 'FullYear'; + setMinutes = SET + 'Minutes'; + setHours = SET + 'Hours'; + setDate = SET + 'Date'; + setMonth = SET + 'Month'; + setFullYear = SET + 'FullYear'; + +} + +/** + * Merge the default options with custom options and return the new options structure + * @param {Object} options The new custom options + */ +function setOptions(options) { + + // Copy in the default options + defaultOptions = merge(true, defaultOptions, options); + + // Apply UTC + setTimeMethods(); + + return defaultOptions; +} + +/** + * Get the updated default options. Until 3.0.7, merely exposing defaultOptions for outside modules + * wasn't enough because the setOptions method created a new object. + */ +function getOptions() { + return defaultOptions; +} + + +/** + * Handle color operations. The object methods are chainable. + * @param {String} input The input color in either rbga or hex format + */ +var rgbaRegEx = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/, + hexRegEx = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, + rgbRegEx = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/; + +var Color = function (input) { + // declare variables + var rgba = [], result, stops; + + /** + * Parse the input color to rgba array + * @param {String} input + */ + function init(input) { + + // Gradients + if (input && input.stops) { + stops = map(input.stops, function (stop) { + return Color(stop[1]); + }); + + // Solid colors + } else { + // rgba + result = rgbaRegEx.exec(input); + if (result) { + rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)]; + } else { + // hex + result = hexRegEx.exec(input); + if (result) { + rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1]; + } else { + // rgb + result = rgbRegEx.exec(input); + if (result) { + rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1]; + } + } + } + } + + } + /** + * Return the color a specified format + * @param {String} format + */ + function get(format) { + var ret; + + if (stops) { + ret = merge(input); + ret.stops = [].concat(ret.stops); + each(stops, function (stop, i) { + ret.stops[i] = [ret.stops[i][0], stop.get(format)]; + }); + + // it's NaN if gradient colors on a column chart + } else if (rgba && !isNaN(rgba[0])) { + if (format === 'rgb') { + ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')'; + } else if (format === 'a') { + ret = rgba[3]; + } else { + ret = 'rgba(' + rgba.join(',') + ')'; + } + } else { + ret = input; + } + return ret; + } + + /** + * Brighten the color + * @param {Number} alpha + */ + function brighten(alpha) { + if (stops) { + each(stops, function (stop) { + stop.brighten(alpha); + }); + + } else if (isNumber(alpha) && alpha !== 0) { + var i; + for (i = 0; i < 3; i++) { + rgba[i] += pInt(alpha * 255); + + if (rgba[i] < 0) { + rgba[i] = 0; + } + if (rgba[i] > 255) { + rgba[i] = 255; + } + } + } + return this; + } + /** + * Set the color's opacity to a given alpha value + * @param {Number} alpha + */ + function setOpacity(alpha) { + rgba[3] = alpha; + return this; + } + + // initialize: parse the input + init(input); + + // public methods + return { + get: get, + brighten: brighten, + rgba: rgba, + setOpacity: setOpacity + }; +}; + + +/** + * A wrapper object for SVG elements + */ +function SVGElement() {} + +SVGElement.prototype = { + /** + * Initialize the SVG renderer + * @param {Object} renderer + * @param {String} nodeName + */ + init: function (renderer, nodeName) { + var wrapper = this; + wrapper.element = nodeName === 'span' ? + createElement(nodeName) : + doc.createElementNS(SVG_NS, nodeName); + wrapper.renderer = renderer; + }, + /** + * Default base for animation + */ + opacity: 1, + /** + * Animate a given attribute + * @param {Object} params + * @param {Number} options The same options as in jQuery animation + * @param {Function} complete Function to perform at the end of animation + */ + animate: function (params, options, complete) { + var animOptions = pick(options, globalAnimation, true); + stop(this); // stop regardless of animation actually running, or reverting to .attr (#607) + if (animOptions) { + animOptions = merge(animOptions, {}); //#2625 + if (complete) { // allows using a callback with the global animation without overwriting it + animOptions.complete = complete; + } + animate(this, params, animOptions); + } else { + this.attr(params); + if (complete) { + complete(); + } + } + }, + + /** + * Build an SVG gradient out of a common JavaScript configuration object + */ + colorGradient: function (color, prop, elem) { + var renderer = this.renderer, + colorObject, + gradName, + gradAttr, + gradients, + gradientObject, + stops, + stopColor, + stopOpacity, + radialReference, + n, + id, + key = []; + + // Apply linear or radial gradients + if (color.linearGradient) { + gradName = 'linearGradient'; + } else if (color.radialGradient) { + gradName = 'radialGradient'; + } + + if (gradName) { + gradAttr = color[gradName]; + gradients = renderer.gradients; + stops = color.stops; + radialReference = elem.radialReference; + + // Keep < 2.2 kompatibility + if (isArray(gradAttr)) { + color[gradName] = gradAttr = { + x1: gradAttr[0], + y1: gradAttr[1], + x2: gradAttr[2], + y2: gradAttr[3], + gradientUnits: 'userSpaceOnUse' + }; + } + + // Correct the radial gradient for the radial reference system + if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) { + gradAttr = merge(gradAttr, { + cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2], + cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2], + r: gradAttr.r * radialReference[2], + gradientUnits: 'userSpaceOnUse' + }); + } + + // Build the unique key to detect whether we need to create a new element (#1282) + for (n in gradAttr) { + if (n !== 'id') { + key.push(n, gradAttr[n]); + } + } + for (n in stops) { + key.push(stops[n]); + } + key = key.join(','); + + // Check if a gradient object with the same config object is created within this renderer + if (gradients[key]) { + id = gradients[key].attr('id'); + + } else { + + // Set the id and create the element + gradAttr.id = id = PREFIX + idCounter++; + gradients[key] = gradientObject = renderer.createElement(gradName) + .attr(gradAttr) + .add(renderer.defs); + + + // The gradient needs to keep a list of stops to be able to destroy them + gradientObject.stops = []; + each(stops, function (stop) { + var stopObject; + if (stop[1].indexOf('rgba') === 0) { + colorObject = Color(stop[1]); + stopColor = colorObject.get('rgb'); + stopOpacity = colorObject.get('a'); + } else { + stopColor = stop[1]; + stopOpacity = 1; + } + stopObject = renderer.createElement('stop').attr({ + offset: stop[0], + 'stop-color': stopColor, + 'stop-opacity': stopOpacity + }).add(gradientObject); + + // Add the stop element to the gradient + gradientObject.stops.push(stopObject); + }); + } + + // Set the reference to the gradient object + elem.setAttribute(prop, 'url(' + renderer.url + '#' + id + ')'); + } + }, + + /** + * Set or get a given attribute + * @param {Object|String} hash + * @param {Mixed|Undefined} val + */ + attr: function (hash, val) { + var key, + value, + element = this.element, + hasSetSymbolSize, + ret = this, + skipAttr; + + // single key-value pair + if (typeof hash === 'string' && val !== UNDEFINED) { + key = hash; + hash = {}; + hash[key] = val; + } + + // used as a getter: first argument is a string, second is undefined + if (typeof hash === 'string') { + ret = (this[hash + 'Getter'] || this._defaultGetter).call(this, hash, element); + + // setter + } else { + + for (key in hash) { + value = hash[key]; + skipAttr = false; + + + + if (this.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) { + if (!hasSetSymbolSize) { + this.symbolAttr(hash); + hasSetSymbolSize = true; + } + skipAttr = true; + } + + if (this.rotation && (key === 'x' || key === 'y')) { + this.doTransform = true; + } + + if (!skipAttr) { + (this[key + 'Setter'] || this._defaultSetter).call(this, value, key, element); + } + + // Let the shadow follow the main element + if (this.shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) { + this.updateShadows(key, value); + } + } + + // Update transform. Do this outside the loop to prevent redundant updating for batch setting + // of attributes. + if (this.doTransform) { + this.updateTransform(); + this.doTransform = false; + } + + } + + return ret; + }, + + updateShadows: function (key, value) { + var shadows = this.shadows, + i = shadows.length; + while (i--) { + shadows[i].setAttribute( + key, + key === 'height' ? + mathMax(value - (shadows[i].cutHeight || 0), 0) : + key === 'd' ? this.d : value + ); + } + }, + + /** + * Add a class name to an element + */ + addClass: function (className) { + var element = this.element, + currentClassName = attr(element, 'class') || ''; + + if (currentClassName.indexOf(className) === -1) { + attr(element, 'class', currentClassName + ' ' + className); + } + return this; + }, + /* hasClass and removeClass are not (yet) needed + hasClass: function (className) { + return attr(this.element, 'class').indexOf(className) !== -1; + }, + removeClass: function (className) { + attr(this.element, 'class', attr(this.element, 'class').replace(className, '')); + return this; + }, + */ + + /** + * If one of the symbol size affecting parameters are changed, + * check all the others only once for each call to an element's + * .attr() method + * @param {Object} hash + */ + symbolAttr: function (hash) { + var wrapper = this; + + each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) { + wrapper[key] = pick(hash[key], wrapper[key]); + }); + + wrapper.attr({ + d: wrapper.renderer.symbols[wrapper.symbolName]( + wrapper.x, + wrapper.y, + wrapper.width, + wrapper.height, + wrapper + ) + }); + }, + + /** + * Apply a clipping path to this object + * @param {String} id + */ + clip: function (clipRect) { + return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE); + }, + + /** + * Calculate the coordinates needed for drawing a rectangle crisply and return the + * calculated attributes + * @param {Number} strokeWidth + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + crisp: function (rect) { + + var wrapper = this, + key, + attribs = {}, + normalizer, + strokeWidth = rect.strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0; + + normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors + + // normalize for crisp edges + rect.x = mathFloor(rect.x || wrapper.x || 0) + normalizer; + rect.y = mathFloor(rect.y || wrapper.y || 0) + normalizer; + rect.width = mathFloor((rect.width || wrapper.width || 0) - 2 * normalizer); + rect.height = mathFloor((rect.height || wrapper.height || 0) - 2 * normalizer); + rect.strokeWidth = strokeWidth; + + for (key in rect) { + if (wrapper[key] !== rect[key]) { // only set attribute if changed + wrapper[key] = attribs[key] = rect[key]; + } + } + + return attribs; + }, + + /** + * Set styles for the element + * @param {Object} styles + */ + css: function (styles) { + var elemWrapper = this, + oldStyles = elemWrapper.styles, + newStyles = {}, + elem = elemWrapper.element, + textWidth, + n, + serializedCss = '', + hyphenate, + hasNew = !oldStyles; + + // convert legacy + if (styles && styles.color) { + styles.fill = styles.color; + } + + // Filter out existing styles to increase performance (#2640) + if (oldStyles) { + for (n in styles) { + if (styles[n] !== oldStyles[n]) { + newStyles[n] = styles[n]; + hasNew = true; + } + } + } + if (hasNew) { + textWidth = elemWrapper.textWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text' && pInt(styles.width); + + // Merge the new styles with the old ones + if (oldStyles) { + styles = extend( + oldStyles, + newStyles + ); + } + + // store object + elemWrapper.styles = styles; + + if (textWidth && (useCanVG || (!hasSVG && elemWrapper.renderer.forExport))) { + delete styles.width; + } + + // serialize and set style attribute + if (isIE && !hasSVG) { + css(elemWrapper.element, styles); + } else { + /*jslint unparam: true*/ + hyphenate = function (a, b) { return '-' + b.toLowerCase(); }; + /*jslint unparam: false*/ + for (n in styles) { + serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';'; + } + attr(elem, 'style', serializedCss); // #1881 + } + + + // re-build text + if (textWidth && elemWrapper.added) { + elemWrapper.renderer.buildText(elemWrapper); + } + } + + return elemWrapper; + }, + + /** + * Add an event listener + * @param {String} eventType + * @param {Function} handler + */ + on: function (eventType, handler) { + var svgElement = this, + element = svgElement.element; + + // touch + if (hasTouch && eventType === 'click') { + element.ontouchstart = function (e) { + svgElement.touchEventFired = Date.now(); + e.preventDefault(); + handler.call(element, e); + }; + element.onclick = function (e) { + if (userAgent.indexOf('Android') === -1 || Date.now() - (svgElement.touchEventFired || 0) > 1100) { // #2269 + handler.call(element, e); + } + }; + } else { + // simplest possible event model for internal use + element['on' + eventType] = handler; + } + return this; + }, + + /** + * Set the coordinates needed to draw a consistent radial gradient across + * pie slices regardless of positioning inside the chart. The format is + * [centerX, centerY, diameter] in pixels. + */ + setRadialReference: function (coordinates) { + this.element.radialReference = coordinates; + return this; + }, + + /** + * Move an object and its children by x and y values + * @param {Number} x + * @param {Number} y + */ + translate: function (x, y) { + return this.attr({ + translateX: x, + translateY: y + }); + }, + + /** + * Invert a group, rotate and flip + */ + invert: function () { + var wrapper = this; + wrapper.inverted = true; + wrapper.updateTransform(); + return wrapper; + }, + + /** + * Private method to update the transform attribute based on internal + * properties + */ + updateTransform: function () { + var wrapper = this, + translateX = wrapper.translateX || 0, + translateY = wrapper.translateY || 0, + scaleX = wrapper.scaleX, + scaleY = wrapper.scaleY, + inverted = wrapper.inverted, + rotation = wrapper.rotation, + element = wrapper.element, + transform; + + // flipping affects translate as adjustment for flipping around the group's axis + if (inverted) { + translateX += wrapper.attr('width'); + translateY += wrapper.attr('height'); + } + + // Apply translate. Nearly all transformed elements have translation, so instead + // of checking for translate = 0, do it always (#1767, #1846). + transform = ['translate(' + translateX + ',' + translateY + ')']; + + // apply rotation + if (inverted) { + transform.push('rotate(90) scale(-1,1)'); + } else if (rotation) { // text rotation + transform.push('rotate(' + rotation + ' ' + (element.getAttribute('x') || 0) + ' ' + (element.getAttribute('y') || 0) + ')'); + } + + // apply scale + if (defined(scaleX) || defined(scaleY)) { + transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'); + } + + if (transform.length) { + element.setAttribute('transform', transform.join(' ')); + } + }, + /** + * Bring the element to the front + */ + toFront: function () { + var element = this.element; + element.parentNode.appendChild(element); + return this; + }, + + + /** + * Break down alignment options like align, verticalAlign, x and y + * to x and y relative to the chart. + * + * @param {Object} alignOptions + * @param {Boolean} alignByTranslate + * @param {String[Object} box The box to align to, needs a width and height. When the + * box is a string, it refers to an object in the Renderer. For example, when + * box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height + * x and y properties. + * + */ + align: function (alignOptions, alignByTranslate, box) { + var align, + vAlign, + x, + y, + attribs = {}, + alignTo, + renderer = this.renderer, + alignedObjects = renderer.alignedObjects; + + // First call on instanciate + if (alignOptions) { + this.alignOptions = alignOptions; + this.alignByTranslate = alignByTranslate; + if (!box || isString(box)) { // boxes other than renderer handle this internally + this.alignTo = alignTo = box || 'renderer'; + erase(alignedObjects, this); // prevent duplicates, like legendGroup after resize + alignedObjects.push(this); + box = null; // reassign it below + } + + // When called on resize, no arguments are supplied + } else { + alignOptions = this.alignOptions; + alignByTranslate = this.alignByTranslate; + alignTo = this.alignTo; + } + + box = pick(box, renderer[alignTo], renderer); + + // Assign variables + align = alignOptions.align; + vAlign = alignOptions.verticalAlign; + x = (box.x || 0) + (alignOptions.x || 0); // default: left align + y = (box.y || 0) + (alignOptions.y || 0); // default: top align + + // Align + if (align === 'right' || align === 'center') { + x += (box.width - (alignOptions.width || 0)) / + { right: 1, center: 2 }[align]; + } + attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x); + + + // Vertical align + if (vAlign === 'bottom' || vAlign === 'middle') { + y += (box.height - (alignOptions.height || 0)) / + ({ bottom: 1, middle: 2 }[vAlign] || 1); + + } + attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y); + + // Animate only if already placed + this[this.placed ? 'animate' : 'attr'](attribs); + this.placed = true; + this.alignAttr = attribs; + + return this; + }, + + /** + * Get the bounding box (width, height, x and y) for the element + */ + getBBox: function () { + var wrapper = this, + bBox = wrapper.bBox, + renderer = wrapper.renderer, + width, + height, + rotation = wrapper.rotation, + element = wrapper.element, + styles = wrapper.styles, + rad = rotation * deg2rad, + textStr = wrapper.textStr, + cacheKey; + + // Since numbers are monospaced, and numerical labels appear a lot in a chart, + // we assume that a label of n characters has the same bounding box as others + // of the same length. + if (textStr === '' || numRegex.test(textStr)) { + cacheKey = 'num.' + textStr.toString().length + (styles ? ('|' + styles.fontSize + '|' + styles.fontFamily) : ''); + + } //else { // This code block made demo/waterfall fail, related to buildText + // Caching all strings reduces rendering time by 4-5%. + // TODO: Check how this affects places where bBox is found on the element + //cacheKey = textStr + (styles ? ('|' + styles.fontSize + '|' + styles.fontFamily) : ''); + //} + if (cacheKey) { + bBox = renderer.cache[cacheKey]; + } + + // No cache found + if (!bBox) { + + // SVG elements + if (element.namespaceURI === SVG_NS || renderer.forExport) { + try { // Fails in Firefox if the container has display: none. + + bBox = element.getBBox ? + // SVG: use extend because IE9 is not allowed to change width and height in case + // of rotation (below) + extend({}, element.getBBox()) : + // Canvas renderer and legacy IE in export mode + { + width: element.offsetWidth, + height: element.offsetHeight + }; + } catch (e) {} + + // If the bBox is not set, the try-catch block above failed. The other condition + // is for Opera that returns a width of -Infinity on hidden elements. + if (!bBox || bBox.width < 0) { + bBox = { width: 0, height: 0 }; + } + + + // VML Renderer or useHTML within SVG + } else { + + bBox = wrapper.htmlGetBBox(); + + } + + // True SVG elements as well as HTML elements in modern browsers using the .useHTML option + // need to compensated for rotation + if (renderer.isSVG) { + width = bBox.width; + height = bBox.height; + + // Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669, #2568) + if (isIE && styles && styles.fontSize === '11px' && height.toPrecision(3) === '16.9') { + bBox.height = height = 14; + } + + // Adjust for rotated text + if (rotation) { + bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad)); + bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad)); + } + } + + // Cache it + wrapper.bBox = bBox; + if (cacheKey) { + renderer.cache[cacheKey] = bBox; + } + } + return bBox; + }, + + /** + * Show the element + */ + show: function (inherit) { + // IE9-11 doesn't handle visibilty:inherit well, so we remove the attribute instead (#2881) + if (inherit && this.element.namespaceURI === SVG_NS) { + this.element.removeAttribute('visibility'); + return this; + } else { + return this.attr({ visibility: inherit ? 'inherit' : VISIBLE }); + } + }, + + /** + * Hide the element + */ + hide: function () { + return this.attr({ visibility: HIDDEN }); + }, + + fadeOut: function (duration) { + var elemWrapper = this; + elemWrapper.animate({ + opacity: 0 + }, { + duration: duration || 150, + complete: function () { + elemWrapper.hide(); + } + }); + }, + + /** + * Add the element + * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined + * to append the element to the renderer.box. + */ + add: function (parent) { + + var renderer = this.renderer, + parentWrapper = parent || renderer, + parentNode = parentWrapper.element || renderer.box, + childNodes, + element = this.element, + zIndex = this.zIndex, + otherElement, + otherZIndex, + i, + inserted; + + if (parent) { + this.parentGroup = parent; + } + + // mark as inverted + this.parentInverted = parent && parent.inverted; + + // build formatted text + if (this.textStr !== undefined) { + renderer.buildText(this); + } + + // mark the container as having z indexed children + if (zIndex) { + parentWrapper.handleZ = true; + zIndex = pInt(zIndex); + } + + // insert according to this and other elements' zIndex + if (parentWrapper.handleZ) { // this element or any of its siblings has a z index + childNodes = parentNode.childNodes; + for (i = 0; i < childNodes.length; i++) { + otherElement = childNodes[i]; + otherZIndex = attr(otherElement, 'zIndex'); + if (otherElement !== element && ( + // insert before the first element with a higher zIndex + pInt(otherZIndex) > zIndex || + // if no zIndex given, insert before the first element with a zIndex + (!defined(zIndex) && defined(otherZIndex)) + + )) { + parentNode.insertBefore(element, otherElement); + inserted = true; + break; + } + } + } + + // default: append at the end + if (!inserted) { + parentNode.appendChild(element); + } + + // mark as added + this.added = true; + + // fire an event for internal hooks + if (this.onAdd) { + this.onAdd(); + } + + return this; + }, + + /** + * Removes a child either by removeChild or move to garbageBin. + * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. + */ + safeRemoveChild: function (element) { + var parentNode = element.parentNode; + if (parentNode) { + parentNode.removeChild(element); + } + }, + + /** + * Destroy the element and element wrapper + */ + destroy: function () { + var wrapper = this, + element = wrapper.element || {}, + shadows = wrapper.shadows, + parentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && wrapper.parentGroup, + grandParent, + key, + i; + + // remove events + element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null; + stop(wrapper); // stop running animations + + if (wrapper.clipPath) { + wrapper.clipPath = wrapper.clipPath.destroy(); + } + + // Destroy stops in case this is a gradient object + if (wrapper.stops) { + for (i = 0; i < wrapper.stops.length; i++) { + wrapper.stops[i] = wrapper.stops[i].destroy(); + } + wrapper.stops = null; + } + + // remove element + wrapper.safeRemoveChild(element); + + // destroy shadows + if (shadows) { + each(shadows, function (shadow) { + wrapper.safeRemoveChild(shadow); + }); + } + + // In case of useHTML, clean up empty containers emulating SVG groups (#1960, #2393). + while (parentToClean && parentToClean.div.childNodes.length === 0) { + grandParent = parentToClean.parentGroup; + wrapper.safeRemoveChild(parentToClean.div); + delete parentToClean.div; + parentToClean = grandParent; + } + + // remove from alignObjects + if (wrapper.alignTo) { + erase(wrapper.renderer.alignedObjects, wrapper); + } + + for (key in wrapper) { + delete wrapper[key]; + } + + return null; + }, + + /** + * Add a shadow to the element. Must be done after the element is added to the DOM + * @param {Boolean|Object} shadowOptions + */ + shadow: function (shadowOptions, group, cutOff) { + var shadows = [], + i, + shadow, + element = this.element, + strokeWidth, + shadowWidth, + shadowElementOpacity, + + // compensate for inverted plot area + transform; + + + if (shadowOptions) { + shadowWidth = pick(shadowOptions.width, 3); + shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; + transform = this.parentInverted ? + '(-1,-1)' : + '(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')'; + for (i = 1; i <= shadowWidth; i++) { + shadow = element.cloneNode(0); + strokeWidth = (shadowWidth * 2) + 1 - (2 * i); + attr(shadow, { + 'isShadow': 'true', + 'stroke': shadowOptions.color || 'black', + 'stroke-opacity': shadowElementOpacity * i, + 'stroke-width': strokeWidth, + 'transform': 'translate' + transform, + 'fill': NONE + }); + if (cutOff) { + attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0)); + shadow.cutHeight = strokeWidth; + } + + if (group) { + group.element.appendChild(shadow); + } else { + element.parentNode.insertBefore(shadow, element); + } + + shadows.push(shadow); + } + + this.shadows = shadows; + } + return this; + + }, + + xGetter: function (key) { + if (this.element.nodeName === 'circle') { + key = { x: 'cx', y: 'cy' }[key] || key; + } + return this._defaultGetter(key); + }, + + /** + * Get the current value of an attribute or pseudo attribute, used mainly + * for animation. + */ + _defaultGetter: function (key) { + var ret = pick(this[key], this.element ? this.element.getAttribute(key) : null, 0); + + if (/^[0-9\.]+$/.test(ret)) { // is numerical + ret = parseFloat(ret); + } + return ret; + }, + + + dSetter: function (value, key, element) { + if (value && value.join) { // join path + value = value.join(' '); + } + if (/(NaN| {2}|^$)/.test(value)) { + value = 'M 0 0'; + } + element.setAttribute(key, value); + + this[key] = value; + }, + dashstyleSetter: function (value) { + var i; + value = value && value.toLowerCase(); + if (value) { + value = value + .replace('shortdashdotdot', '3,1,1,1,1,1,') + .replace('shortdashdot', '3,1,1,1') + .replace('shortdot', '1,1,') + .replace('shortdash', '3,1,') + .replace('longdash', '8,3,') + .replace(/dot/g, '1,3,') + .replace('dash', '4,3,') + .replace(/,$/, '') + .split(','); // ending comma + + i = value.length; + while (i--) { + value[i] = pInt(value[i]) * this.element.getAttribute('stroke-width'); + } + value = value.join(','); + this.element.setAttribute('stroke-dasharray', value); + } + }, + alignSetter: function (value) { + this.element.setAttribute('text-anchor', { left: 'start', center: 'middle', right: 'end' }[value]); + }, + opacitySetter: function (value, key, element) { + this[key] = value; + element.setAttribute(key, value); + }, + // In Chrome/Win < 6 as well as Batik and PhantomJS as of 1.9.7, the stroke attribute can't be set when the stroke- + // width is 0. #1369 + 'stroke-widthSetter': function (value, key, element) { + if (value === 0) { + value = 0.00001; + } + this.strokeWidth = value; // read in symbol paths like 'callout' + element.setAttribute(key, value); + }, + titleSetter: function (value) { + var titleNode = this.element.getElementsByTagName('title')[0]; + if (!titleNode) { + titleNode = doc.createElementNS(SVG_NS, 'title'); + this.element.appendChild(titleNode); + } + titleNode.textContent = value; + }, + textSetter: function (value) { + if (value !== this.textStr) { + // Delete bBox memo when the text changes + delete this.bBox; + + this.textStr = value; + if (this.added) { + this.renderer.buildText(this); + } + } + }, + fillSetter: function (value, key, element) { + + if (typeof value === 'string') { + element.setAttribute(key, value); + } else if (value) { + this.colorGradient(value, key, element); + } + }, + zIndexSetter: function (value, key, element) { + element.setAttribute(key, value); + this[key] = value; + }, + _defaultSetter: function (value, key, element) { + element.setAttribute(key, value); + } +}; + +// Some shared setters and getters +SVGElement.prototype.yGetter = SVGElement.prototype.xGetter; +SVGElement.prototype.translateXSetter = SVGElement.prototype.translateYSetter = + SVGElement.prototype.rotationSetter = SVGElement.prototype.verticalAlignSetter = + SVGElement.prototype.scaleXSetter = SVGElement.prototype.scaleYSetter = function (value, key) { + this[key] = value; + this.doTransform = true; +}; +SVGElement.prototype.strokeSetter = SVGElement.prototype.fillSetter; + + + +// In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke- +// width is 0. #1369 +/*SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter = function (value, key) { + this[key] = value; + // Only apply the stroke attribute if the stroke width is defined and larger than 0 + if (this.stroke && this['stroke-width']) { + this.element.setAttribute('stroke', this.stroke); + this.element.setAttribute('stroke-width', this['stroke-width']); + this.hasStroke = true; + } else if (key === 'stroke-width' && value === 0 && this.hasStroke) { + this.element.removeAttribute('stroke'); + this.hasStroke = false; + } +};*/ + + +/** + * The default SVG renderer + */ +var SVGRenderer = function () { + this.init.apply(this, arguments); +}; +SVGRenderer.prototype = { + Element: SVGElement, + + /** + * Initialize the SVGRenderer + * @param {Object} container + * @param {Number} width + * @param {Number} height + * @param {Boolean} forExport + */ + init: function (container, width, height, style, forExport) { + var renderer = this, + loc = location, + boxWrapper, + element, + desc; + + boxWrapper = renderer.createElement('svg') + .attr({ + version: '1.1' + }) + .css(this.getStyle(style)); + element = boxWrapper.element; + container.appendChild(element); + + // For browsers other than IE, add the namespace attribute (#1978) + if (container.innerHTML.indexOf('xmlns') === -1) { + attr(element, 'xmlns', SVG_NS); + } + + // object properties + renderer.isSVG = true; + renderer.box = element; + renderer.boxWrapper = boxWrapper; + renderer.alignedObjects = []; + + // Page url used for internal references. #24, #672, #1070 + renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ? + loc.href + .replace(/#.*?$/, '') // remove the hash + .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes + .replace(/ /g, '%20') : // replace spaces (needed for Safari only) + ''; + + // Add description + desc = this.createElement('desc').add(); + desc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION)); + + + renderer.defs = this.createElement('defs').add(); + renderer.forExport = forExport; + renderer.gradients = {}; // Object where gradient SvgElements are stored + renderer.cache = {}; // Cache for numerical bounding boxes + + renderer.setSize(width, height, false); + + + + // Issue 110 workaround: + // In Firefox, if a div is positioned by percentage, its pixel position may land + // between pixels. The container itself doesn't display this, but an SVG element + // inside this container will be drawn at subpixel precision. In order to draw + // sharp lines, this must be compensated for. This doesn't seem to work inside + // iframes though (like in jsFiddle). + var subPixelFix, rect; + if (isFirefox && container.getBoundingClientRect) { + renderer.subPixelFix = subPixelFix = function () { + css(container, { left: 0, top: 0 }); + rect = container.getBoundingClientRect(); + css(container, { + left: (mathCeil(rect.left) - rect.left) + PX, + top: (mathCeil(rect.top) - rect.top) + PX + }); + }; + + // run the fix now + subPixelFix(); + + // run it on resize + addEvent(win, 'resize', subPixelFix); + } + }, + + getStyle: function (style) { + return (this.style = extend({ + fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif', // default font + fontSize: '12px' + }, style)); + }, + + /** + * Detect whether the renderer is hidden. This happens when one of the parent elements + * has display: none. #608. + */ + isHidden: function () { + return !this.boxWrapper.getBBox().width; + }, + + /** + * Destroys the renderer and its allocated members. + */ + destroy: function () { + var renderer = this, + rendererDefs = renderer.defs; + renderer.box = null; + renderer.boxWrapper = renderer.boxWrapper.destroy(); + + // Call destroy on all gradient elements + destroyObjectProperties(renderer.gradients || {}); + renderer.gradients = null; + + // Defs are null in VMLRenderer + // Otherwise, destroy them here. + if (rendererDefs) { + renderer.defs = rendererDefs.destroy(); + } + + // Remove sub pixel fix handler + // We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed + // See issue #982 + if (renderer.subPixelFix) { + removeEvent(win, 'resize', renderer.subPixelFix); + } + + renderer.alignedObjects = null; + + return null; + }, + + /** + * Create a wrapper for an SVG element + * @param {Object} nodeName + */ + createElement: function (nodeName) { + var wrapper = new this.Element(); + wrapper.init(this, nodeName); + return wrapper; + }, + + /** + * Dummy function for use in canvas renderer + */ + draw: function () {}, + + /** + * Parse a simple HTML string into SVG tspans + * + * @param {Object} textNode The parent text SVG node + */ + buildText: function (wrapper) { + var textNode = wrapper.element, + renderer = this, + forExport = renderer.forExport, + textStr = pick(wrapper.textStr, '').toString(), + hasMarkup = textStr.indexOf('<') !== -1, + lines, + childNodes = textNode.childNodes, + styleRegex, + hrefRegex, + parentX = attr(textNode, 'x'), + textStyles = wrapper.styles, + width = wrapper.textWidth, + textLineHeight = textStyles && textStyles.lineHeight, + i = childNodes.length, + getLineHeight = function (tspan) { + return textLineHeight ? + pInt(textLineHeight) : + renderer.fontMetrics( + /(px|em)$/.test(tspan && tspan.style.fontSize) ? + tspan.style.fontSize : + ((textStyles && textStyles.fontSize) || renderer.style.fontSize || 12) + ).h; + }; + + /// remove old text + while (i--) { + textNode.removeChild(childNodes[i]); + } + + // Skip tspans, add text directly to text node + if (!hasMarkup && textStr.indexOf(' ') === -1) { + textNode.appendChild(doc.createTextNode(textStr)); + return; + + // Complex strings, add more logic + } else { + + styleRegex = /<.*style="([^"]+)".*>/; + hrefRegex = /<.*href="(http[^"]+)".*>/; + + if (width && !wrapper.added) { + this.box.appendChild(textNode); // attach it to the DOM to read offset width + } + + if (hasMarkup) { + lines = textStr + .replace(/<(b|strong)>/g, '') + .replace(/<(i|em)>/g, '') + .replace(/
/g, '') + .split(//g); + + } else { + lines = [textStr]; + } + + + // remove empty line at end + if (lines[lines.length - 1] === '') { + lines.pop(); + } + + + // build the lines + each(lines, function (line, lineNo) { + var spans, spanNo = 0; + + line = line.replace(//g, '|||'); + spans = line.split('|||'); + + each(spans, function (span) { + if (span !== '' || spans.length === 1) { + var attributes = {}, + tspan = doc.createElementNS(SVG_NS, 'tspan'), + spanStyle; // #390 + if (styleRegex.test(span)) { + spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2'); + attr(tspan, 'style', spanStyle); + } + if (hrefRegex.test(span) && !forExport) { // Not for export - #1529 + attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"'); + css(tspan, { cursor: 'pointer' }); + } + + span = (span.replace(/<(.|\n)*?>/g, '') || ' ') + .replace(/</g, '<') + .replace(/>/g, '>'); + + // Nested tags aren't supported, and cause crash in Safari (#1596) + if (span !== ' ') { + + // add the text node + tspan.appendChild(doc.createTextNode(span)); + + if (!spanNo) { // first span in a line, align it to the left + if (lineNo && parentX !== null) { + attributes.x = parentX; + } + } else { + attributes.dx = 0; // #16 + } + + // add attributes + attr(tspan, attributes); + + // first span on subsequent line, add the line height + if (!spanNo && lineNo) { + + // allow getting the right offset height in exporting in IE + if (!hasSVG && forExport) { + css(tspan, { display: 'block' }); + } + + // Set the line height based on the font size of either + // the text element or the tspan element + attr( + tspan, + 'dy', + getLineHeight(tspan), + // Safari 6.0.2 - too optimized for its own good (#1539) + // TODO: revisit this with future versions of Safari + isWebKit && tspan.offsetHeight + ); + } + + // Append it + textNode.appendChild(tspan); + + spanNo++; + + // check width and apply soft breaks + if (width) { + var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273 + hasWhiteSpace = words.length > 1 && textStyles.whiteSpace !== 'nowrap', + tooLong, + actualWidth, + clipHeight = wrapper._clipHeight, + rest = [], + dy = getLineHeight(), + softLineNo = 1, + bBox; + + while (hasWhiteSpace && (words.length || rest.length)) { + delete wrapper.bBox; // delete cache + bBox = wrapper.getBBox(); + actualWidth = bBox.width; + + // Old IE cannot measure the actualWidth for SVG elements (#2314) + if (!hasSVG && renderer.forExport) { + actualWidth = renderer.measureSpanWidth(tspan.firstChild.data, wrapper.styles); + } + + tooLong = actualWidth > width; + if (!tooLong || words.length === 1) { // new line needed + words = rest; + rest = []; + if (words.length) { + softLineNo++; + + if (clipHeight && softLineNo * dy > clipHeight) { + words = ['...']; + wrapper.attr('title', wrapper.textStr); + } else { + + tspan = doc.createElementNS(SVG_NS, 'tspan'); + attr(tspan, { + dy: dy, + x: parentX + }); + if (spanStyle) { // #390 + attr(tspan, 'style', spanStyle); + } + textNode.appendChild(tspan); + + if (actualWidth > width) { // a single word is pressing it out + width = actualWidth; + } + } + } + } else { // append to existing line tspan + tspan.removeChild(tspan.firstChild); + rest.unshift(words.pop()); + } + if (words.length) { + tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-'))); + } + } + } + } + } + }); + }); + } + }, + + /** + * Create a button with preset states + * @param {String} text + * @param {Number} x + * @param {Number} y + * @param {Function} callback + * @param {Object} normalState + * @param {Object} hoverState + * @param {Object} pressedState + */ + button: function (text, x, y, callback, normalState, hoverState, pressedState, disabledState, shape) { + var label = this.label(text, x, y, shape, null, null, null, null, 'button'), + curState = 0, + stateOptions, + stateStyle, + normalStyle, + hoverStyle, + pressedStyle, + disabledStyle, + verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 }; + + // Normal state - prepare the attributes + normalState = merge({ + 'stroke-width': 1, + stroke: '#CCCCCC', + fill: { + linearGradient: verticalGradient, + stops: [ + [0, '#FEFEFE'], + [1, '#F6F6F6'] + ] + }, + r: 2, + padding: 5, + style: { + color: 'black' + } + }, normalState); + normalStyle = normalState.style; + delete normalState.style; + + // Hover state + hoverState = merge(normalState, { + stroke: '#68A', + fill: { + linearGradient: verticalGradient, + stops: [ + [0, '#FFF'], + [1, '#ACF'] + ] + } + }, hoverState); + hoverStyle = hoverState.style; + delete hoverState.style; + + // Pressed state + pressedState = merge(normalState, { + stroke: '#68A', + fill: { + linearGradient: verticalGradient, + stops: [ + [0, '#9BD'], + [1, '#CDF'] + ] + } + }, pressedState); + pressedStyle = pressedState.style; + delete pressedState.style; + + // Disabled state + disabledState = merge(normalState, { + style: { + color: '#CCC' + } + }, disabledState); + disabledStyle = disabledState.style; + delete disabledState.style; + + // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667). + addEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () { + if (curState !== 3) { + label.attr(hoverState) + .css(hoverStyle); + } + }); + addEvent(label.element, isIE ? 'mouseout' : 'mouseleave', function () { + if (curState !== 3) { + stateOptions = [normalState, hoverState, pressedState][curState]; + stateStyle = [normalStyle, hoverStyle, pressedStyle][curState]; + label.attr(stateOptions) + .css(stateStyle); + } + }); + + label.setState = function (state) { + label.state = curState = state; + if (!state) { + label.attr(normalState) + .css(normalStyle); + } else if (state === 2) { + label.attr(pressedState) + .css(pressedStyle); + } else if (state === 3) { + label.attr(disabledState) + .css(disabledStyle); + } + }; + + return label + .on('click', function () { + if (curState !== 3) { + callback.call(label); + } + }) + .attr(normalState) + .css(extend({ cursor: 'default' }, normalStyle)); + }, + + /** + * Make a straight line crisper by not spilling out to neighbour pixels + * @param {Array} points + * @param {Number} width + */ + crispLine: function (points, width) { + // points format: [M, 0, 0, L, 100, 0] + // normalize to a crisp line + if (points[1] === points[4]) { + // Substract due to #1129. Now bottom and left axis gridlines behave the same. + points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2); + } + if (points[2] === points[5]) { + points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2); + } + return points; + }, + + + /** + * Draw a path + * @param {Array} path An SVG path in array form + */ + path: function (path) { + var attr = { + fill: NONE + }; + if (isArray(path)) { + attr.d = path; + } else if (isObject(path)) { // attributes + extend(attr, path); + } + return this.createElement('path').attr(attr); + }, + + /** + * Draw and return an SVG circle + * @param {Number} x The x position + * @param {Number} y The y position + * @param {Number} r The radius + */ + circle: function (x, y, r) { + var attr = isObject(x) ? + x : + { + x: x, + y: y, + r: r + }, + wrapper = this.createElement('circle'); + + wrapper.xSetter = function (value) { + this.element.setAttribute('cx', value); + }; + wrapper.ySetter = function (value) { + this.element.setAttribute('cy', value); + }; + return wrapper.attr(attr); + }, + + /** + * Draw and return an arc + * @param {Number} x X position + * @param {Number} y Y position + * @param {Number} r Radius + * @param {Number} innerR Inner radius like used in donut charts + * @param {Number} start Starting angle + * @param {Number} end Ending angle + */ + arc: function (x, y, r, innerR, start, end) { + var arc; + + if (isObject(x)) { + y = x.y; + r = x.r; + innerR = x.innerR; + start = x.start; + end = x.end; + x = x.x; + } + + // Arcs are defined as symbols for the ability to set + // attributes in attr and animate + arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, { + innerR: innerR || 0, + start: start || 0, + end: end || 0 + }); + arc.r = r; // #959 + return arc; + }, + + /** + * Draw and return a rectangle + * @param {Number} x Left position + * @param {Number} y Top position + * @param {Number} width + * @param {Number} height + * @param {Number} r Border corner radius + * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing + */ + rect: function (x, y, width, height, r, strokeWidth) { + + r = isObject(x) ? x.r : r; + + var wrapper = this.createElement('rect'), + attribs = isObject(x) ? x : x === UNDEFINED ? {} : { + x: x, + y: y, + width: mathMax(width, 0), + height: mathMax(height, 0) + }; + + if (strokeWidth !== UNDEFINED) { + attribs.strokeWidth = strokeWidth; + attribs = wrapper.crisp(attribs); + } + + if (r) { + attribs.r = r; + } + + wrapper.rSetter = function (value) { + attr(this.element, { + rx: value, + ry: value + }); + }; + + return wrapper.attr(attribs); + }, + + /** + * Resize the box and re-align all aligned elements + * @param {Object} width + * @param {Object} height + * @param {Boolean} animate + * + */ + setSize: function (width, height, animate) { + var renderer = this, + alignedObjects = renderer.alignedObjects, + i = alignedObjects.length; + + renderer.width = width; + renderer.height = height; + + renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({ + width: width, + height: height + }); + + while (i--) { + alignedObjects[i].align(); + } + }, + + /** + * Create a group + * @param {String} name The group will be given a class name of 'highcharts-{name}'. + * This can be used for styling and scripting. + */ + g: function (name) { + var elem = this.createElement('g'); + return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem; + }, + + /** + * Display an image + * @param {String} src + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + image: function (src, x, y, width, height) { + var attribs = { + preserveAspectRatio: NONE + }, + elemWrapper; + + // optional properties + if (arguments.length > 1) { + extend(attribs, { + x: x, + y: y, + width: width, + height: height + }); + } + + elemWrapper = this.createElement('image').attr(attribs); + + // set the href in the xlink namespace + if (elemWrapper.element.setAttributeNS) { + elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink', + 'href', src); + } else { + // could be exporting in IE + // using href throws "not supported" in ie7 and under, requries regex shim to fix later + elemWrapper.element.setAttribute('hc-svg-href', src); + } + + return elemWrapper; + }, + + /** + * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object. + * + * @param {Object} symbol + * @param {Object} x + * @param {Object} y + * @param {Object} radius + * @param {Object} options + */ + symbol: function (symbol, x, y, width, height, options) { + + var obj, + + // get the symbol definition function + symbolFn = this.symbols[symbol], + + // check if there's a path defined for this symbol + path = symbolFn && symbolFn( + mathRound(x), + mathRound(y), + width, + height, + options + ), + + imageElement, + imageRegex = /^url\((.*?)\)$/, + imageSrc, + imageSize, + centerImage; + + if (path) { + + obj = this.path(path); + // expando properties for use in animate and attr + extend(obj, { + symbolName: symbol, + x: x, + y: y, + width: width, + height: height + }); + if (options) { + extend(obj, options); + } + + + // image symbols + } else if (imageRegex.test(symbol)) { + + // On image load, set the size and position + centerImage = function (img, size) { + if (img.element) { // it may be destroyed in the meantime (#1390) + img.attr({ + width: size[0], + height: size[1] + }); + + if (!img.alignByTranslate) { // #185 + img.translate( + mathRound((width - size[0]) / 2), // #1378 + mathRound((height - size[1]) / 2) + ); + } + } + }; + + imageSrc = symbol.match(imageRegex)[1]; + imageSize = symbolSizes[imageSrc]; + + // Ireate the image synchronously, add attribs async + obj = this.image(imageSrc) + .attr({ + x: x, + y: y + }); + obj.isImg = true; + + if (imageSize) { + centerImage(obj, imageSize); + } else { + // Initialize image to be 0 size so export will still function if there's no cached sizes. + // + obj.attr({ width: 0, height: 0 }); + + // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8, + // the created element must be assigned to a variable in order to load (#292). + imageElement = createElement('img', { + onload: function () { + centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]); + }, + src: imageSrc + }); + } + } + + return obj; + }, + + /** + * An extendable collection of functions for defining symbol paths. + */ + symbols: { + 'circle': function (x, y, w, h) { + var cpw = 0.166 * w; + return [ + M, x + w / 2, y, + 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h, + 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y, + 'Z' + ]; + }, + + 'square': function (x, y, w, h) { + return [ + M, x, y, + L, x + w, y, + x + w, y + h, + x, y + h, + 'Z' + ]; + }, + + 'triangle': function (x, y, w, h) { + return [ + M, x + w / 2, y, + L, x + w, y + h, + x, y + h, + 'Z' + ]; + }, + + 'triangle-down': function (x, y, w, h) { + return [ + M, x, y, + L, x + w, y, + x + w / 2, y + h, + 'Z' + ]; + }, + 'diamond': function (x, y, w, h) { + return [ + M, x + w / 2, y, + L, x + w, y + h / 2, + x + w / 2, y + h, + x, y + h / 2, + 'Z' + ]; + }, + 'arc': function (x, y, w, h, options) { + var start = options.start, + radius = options.r || w || h, + end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561) + innerRadius = options.innerR, + open = options.open, + cosStart = mathCos(start), + sinStart = mathSin(start), + cosEnd = mathCos(end), + sinEnd = mathSin(end), + longArc = options.end - start < mathPI ? 0 : 1; + + return [ + M, + x + radius * cosStart, + y + radius * sinStart, + 'A', // arcTo + radius, // x radius + radius, // y radius + 0, // slanting + longArc, // long or short arc + 1, // clockwise + x + radius * cosEnd, + y + radius * sinEnd, + open ? M : L, + x + innerRadius * cosEnd, + y + innerRadius * sinEnd, + 'A', // arcTo + innerRadius, // x radius + innerRadius, // y radius + 0, // slanting + longArc, // long or short arc + 0, // clockwise + x + innerRadius * cosStart, + y + innerRadius * sinStart, + + open ? '' : 'Z' // close + ]; + }, + + /** + * Callout shape used for default tooltips, also used for rounded rectangles in VML + */ + callout: function (x, y, w, h, options) { + var arrowLength = 6, + halfDistance = 6, + r = mathMin((options && options.r) || 0, w, h), + safeDistance = r + halfDistance, + anchorX = options && options.anchorX, + anchorY = options && options.anchorY, + path, + normalizer = mathRound(options.strokeWidth || 0) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors; + + x += normalizer; + y += normalizer; + path = [ + 'M', x + r, y, + 'L', x + w - r, y, // top side + 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner + 'L', x + w, y + h - r, // right side + 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-right corner + 'L', x + r, y + h, // bottom side + 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner + 'L', x, y + r, // left side + 'C', x, y, x, y, x + r, y // top-right corner + ]; + + if (anchorX && anchorX > w && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace right side + path.splice(13, 3, + 'L', x + w, anchorY - halfDistance, + x + w + arrowLength, anchorY, + x + w, anchorY + halfDistance, + x + w, y + h - r + ); + } else if (anchorX && anchorX < 0 && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace left side + path.splice(33, 3, + 'L', x, anchorY + halfDistance, + x - arrowLength, anchorY, + x, anchorY - halfDistance, + x, y + r + ); + } else if (anchorY && anchorY > h && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace bottom + path.splice(23, 3, + 'L', anchorX + halfDistance, y + h, + anchorX, y + h + arrowLength, + anchorX - halfDistance, y + h, + x + r, y + h + ); + } else if (anchorY && anchorY < 0 && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace top + path.splice(3, 3, + 'L', anchorX - halfDistance, y, + anchorX, y - arrowLength, + anchorX + halfDistance, y, + w - r, y + ); + } + return path; + } + }, + + /** + * Define a clipping rectangle + * @param {String} id + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + clipRect: function (x, y, width, height) { + var wrapper, + id = PREFIX + idCounter++, + + clipPath = this.createElement('clipPath').attr({ + id: id + }).add(this.defs); + + wrapper = this.rect(x, y, width, height, 0).add(clipPath); + wrapper.id = id; + wrapper.clipPath = clipPath; + + return wrapper; + }, + + + + + + /** + * Add text to the SVG object + * @param {String} str + * @param {Number} x Left position + * @param {Number} y Top position + * @param {Boolean} useHTML Use HTML to render the text + */ + text: function (str, x, y, useHTML) { + + // declare variables + var renderer = this, + fakeSVG = useCanVG || (!hasSVG && renderer.forExport), + wrapper, + attr = {}; + + if (useHTML && !renderer.forExport) { + return renderer.html(str, x, y); + } + + attr.x = Math.round(x || 0); // X is always needed for line-wrap logic + if (y) { + attr.y = Math.round(y); + } + if (str || str === 0) { + attr.text = str; + } + + wrapper = renderer.createElement('text') + .attr(attr); + + // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063) + if (fakeSVG) { + wrapper.css({ + position: ABSOLUTE + }); + } + + if (!useHTML) { + wrapper.xSetter = function (value, key, element) { + var childNodes = element.childNodes, + child, + i; + for (i = 1; i < childNodes.length; i++) { + child = childNodes[i]; + // if the x values are equal, the tspan represents a linebreak + if (child.getAttribute('x') === element.getAttribute('x')) { + child.setAttribute('x', value); + } + } + element.setAttribute(key, value); + }; + } + + return wrapper; + }, + + /** + * Utility to return the baseline offset and total line height from the font size + */ + fontMetrics: function (fontSize) { + fontSize = fontSize || this.style.fontSize; + fontSize = /px/.test(fontSize) ? pInt(fontSize) : /em/.test(fontSize) ? parseFloat(fontSize) * 12 : 12; + + // Empirical values found by comparing font size and bounding box height. + // Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/ + var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2), + baseline = mathRound(lineHeight * 0.8); + + return { + h: lineHeight, + b: baseline + }; + }, + + /** + * Add a label, a text item that can hold a colored or gradient background + * as well as a border and shadow. + * @param {string} str + * @param {Number} x + * @param {Number} y + * @param {String} shape + * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the + * coordinates it should be pinned to + * @param {Number} anchorY + * @param {Boolean} baseline Whether to position the label relative to the text baseline, + * like renderer.text, or to the upper border of the rectangle. + * @param {String} className Class name for the group + */ + label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { + + var renderer = this, + wrapper = renderer.g(className), + text = renderer.text('', 0, 0, useHTML) + .attr({ + zIndex: 1 + }), + //.add(wrapper), + box, + bBox, + alignFactor = 0, + padding = 3, + paddingLeft = 0, + width, + height, + wrapperX, + wrapperY, + crispAdjust = 0, + deferredAttr = {}, + baselineOffset, + needsBox; + + /** + * This function runs after the label is added to the DOM (when the bounding box is + * available), and after the text of the label is updated to detect the new bounding + * box and reflect it in the border box. + */ + function updateBoxSize() { + var boxX, + boxY, + style = text.element.style; + + bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) && text.textStr && + text.getBBox(); + wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft; + wrapper.height = (height || bBox.height || 0) + 2 * padding; + + // update the label-scoped y offset + baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b; + + + if (needsBox) { + + // create the border box if it is not already present + if (!box) { + boxX = mathRound(-alignFactor * padding); + boxY = baseline ? -baselineOffset : 0; + + wrapper.box = box = shape ? + renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height, deferredAttr) : + renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]); + box.attr('fill', NONE).add(wrapper); + } + + // apply the box attributes + if (!box.isImg) { // #1630 + box.attr(extend({ + width: mathRound(wrapper.width), + height: mathRound(wrapper.height) + }, deferredAttr)); + } + deferredAttr = null; + } + } + + /** + * This function runs after setting text or padding, but only if padding is changed + */ + function updateTextPadding() { + var styles = wrapper.styles, + textAlign = styles && styles.textAlign, + x = paddingLeft + padding * (1 - alignFactor), + y; + + // determin y based on the baseline + y = baseline ? 0 : baselineOffset; + + // compensate for alignment + if (defined(width) && bBox && (textAlign === 'center' || textAlign === 'right')) { + x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width); + } + + // update if anything changed + if (x !== text.x || y !== text.y) { + text.attr('x', x); + if (y !== UNDEFINED) { + text.attr('y', y); + } + } + + // record current values + text.x = x; + text.y = y; + } + + /** + * Set a box attribute, or defer it if the box is not yet created + * @param {Object} key + * @param {Object} value + */ + function boxAttr(key, value) { + if (box) { + box.attr(key, value); + } else { + deferredAttr[key] = value; + } + } + + /** + * After the text element is added, get the desired size of the border box + * and add it before the text in the DOM. + */ + wrapper.onAdd = function () { + text.add(wrapper); + wrapper.attr({ + text: str || '', // alignment is available now + x: x, + y: y + }); + + if (box && defined(anchorX)) { + wrapper.attr({ + anchorX: anchorX, + anchorY: anchorY + }); + } + }; + + /* + * Add specific attribute setters. + */ + + // only change local variables + wrapper.widthSetter = function (value) { + width = value; + }; + wrapper.heightSetter = function (value) { + height = value; + }; + wrapper.paddingSetter = function (value) { + if (defined(value) && value !== padding) { + padding = value; + updateTextPadding(); + } + }; + wrapper.paddingLeftSetter = function (value) { + if (defined(value) && value !== paddingLeft) { + paddingLeft = value; + updateTextPadding(); + } + }; + + + // change local variable and prevent setting attribute on the group + wrapper.alignSetter = function (value) { + alignFactor = { left: 0, center: 0.5, right: 1 }[value]; + }; + + // apply these to the box and the text alike + wrapper.textSetter = function (value) { + if (value !== UNDEFINED) { + text.textSetter(value); + } + updateBoxSize(); + updateTextPadding(); + }; + + // apply these to the box but not to the text + wrapper['stroke-widthSetter'] = function (value, key) { + if (value) { + needsBox = true; + } + crispAdjust = value % 2 / 2; + boxAttr(key, value); + }; + wrapper.strokeSetter = wrapper.fillSetter = wrapper.rSetter = function (value, key) { + if (key === 'fill' && value) { + needsBox = true; + } + boxAttr(key, value); + }; + wrapper.anchorXSetter = function (value, key) { + anchorX = value; + boxAttr(key, value + crispAdjust - wrapperX); + }; + wrapper.anchorYSetter = function (value, key) { + anchorY = value; + boxAttr(key, value - wrapperY); + }; + + // rename attributes + wrapper.xSetter = function (value) { + wrapper.x = value; // for animation getter + if (alignFactor) { + value -= alignFactor * ((width || bBox.width) + padding); + } + wrapperX = mathRound(value); + wrapper.attr('translateX', wrapperX); + }; + wrapper.ySetter = function (value) { + wrapperY = wrapper.y = mathRound(value); + wrapper.attr('translateY', wrapperY); + }; + + // Redirect certain methods to either the box or the text + var baseCss = wrapper.css; + return extend(wrapper, { + /** + * Pick up some properties and apply them to the text instead of the wrapper + */ + css: function (styles) { + if (styles) { + var textStyles = {}; + styles = merge(styles); // create a copy to avoid altering the original object (#537) + each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration', 'textShadow'], function (prop) { + if (styles[prop] !== UNDEFINED) { + textStyles[prop] = styles[prop]; + delete styles[prop]; + } + }); + text.css(textStyles); + } + return baseCss.call(wrapper, styles); + }, + /** + * Return the bounding box of the box, not the group + */ + getBBox: function () { + return { + width: bBox.width + 2 * padding, + height: bBox.height + 2 * padding, + x: bBox.x - padding, + y: bBox.y - padding + }; + }, + /** + * Apply the shadow to the box + */ + shadow: function (b) { + if (box) { + box.shadow(b); + } + return wrapper; + }, + /** + * Destroy and release memory. + */ + destroy: function () { + + // Added by button implementation + removeEvent(wrapper.element, 'mouseenter'); + removeEvent(wrapper.element, 'mouseleave'); + + if (text) { + text = text.destroy(); + } + if (box) { + box = box.destroy(); + } + // Call base implementation to destroy the rest + SVGElement.prototype.destroy.call(wrapper); + + // Release local pointers (#1298) + wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = null; + } + }); + } +}; // end SVGRenderer + + +// general renderer +Renderer = SVGRenderer; +// extend SvgElement for useHTML option +extend(SVGElement.prototype, { + /** + * Apply CSS to HTML elements. This is used in text within SVG rendering and + * by the VML renderer + */ + htmlCss: function (styles) { + var wrapper = this, + element = wrapper.element, + textWidth = styles && element.tagName === 'SPAN' && styles.width; + + if (textWidth) { + delete styles.width; + wrapper.textWidth = textWidth; + wrapper.updateTransform(); + } + + wrapper.styles = extend(wrapper.styles, styles); + css(wrapper.element, styles); + + return wrapper; + }, + + /** + * VML and useHTML method for calculating the bounding box based on offsets + * @param {Boolean} refresh Whether to force a fresh value from the DOM or to + * use the cached value + * + * @return {Object} A hash containing values for x, y, width and height + */ + + htmlGetBBox: function () { + var wrapper = this, + element = wrapper.element, + bBox = wrapper.bBox; + + // faking getBBox in exported SVG in legacy IE + if (!bBox) { + // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?) + if (element.nodeName === 'text') { + element.style.position = ABSOLUTE; + } + + bBox = wrapper.bBox = { + x: element.offsetLeft, + y: element.offsetTop, + width: element.offsetWidth, + height: element.offsetHeight + }; + } + + return bBox; + }, + + /** + * VML override private method to update elements based on internal + * properties based on SVG transform + */ + htmlUpdateTransform: function () { + // aligning non added elements is expensive + if (!this.added) { + this.alignOnAdd = true; + return; + } + + var wrapper = this, + renderer = wrapper.renderer, + elem = wrapper.element, + translateX = wrapper.translateX || 0, + translateY = wrapper.translateY || 0, + x = wrapper.x || 0, + y = wrapper.y || 0, + align = wrapper.textAlign || 'left', + alignCorrection = { left: 0, center: 0.5, right: 1 }[align], + shadows = wrapper.shadows; + + // apply translate + css(elem, { + marginLeft: translateX, + marginTop: translateY + }); + if (shadows) { // used in labels/tooltip + each(shadows, function (shadow) { + css(shadow, { + marginLeft: translateX + 1, + marginTop: translateY + 1 + }); + }); + } + + // apply inversion + if (wrapper.inverted) { // wrapper is a group + each(elem.childNodes, function (child) { + renderer.invertChild(child, elem); + }); + } + + if (elem.tagName === 'SPAN') { + + var width, + rotation = wrapper.rotation, + baseline, + textWidth = pInt(wrapper.textWidth), + currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','); + + if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed + + + baseline = renderer.fontMetrics(elem.style.fontSize).b; + + // Renderer specific handling of span rotation + if (defined(rotation)) { + wrapper.setSpanRotation(rotation, alignCorrection, baseline); + } + + width = pick(wrapper.elemWidth, elem.offsetWidth); + + // Update textWidth + if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254 + css(elem, { + width: textWidth + PX, + display: 'block', + whiteSpace: 'normal' + }); + width = textWidth; + } + + wrapper.getSpanCorrection(width, baseline, alignCorrection, rotation, align); + } + + // apply position with correction + css(elem, { + left: (x + (wrapper.xCorr || 0)) + PX, + top: (y + (wrapper.yCorr || 0)) + PX + }); + + // force reflow in webkit to apply the left and top on useHTML element (#1249) + if (isWebKit) { + baseline = elem.offsetHeight; // assigned to baseline for JSLint purpose + } + + // record current text transform + wrapper.cTT = currentTextTransform; + } + }, + + /** + * Set the rotation of an individual HTML span + */ + setSpanRotation: function (rotation, alignCorrection, baseline) { + var rotationStyle = {}, + cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : ''; + + rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)'; + rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] = rotationStyle.transformOrigin = (alignCorrection * 100) + '% ' + baseline + 'px'; + css(this.element, rotationStyle); + }, + + /** + * Get the correction in X and Y positioning as the element is rotated. + */ + getSpanCorrection: function (width, baseline, alignCorrection) { + this.xCorr = -width * alignCorrection; + this.yCorr = -baseline; + } +}); + +// Extend SvgRenderer for useHTML option. +extend(SVGRenderer.prototype, { + /** + * Create HTML text node. This is used by the VML renderer as well as the SVG + * renderer through the useHTML option. + * + * @param {String} str + * @param {Number} x + * @param {Number} y + */ + html: function (str, x, y) { + var wrapper = this.createElement('span'), + element = wrapper.element, + renderer = wrapper.renderer; + + // Text setter + wrapper.textSetter = function (value) { + if (value !== element.innerHTML) { + delete this.bBox; + } + element.innerHTML = this.textStr = value; + }; + + // Various setters which rely on update transform + wrapper.xSetter = wrapper.ySetter = wrapper.alignSetter = wrapper.rotationSetter = function (value, key) { + if (key === 'align') { + key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML. + } + wrapper[key] = value; + wrapper.htmlUpdateTransform(); + }; + + // Set the default attributes + wrapper.attr({ + text: str, + x: mathRound(x), + y: mathRound(y) + }) + .css({ + position: ABSOLUTE, + whiteSpace: 'nowrap', + fontFamily: this.style.fontFamily, + fontSize: this.style.fontSize + }); + + // Use the HTML specific .css method + wrapper.css = wrapper.htmlCss; + + // This is specific for HTML within SVG + if (renderer.isSVG) { + wrapper.add = function (svgGroupWrapper) { + + var htmlGroup, + container = renderer.box.parentNode, + parentGroup, + parents = []; + + this.parentGroup = svgGroupWrapper; + + // Create a mock group to hold the HTML elements + if (svgGroupWrapper) { + htmlGroup = svgGroupWrapper.div; + if (!htmlGroup) { + + // Read the parent chain into an array and read from top down + parentGroup = svgGroupWrapper; + while (parentGroup) { + + parents.push(parentGroup); + + // Move up to the next parent group + parentGroup = parentGroup.parentGroup; + } + + // Ensure dynamically updating position when any parent is translated + each(parents.reverse(), function (parentGroup) { + var htmlGroupStyle; + + // Create a HTML div and append it to the parent div to emulate + // the SVG group structure + htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, { + className: attr(parentGroup.element, 'class') + }, { + position: ABSOLUTE, + left: (parentGroup.translateX || 0) + PX, + top: (parentGroup.translateY || 0) + PX + }, htmlGroup || container); // the top group is appended to container + + // Shortcut + htmlGroupStyle = htmlGroup.style; + + // Set listeners to update the HTML div's position whenever the SVG group + // position is changed + extend(parentGroup, { + translateXSetter: function (value, key) { + htmlGroupStyle.left = value + PX; + parentGroup[key] = value; + parentGroup.doTransform = true; + }, + translateYSetter: function (value, key) { + htmlGroupStyle.top = value + PX; + parentGroup[key] = value; + parentGroup.doTransform = true; + }, + visibilitySetter: function (value, key) { + htmlGroupStyle[key] = value; + } + }); + }); + + } + } else { + htmlGroup = container; + } + + htmlGroup.appendChild(element); + + // Shared with VML: + wrapper.added = true; + if (wrapper.alignOnAdd) { + wrapper.htmlUpdateTransform(); + } + + return wrapper; + }; + } + return wrapper; + } +}); + +/* **************************************************************************** + * * + * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE * + * * + * For applications and websites that don't need IE support, like platform * + * targeted mobile apps and web apps, this code can be removed. * + * * + *****************************************************************************/ + +/** + * @constructor + */ +var VMLRenderer, VMLElement; +if (!hasSVG && !useCanVG) { + +/** + * The VML element wrapper. + */ +Highcharts.VMLElement = VMLElement = { + + /** + * Initialize a new VML element wrapper. It builds the markup as a string + * to minimize DOM traffic. + * @param {Object} renderer + * @param {Object} nodeName + */ + init: function (renderer, nodeName) { + var wrapper = this, + markup = ['<', nodeName, ' filled="f" stroked="f"'], + style = ['position: ', ABSOLUTE, ';'], + isDiv = nodeName === DIV; + + // divs and shapes need size + if (nodeName === 'shape' || isDiv) { + style.push('left:0;top:0;width:1px;height:1px;'); + } + style.push('visibility: ', isDiv ? HIDDEN : VISIBLE); + + markup.push(' style="', style.join(''), '"/>'); + + // create element with default attributes and style + if (nodeName) { + markup = isDiv || nodeName === 'span' || nodeName === 'img' ? + markup.join('') + : renderer.prepVML(markup); + wrapper.element = createElement(markup); + } + + wrapper.renderer = renderer; + }, + + /** + * Add the node to the given parent + * @param {Object} parent + */ + add: function (parent) { + var wrapper = this, + renderer = wrapper.renderer, + element = wrapper.element, + box = renderer.box, + inverted = parent && parent.inverted, + + // get the parent node + parentNode = parent ? + parent.element || parent : + box; + + + // if the parent group is inverted, apply inversion on all children + if (inverted) { // only on groups + renderer.invertChild(element, parentNode); + } + + // append it + parentNode.appendChild(element); + + // align text after adding to be able to read offset + wrapper.added = true; + if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) { + wrapper.updateTransform(); + } + + // fire an event for internal hooks + if (wrapper.onAdd) { + wrapper.onAdd(); + } + + return wrapper; + }, + + /** + * VML always uses htmlUpdateTransform + */ + updateTransform: SVGElement.prototype.htmlUpdateTransform, + + /** + * Set the rotation of a span with oldIE's filter + */ + setSpanRotation: function () { + // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented + // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+ + // has support for CSS3 transform. The getBBox method also needs to be updated + // to compensate for the rotation, like it currently does for SVG. + // Test case: http://jsfiddle.net/highcharts/Ybt44/ + + var rotation = this.rotation, + costheta = mathCos(rotation * deg2rad), + sintheta = mathSin(rotation * deg2rad); + + css(this.element, { + filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta, + ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta, + ', sizingMethod=\'auto expand\')'].join('') : NONE + }); + }, + + /** + * Get the positioning correction for the span after rotating. + */ + getSpanCorrection: function (width, baseline, alignCorrection, rotation, align) { + + var costheta = rotation ? mathCos(rotation * deg2rad) : 1, + sintheta = rotation ? mathSin(rotation * deg2rad) : 0, + height = pick(this.elemHeight, this.element.offsetHeight), + quad, + nonLeft = align && align !== 'left'; + + // correct x and y + this.xCorr = costheta < 0 && -width; + this.yCorr = sintheta < 0 && -height; + + // correct for baseline and corners spilling out after rotation + quad = costheta * sintheta < 0; + this.xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection); + this.yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1); + // correct for the length/height of the text + if (nonLeft) { + this.xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1); + if (rotation) { + this.yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1); + } + css(this.element, { + textAlign: align + }); + } + }, + + /** + * Converts a subset of an SVG path definition to its VML counterpart. Takes an array + * as the parameter and returns a string. + */ + pathToVML: function (value) { + // convert paths + var i = value.length, + path = []; + + while (i--) { + + // Multiply by 10 to allow subpixel precision. + // Substracting half a pixel seems to make the coordinates + // align with SVG, but this hasn't been tested thoroughly + if (isNumber(value[i])) { + path[i] = mathRound(value[i] * 10) - 5; + } else if (value[i] === 'Z') { // close the path + path[i] = 'x'; + } else { + path[i] = value[i]; + + // When the start X and end X coordinates of an arc are too close, + // they are rounded to the same value above. In this case, substract or + // add 1 from the end X and Y positions. #186, #760, #1371, #1410. + if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) { + // Start and end X + if (path[i + 5] === path[i + 7]) { + path[i + 7] += value[i + 7] > value[i + 5] ? 1 : -1; + } + // Start and end Y + if (path[i + 6] === path[i + 8]) { + path[i + 8] += value[i + 8] > value[i + 6] ? 1 : -1; + } + } + } + } + + + // Loop up again to handle path shortcuts (#2132) + /*while (i++ < path.length) { + if (path[i] === 'H') { // horizontal line to + path[i] = 'L'; + path.splice(i + 2, 0, path[i - 1]); + } else if (path[i] === 'V') { // vertical line to + path[i] = 'L'; + path.splice(i + 1, 0, path[i - 2]); + } + }*/ + return path.join(' ') || 'x'; + }, + + /** + * Set the element's clipping to a predefined rectangle + * + * @param {String} id The id of the clip rectangle + */ + clip: function (clipRect) { + var wrapper = this, + clipMembers, + cssRet; + + if (clipRect) { + clipMembers = clipRect.members; + erase(clipMembers, wrapper); // Ensure unique list of elements (#1258) + clipMembers.push(wrapper); + wrapper.destroyClip = function () { + erase(clipMembers, wrapper); + }; + cssRet = clipRect.getCSS(wrapper); + + } else { + if (wrapper.destroyClip) { + wrapper.destroyClip(); + } + cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214 + } + + return wrapper.css(cssRet); + + }, + + /** + * Set styles for the element + * @param {Object} styles + */ + css: SVGElement.prototype.htmlCss, + + /** + * Removes a child either by removeChild or move to garbageBin. + * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. + */ + safeRemoveChild: function (element) { + // discardElement will detach the node from its parent before attaching it + // to the garbage bin. Therefore it is important that the node is attached and have parent. + if (element.parentNode) { + discardElement(element); + } + }, + + /** + * Extend element.destroy by removing it from the clip members array + */ + destroy: function () { + if (this.destroyClip) { + this.destroyClip(); + } + + return SVGElement.prototype.destroy.apply(this); + }, + + /** + * Add an event listener. VML override for normalizing event parameters. + * @param {String} eventType + * @param {Function} handler + */ + on: function (eventType, handler) { + // simplest possible event model for internal use + this.element['on' + eventType] = function () { + var evt = win.event; + evt.target = evt.srcElement; + handler(evt); + }; + return this; + }, + + /** + * In stacked columns, cut off the shadows so that they don't overlap + */ + cutOffPath: function (path, length) { + + var len; + + path = path.split(/[ ,]/); + len = path.length; + + if (len === 9 || len === 11) { + path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length; + } + return path.join(' '); + }, + + /** + * Apply a drop shadow by copying elements and giving them different strokes + * @param {Boolean|Object} shadowOptions + */ + shadow: function (shadowOptions, group, cutOff) { + var shadows = [], + i, + element = this.element, + renderer = this.renderer, + shadow, + elemStyle = element.style, + markup, + path = element.path, + strokeWidth, + modifiedPath, + shadowWidth, + shadowElementOpacity; + + // some times empty paths are not strings + if (path && typeof path.value !== 'string') { + path = 'x'; + } + modifiedPath = path; + + if (shadowOptions) { + shadowWidth = pick(shadowOptions.width, 3); + shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; + for (i = 1; i <= 3; i++) { + + strokeWidth = (shadowWidth * 2) + 1 - (2 * i); + + // Cut off shadows for stacked column items + if (cutOff) { + modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5); + } + + markup = ['']; + + shadow = createElement(renderer.prepVML(markup), + null, { + left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1), + top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1) + } + ); + if (cutOff) { + shadow.cutOff = strokeWidth + 1; + } + + // apply the opacity + markup = ['']; + createElement(renderer.prepVML(markup), null, null, shadow); + + + // insert it + if (group) { + group.element.appendChild(shadow); + } else { + element.parentNode.insertBefore(shadow, element); + } + + // record it + shadows.push(shadow); + + } + + this.shadows = shadows; + } + return this; + }, + updateShadows: noop, // Used in SVG only + + setAttr: function (key, value) { + if (docMode8) { // IE8 setAttribute bug + this.element[key] = value; + } else { + this.element.setAttribute(key, value); + } + }, + classSetter: function (value) { + // IE8 Standards mode has problems retrieving the className unless set like this + this.element.className = value; + }, + dashstyleSetter: function (value, key, element) { + var strokeElem = element.getElementsByTagName('stroke')[0] || + createElement(this.renderer.prepVML(['']), null, null, element); + strokeElem[key] = value || 'solid'; + this[key] = value; /* because changing stroke-width will change the dash length + and cause an epileptic effect */ + }, + dSetter: function (value, key, element) { + var i, + shadows = this.shadows; + value = value || []; + this.d = value.join(' '); // used in getter for animation + + element.path = value = this.pathToVML(value); + + // update shadows + if (shadows) { + i = shadows.length; + while (i--) { + shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value; + } + } + this.setAttr(key, value); + }, + fillSetter: function (value, key, element) { + var nodeName = element.nodeName; + if (nodeName === 'SPAN') { // text color + element.style.color = value; + } else if (nodeName !== 'IMG') { // #1336 + element.filled = value !== NONE; + this.setAttr('fillcolor', this.renderer.color(value, element, key, this)); + } + }, + opacitySetter: noop, // Don't bother - animation is too slow and filters introduce artifacts + rotationSetter: function (value, key, element) { + var style = element.style; + this[key] = style[key] = value; // style is for #1873 + + // Correction for the 1x1 size of the shape container. Used in gauge needles. + style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX; + style.top = mathRound(mathCos(value * deg2rad)) + PX; + }, + strokeSetter: function (value, key, element) { + this.setAttr('strokecolor', this.renderer.color(value, element, key)); + }, + 'stroke-widthSetter': function (value, key, element) { + element.stroked = !!value; // VML "stroked" attribute + this[key] = value; // used in getter, issue #113 + if (isNumber(value)) { + value += PX; + } + this.setAttr('strokeweight', value); + }, + titleSetter: function (value, key) { + this.setAttr(key, value); + }, + visibilitySetter: function (value, key, element) { + + // Handle inherited visibility + if (value === 'inherit') { + value = VISIBLE; + } + + // Let the shadow follow the main element + if (this.shadows) { + each(this.shadows, function (shadow) { + shadow.style[key] = value; + }); + } + + // Instead of toggling the visibility CSS property, move the div out of the viewport. + // This works around #61 and #586 + if (element.nodeName === 'DIV') { + value = value === HIDDEN ? '-999em' : 0; + + // In order to redraw, IE7 needs the div to be visible when tucked away + // outside the viewport. So the visibility is actually opposite of + // the expected value. This applies to the tooltip only. + if (!docMode8) { + element.style[key] = value ? VISIBLE : HIDDEN; + } + key = 'top'; + } + element.style[key] = value; + }, + xSetter: function (value, key, element) { + this[key] = value; // used in getter + + if (key === 'x') { + key = 'left'; + } else if (key === 'y') { + key = 'top'; + }/* else { + value = mathMax(0, value); // don't set width or height below zero (#311) + }*/ + + // clipping rectangle special + if (this.updateClipping) { + this[key] = value; // the key is now 'left' or 'top' for 'x' and 'y' + this.updateClipping(); + } else { + // normal + element.style[key] = value; + } + }, + zIndexSetter: function (value, key, element) { + element.style[key] = value; + } +}; +VMLElement = extendClass(SVGElement, VMLElement); + +// Some shared setters +VMLElement.prototype.ySetter = + VMLElement.prototype.widthSetter = + VMLElement.prototype.heightSetter = + VMLElement.prototype.xSetter; + + +/** + * The VML renderer + */ +var VMLRendererExtension = { // inherit SVGRenderer + + Element: VMLElement, + isIE8: userAgent.indexOf('MSIE 8.0') > -1, + + + /** + * Initialize the VMLRenderer + * @param {Object} container + * @param {Number} width + * @param {Number} height + */ + init: function (container, width, height, style) { + var renderer = this, + boxWrapper, + box, + css; + + renderer.alignedObjects = []; + + boxWrapper = renderer.createElement(DIV) + .css(extend(this.getStyle(style), { position: RELATIVE})); + box = boxWrapper.element; + container.appendChild(boxWrapper.element); + + + // generate the containing box + renderer.isVML = true; + renderer.box = box; + renderer.boxWrapper = boxWrapper; + renderer.cache = {}; + + + renderer.setSize(width, height, false); + + // The only way to make IE6 and IE7 print is to use a global namespace. However, + // with IE8 the only way to make the dynamic shapes visible in screen and print mode + // seems to be to add the xmlns attribute and the behaviour style inline. + if (!doc.namespaces.hcv) { + + doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml'); + + // Setup default CSS (#2153, #2368, #2384) + css = 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' + + '{ behavior:url(#default#VML); display: inline-block; } '; + try { + doc.createStyleSheet().cssText = css; + } catch (e) { + doc.styleSheets[0].cssText += css; + } + + } + }, + + + /** + * Detect whether the renderer is hidden. This happens when one of the parent elements + * has display: none + */ + isHidden: function () { + return !this.box.offsetWidth; + }, + + /** + * Define a clipping rectangle. In VML it is accomplished by storing the values + * for setting the CSS style to all associated members. + * + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + clipRect: function (x, y, width, height) { + + // create a dummy element + var clipRect = this.createElement(), + isObj = isObject(x); + + // mimic a rectangle with its style object for automatic updating in attr + return extend(clipRect, { + members: [], + left: (isObj ? x.x : x) + 1, + top: (isObj ? x.y : y) + 1, + width: (isObj ? x.width : width) - 1, + height: (isObj ? x.height : height) - 1, + getCSS: function (wrapper) { + var element = wrapper.element, + nodeName = element.nodeName, + isShape = nodeName === 'shape', + inverted = wrapper.inverted, + rect = this, + top = rect.top - (isShape ? element.offsetTop : 0), + left = rect.left, + right = left + rect.width, + bottom = top + rect.height, + ret = { + clip: 'rect(' + + mathRound(inverted ? left : top) + 'px,' + + mathRound(inverted ? bottom : right) + 'px,' + + mathRound(inverted ? right : bottom) + 'px,' + + mathRound(inverted ? top : left) + 'px)' + }; + + // issue 74 workaround + if (!inverted && docMode8 && nodeName === 'DIV') { + extend(ret, { + width: right + PX, + height: bottom + PX + }); + } + return ret; + }, + + // used in attr and animation to update the clipping of all members + updateClipping: function () { + each(clipRect.members, function (member) { + if (member.element) { // Deleted series, like in stock/members/series-remove demo. Should be removed from members, but this will do. + member.css(clipRect.getCSS(member)); + } + }); + } + }); + + }, + + + /** + * Take a color and return it if it's a string, make it a gradient if it's a + * gradient configuration object, and apply opacity. + * + * @param {Object} color The color or config object + */ + color: function (color, elem, prop, wrapper) { + var renderer = this, + colorObject, + regexRgba = /^rgba/, + markup, + fillType, + ret = NONE; + + // Check for linear or radial gradient + if (color && color.linearGradient) { + fillType = 'gradient'; + } else if (color && color.radialGradient) { + fillType = 'pattern'; + } + + + if (fillType) { + + var stopColor, + stopOpacity, + gradient = color.linearGradient || color.radialGradient, + x1, + y1, + x2, + y2, + opacity1, + opacity2, + color1, + color2, + fillAttr = '', + stops = color.stops, + firstStop, + lastStop, + colors = [], + addFillNode = function () { + // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2 + // are reversed. + markup = ['']; + createElement(renderer.prepVML(markup), null, null, elem); + }; + + // Extend from 0 to 1 + firstStop = stops[0]; + lastStop = stops[stops.length - 1]; + if (firstStop[0] > 0) { + stops.unshift([ + 0, + firstStop[1] + ]); + } + if (lastStop[0] < 1) { + stops.push([ + 1, + lastStop[1] + ]); + } + + // Compute the stops + each(stops, function (stop, i) { + if (regexRgba.test(stop[1])) { + colorObject = Color(stop[1]); + stopColor = colorObject.get('rgb'); + stopOpacity = colorObject.get('a'); + } else { + stopColor = stop[1]; + stopOpacity = 1; + } + + // Build the color attribute + colors.push((stop[0] * 100) + '% ' + stopColor); + + // Only start and end opacities are allowed, so we use the first and the last + if (!i) { + opacity1 = stopOpacity; + color2 = stopColor; + } else { + opacity2 = stopOpacity; + color1 = stopColor; + } + }); + + // Apply the gradient to fills only. + if (prop === 'fill') { + + // Handle linear gradient angle + if (fillType === 'gradient') { + x1 = gradient.x1 || gradient[0] || 0; + y1 = gradient.y1 || gradient[1] || 0; + x2 = gradient.x2 || gradient[2] || 0; + y2 = gradient.y2 || gradient[3] || 0; + fillAttr = 'angle="' + (90 - math.atan( + (y2 - y1) / // y vector + (x2 - x1) // x vector + ) * 180 / mathPI) + '"'; + + addFillNode(); + + // Radial (circular) gradient + } else { + + var r = gradient.r, + sizex = r * 2, + sizey = r * 2, + cx = gradient.cx, + cy = gradient.cy, + radialReference = elem.radialReference, + bBox, + applyRadialGradient = function () { + if (radialReference) { + bBox = wrapper.getBBox(); + cx += (radialReference[0] - bBox.x) / bBox.width - 0.5; + cy += (radialReference[1] - bBox.y) / bBox.height - 0.5; + sizex *= radialReference[2] / bBox.width; + sizey *= radialReference[2] / bBox.height; + } + fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' + + 'size="' + sizex + ',' + sizey + '" ' + + 'origin="0.5,0.5" ' + + 'position="' + cx + ',' + cy + '" ' + + 'color2="' + color2 + '" '; + + addFillNode(); + }; + + // Apply radial gradient + if (wrapper.added) { + applyRadialGradient(); + } else { + // We need to know the bounding box to get the size and position right + wrapper.onAdd = applyRadialGradient; + } + + // The fill element's color attribute is broken in IE8 standards mode, so we + // need to set the parent shape's fillcolor attribute instead. + ret = color1; + } + + // Gradients are not supported for VML stroke, return the first color. #722. + } else { + ret = stopColor; + } + + // if the color is an rgba color, split it and add a fill node + // to hold the opacity component + } else if (regexRgba.test(color) && elem.tagName !== 'IMG') { + + colorObject = Color(color); + + markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>']; + createElement(this.prepVML(markup), null, null, elem); + + ret = colorObject.get('rgb'); + + + } else { + var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node + if (propNodes.length) { + propNodes[0].opacity = 1; + propNodes[0].type = 'solid'; + } + ret = color; + } + + return ret; + }, + + /** + * Take a VML string and prepare it for either IE8 or IE6/IE7. + * @param {Array} markup A string array of the VML markup to prepare + */ + prepVML: function (markup) { + var vmlStyle = 'display:inline-block;behavior:url(#default#VML);', + isIE8 = this.isIE8; + + markup = markup.join(''); + + if (isIE8) { // add xmlns and style inline + markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />'); + if (markup.indexOf('style="') === -1) { + markup = markup.replace('/>', ' style="' + vmlStyle + '" />'); + } else { + markup = markup.replace('style="', 'style="' + vmlStyle); + } + + } else { // add namespace + markup = markup.replace('<', ' 1) { + obj.attr({ + x: x, + y: y, + width: width, + height: height + }); + } + return obj; + }, + + /** + * For rectangles, VML uses a shape for rect to overcome bugs and rotation problems + */ + createElement: function (nodeName) { + return nodeName === 'rect' ? this.symbol(nodeName) : SVGRenderer.prototype.createElement.call(this, nodeName); + }, + + /** + * In the VML renderer, each child of an inverted div (group) is inverted + * @param {Object} element + * @param {Object} parentNode + */ + invertChild: function (element, parentNode) { + var ren = this, + parentStyle = parentNode.style, + imgStyle = element.tagName === 'IMG' && element.style; // #1111 + + css(element, { + flip: 'x', + left: pInt(parentStyle.width) - (imgStyle ? pInt(imgStyle.top) : 1), + top: pInt(parentStyle.height) - (imgStyle ? pInt(imgStyle.left) : 1), + rotation: -90 + }); + + // Recursively invert child elements, needed for nested composite shapes like box plots and error bars. #1680, #1806. + each(element.childNodes, function (child) { + ren.invertChild(child, element); + }); + }, + + /** + * Symbol definitions that override the parent SVG renderer's symbols + * + */ + symbols: { + // VML specific arc function + arc: function (x, y, w, h, options) { + var start = options.start, + end = options.end, + radius = options.r || w || h, + innerRadius = options.innerR, + cosStart = mathCos(start), + sinStart = mathSin(start), + cosEnd = mathCos(end), + sinEnd = mathSin(end), + ret; + + if (end - start === 0) { // no angle, don't show it. + return ['x']; + } + + ret = [ + 'wa', // clockwise arc to + x - radius, // left + y - radius, // top + x + radius, // right + y + radius, // bottom + x + radius * cosStart, // start x + y + radius * sinStart, // start y + x + radius * cosEnd, // end x + y + radius * sinEnd // end y + ]; + + if (options.open && !innerRadius) { + ret.push( + 'e', + M, + x,// - innerRadius, + y// - innerRadius + ); + } + + ret.push( + 'at', // anti clockwise arc to + x - innerRadius, // left + y - innerRadius, // top + x + innerRadius, // right + y + innerRadius, // bottom + x + innerRadius * cosEnd, // start x + y + innerRadius * sinEnd, // start y + x + innerRadius * cosStart, // end x + y + innerRadius * sinStart, // end y + 'x', // finish path + 'e' // close + ); + + ret.isArc = true; + return ret; + + }, + // Add circle symbol path. This performs significantly faster than v:oval. + circle: function (x, y, w, h, wrapper) { + + if (wrapper) { + w = h = 2 * wrapper.r; + } + + // Center correction, #1682 + if (wrapper && wrapper.isCircle) { + x -= w / 2; + y -= h / 2; + } + + // Return the path + return [ + 'wa', // clockwisearcto + x, // left + y, // top + x + w, // right + y + h, // bottom + x + w, // start x + y + h / 2, // start y + x + w, // end x + y + h / 2, // end y + //'x', // finish path + 'e' // close + ]; + }, + /** + * Add rectangle symbol path which eases rotation and omits arcsize problems + * compared to the built-in VML roundrect shape. When borders are not rounded, + * use the simpler square path, else use the callout path without the arrow. + */ + rect: function (x, y, w, h, options) { + return SVGRenderer.prototype.symbols[ + !defined(options) || !options.r ? 'square' : 'callout' + ].call(0, x, y, w, h, options); + } + } +}; +Highcharts.VMLRenderer = VMLRenderer = function () { + this.init.apply(this, arguments); +}; +VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension); + + // general renderer + Renderer = VMLRenderer; +} + +// This method is used with exporting in old IE, when emulating SVG (see #2314) +SVGRenderer.prototype.measureSpanWidth = function (text, styles) { + var measuringSpan = doc.createElement('span'), + offsetWidth, + textNode = doc.createTextNode(text); + + measuringSpan.appendChild(textNode); + css(measuringSpan, styles); + this.box.appendChild(measuringSpan); + offsetWidth = measuringSpan.offsetWidth; + discardElement(measuringSpan); // #2463 + return offsetWidth; +}; + + +/* **************************************************************************** + * * + * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE * + * * + *****************************************************************************/ +/* **************************************************************************** + * * + * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT * + * TARGETING THAT SYSTEM. * + * * + *****************************************************************************/ +var CanVGRenderer, + CanVGController; + +if (useCanVG) { + /** + * The CanVGRenderer is empty from start to keep the source footprint small. + * When requested, the CanVGController downloads the rest of the source packaged + * together with the canvg library. + */ + Highcharts.CanVGRenderer = CanVGRenderer = function () { + // Override the global SVG namespace to fake SVG/HTML that accepts CSS + SVG_NS = 'http://www.w3.org/1999/xhtml'; + }; + + /** + * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but + * the implementation from SvgRenderer will not be merged in until first render. + */ + CanVGRenderer.prototype.symbols = {}; + + /** + * Handles on demand download of canvg rendering support. + */ + CanVGController = (function () { + // List of renderering calls + var deferredRenderCalls = []; + + /** + * When downloaded, we are ready to draw deferred charts. + */ + function drawDeferred() { + var callLength = deferredRenderCalls.length, + callIndex; + + // Draw all pending render calls + for (callIndex = 0; callIndex < callLength; callIndex++) { + deferredRenderCalls[callIndex](); + } + // Clear the list + deferredRenderCalls = []; + } + + return { + push: function (func, scriptLocation) { + // Only get the script once + if (deferredRenderCalls.length === 0) { + getScript(scriptLocation, drawDeferred); + } + // Register render call + deferredRenderCalls.push(func); + } + }; + }()); + + Renderer = CanVGRenderer; +} // end CanVGRenderer + +/* **************************************************************************** + * * + * END OF ANDROID < 3 SPECIFIC CODE * + * * + *****************************************************************************/ + +/** + * The Tick class + */ +function Tick(axis, pos, type, noLabel) { + this.axis = axis; + this.pos = pos; + this.type = type || ''; + this.isNew = true; + + if (!type && !noLabel) { + this.addLabel(); + } +} + +Tick.prototype = { + /** + * Write the tick label + */ + addLabel: function () { + var tick = this, + axis = tick.axis, + options = axis.options, + chart = axis.chart, + horiz = axis.horiz, + categories = axis.categories, + names = axis.names, + pos = tick.pos, + labelOptions = options.labels, + str, + tickPositions = axis.tickPositions, + width = (horiz && categories && + !labelOptions.step && !labelOptions.staggerLines && + !labelOptions.rotation && + chart.plotWidth / tickPositions.length) || + (!horiz && (chart.margin[3] || chart.chartWidth * 0.33)), // #1580, #1931 + isFirst = pos === tickPositions[0], + isLast = pos === tickPositions[tickPositions.length - 1], + css, + attr, + value = categories ? + pick(categories[pos], names[pos], pos) : + pos, + label = tick.label, + tickPositionInfo = tickPositions.info, + dateTimeLabelFormat; + + // Set the datetime label format. If a higher rank is set for this position, use that. If not, + // use the general format. + if (axis.isDatetimeAxis && tickPositionInfo) { + dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName]; + } + // set properties for access in render method + tick.isFirst = isFirst; + tick.isLast = isLast; + + // get the string + str = axis.labelFormatter.call({ + axis: axis, + chart: chart, + isFirst: isFirst, + isLast: isLast, + dateTimeLabelFormat: dateTimeLabelFormat, + value: axis.isLog ? correctFloat(lin2log(value)) : value + }); + + // prepare CSS + css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX }; + css = extend(css, labelOptions.style); + + // first call + if (!defined(label)) { + attr = { + align: axis.labelAlign + }; + if (isNumber(labelOptions.rotation)) { + attr.rotation = labelOptions.rotation; + } + if (width && labelOptions.ellipsis) { + attr._clipHeight = axis.len / tickPositions.length; + } + + tick.label = + defined(str) && labelOptions.enabled ? + chart.renderer.text( + str, + 0, + 0, + labelOptions.useHTML + ) + .attr(attr) + // without position absolute, IE export sometimes is wrong + .css(css) + .add(axis.labelGroup) : + null; + + // update + } else if (label) { + label.attr({ + text: str + }) + .css(css); + } + }, + + /** + * Get the offset height or width of the label + */ + getLabelSize: function () { + var label = this.label, + axis = this.axis; + return label ? + label.getBBox()[axis.horiz ? 'height' : 'width'] : + 0; + }, + + /** + * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision + * detection with overflow logic. + */ + getLabelSides: function () { + var bBox = this.label.getBBox(), + axis = this.axis, + horiz = axis.horiz, + options = axis.options, + labelOptions = options.labels, + size = horiz ? bBox.width : bBox.height, + leftSide = horiz ? + labelOptions.x - size * { left: 0, center: 0.5, right: 1 }[axis.labelAlign] : + 0, + rightSide = horiz ? + size + leftSide : + size; + + return [leftSide, rightSide]; + }, + + /** + * Handle the label overflow by adjusting the labels to the left and right edge, or + * hide them if they collide into the neighbour label. + */ + handleOverflow: function (index, xy) { + var show = true, + axis = this.axis, + isFirst = this.isFirst, + isLast = this.isLast, + horiz = axis.horiz, + pxPos = horiz ? xy.x : xy.y, + reversed = axis.reversed, + tickPositions = axis.tickPositions, + sides = this.getLabelSides(), + leftSide = sides[0], + rightSide = sides[1], + axisLeft, + axisRight, + neighbour, + neighbourEdge, + line = this.label.line || 0, + labelEdge = axis.labelEdge, + justifyLabel = axis.justifyLabels && (isFirst || isLast), + justifyToPlot; + + // Hide it if it now overlaps the neighbour label + if (labelEdge[line] === UNDEFINED || pxPos + leftSide > labelEdge[line]) { + labelEdge[line] = pxPos + rightSide; + + } else if (!justifyLabel) { + show = false; + } + + if (justifyLabel) { + justifyToPlot = axis.justifyToPlot; + axisLeft = justifyToPlot ? axis.pos : 0; + axisRight = justifyToPlot ? axisLeft + axis.len : axis.chart.chartWidth; + + // Find the firsth neighbour on the same line + do { + index += (isFirst ? 1 : -1); + neighbour = axis.ticks[tickPositions[index]]; + } while (tickPositions[index] && (!neighbour || neighbour.label.line !== line)); + + neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1]; + + if ((isFirst && !reversed) || (isLast && reversed)) { + // Is the label spilling out to the left of the plot area? + if (pxPos + leftSide < axisLeft) { + + // Align it to plot left + pxPos = axisLeft - leftSide; + + // Hide it if it now overlaps the neighbour label + if (neighbour && pxPos + rightSide > neighbourEdge) { + show = false; + } + } + + } else { + // Is the label spilling out to the right of the plot area? + if (pxPos + rightSide > axisRight) { + + // Align it to plot right + pxPos = axisRight - rightSide; + + // Hide it if it now overlaps the neighbour label + if (neighbour && pxPos + leftSide < neighbourEdge) { + show = false; + } + + } + } + + // Set the modified x position of the label + xy.x = pxPos; + } + return show; + }, + + /** + * Get the x and y position for ticks and labels + */ + getPosition: function (horiz, pos, tickmarkOffset, old) { + var axis = this.axis, + chart = axis.chart, + cHeight = (old && chart.oldChartHeight) || chart.chartHeight; + + return { + x: horiz ? + axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB : + axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0), + + y: horiz ? + cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) : + cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB + }; + + }, + + /** + * Get the x, y position of the tick label + */ + getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { + var axis = this.axis, + transA = axis.transA, + reversed = axis.reversed, + staggerLines = axis.staggerLines, + baseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b, + rotation = labelOptions.rotation; + + x = x + labelOptions.x - (tickmarkOffset && horiz ? + tickmarkOffset * transA * (reversed ? -1 : 1) : 0); + y = y + labelOptions.y - (tickmarkOffset && !horiz ? + tickmarkOffset * transA * (reversed ? 1 : -1) : 0); + + // Correct for rotation (#1764) + if (rotation && axis.side === 2) { + y -= baseline - baseline * mathCos(rotation * deg2rad); + } + + // Vertically centered + if (!defined(labelOptions.y) && !rotation) { // #1951 + y += baseline - label.getBBox().height / 2; + } + + // Correct for staggered labels + if (staggerLines) { + label.line = (index / (step || 1) % staggerLines); + y += label.line * (axis.labelOffset / staggerLines); + } + + return { + x: x, + y: y + }; + }, + + /** + * Extendible method to return the path of the marker + */ + getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) { + return renderer.crispLine([ + M, + x, + y, + L, + x + (horiz ? 0 : -tickLength), + y + (horiz ? tickLength : 0) + ], tickWidth); + }, + + /** + * Put everything in place + * + * @param index {Number} + * @param old {Boolean} Use old coordinates to prepare an animation into new position + */ + render: function (index, old, opacity) { + var tick = this, + axis = tick.axis, + options = axis.options, + chart = axis.chart, + renderer = chart.renderer, + horiz = axis.horiz, + type = tick.type, + label = tick.label, + pos = tick.pos, + labelOptions = options.labels, + gridLine = tick.gridLine, + gridPrefix = type ? type + 'Grid' : 'grid', + tickPrefix = type ? type + 'Tick' : 'tick', + gridLineWidth = options[gridPrefix + 'LineWidth'], + gridLineColor = options[gridPrefix + 'LineColor'], + dashStyle = options[gridPrefix + 'LineDashStyle'], + tickLength = options[tickPrefix + 'Length'], + tickWidth = options[tickPrefix + 'Width'] || 0, + tickColor = options[tickPrefix + 'Color'], + tickPosition = options[tickPrefix + 'Position'], + gridLinePath, + mark = tick.mark, + markPath, + step = labelOptions.step, + attribs, + show = true, + tickmarkOffset = axis.tickmarkOffset, + xy = tick.getPosition(horiz, pos, tickmarkOffset, old), + x = xy.x, + y = xy.y, + reverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687 + + this.isActive = true; + + // create the grid line + if (gridLineWidth) { + gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true); + + if (gridLine === UNDEFINED) { + attribs = { + stroke: gridLineColor, + 'stroke-width': gridLineWidth + }; + if (dashStyle) { + attribs.dashstyle = dashStyle; + } + if (!type) { + attribs.zIndex = 1; + } + if (old) { + attribs.opacity = 0; + } + tick.gridLine = gridLine = + gridLineWidth ? + renderer.path(gridLinePath) + .attr(attribs).add(axis.gridGroup) : + null; + } + + // If the parameter 'old' is set, the current call will be followed + // by another call, therefore do not do any animations this time + if (!old && gridLine && gridLinePath) { + gridLine[tick.isNew ? 'attr' : 'animate']({ + d: gridLinePath, + opacity: opacity + }); + } + } + + // create the tick mark + if (tickWidth && tickLength) { + + // negate the length + if (tickPosition === 'inside') { + tickLength = -tickLength; + } + if (axis.opposite) { + tickLength = -tickLength; + } + + markPath = tick.getMarkPath(x, y, tickLength, tickWidth * reverseCrisp, horiz, renderer); + if (mark) { // updating + mark.animate({ + d: markPath, + opacity: opacity + }); + } else { // first time + tick.mark = renderer.path( + markPath + ).attr({ + stroke: tickColor, + 'stroke-width': tickWidth, + opacity: opacity + }).add(axis.axisGroup); + } + } + + // the label is created on init - now move it into place + if (label && !isNaN(x)) { + label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step); + + // Apply show first and show last. If the tick is both first and last, it is + // a single centered tick, in which case we show the label anyway (#2100). + if ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) || + (tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) { + show = false; + + // Handle label overflow and show or hide accordingly + } else if (!axis.isRadial && !labelOptions.step && !labelOptions.rotation && !old && opacity !== 0) { + show = tick.handleOverflow(index, xy); + } + + // apply step + if (step && index % step) { + // show those indices dividable by step + show = false; + } + + // Set the new position, and show or hide + if (show && !isNaN(xy.y)) { + xy.opacity = opacity; + label[tick.isNew ? 'attr' : 'animate'](xy); + tick.isNew = false; + } else { + label.attr('y', -9999); // #1338 + } + } + }, + + /** + * Destructor for the tick prototype + */ + destroy: function () { + destroyObjectProperties(this, this.axis); + } +}; + +/** + * The object wrapper for plot lines and plot bands + * @param {Object} options + */ +Highcharts.PlotLineOrBand = function (axis, options) { + this.axis = axis; + + if (options) { + this.options = options; + this.id = options.id; + } +}; + +Highcharts.PlotLineOrBand.prototype = { + + /** + * Render the plot line or plot band. If it is already existing, + * move it. + */ + render: function () { + var plotLine = this, + axis = plotLine.axis, + horiz = axis.horiz, + halfPointRange = (axis.pointRange || 0) / 2, + options = plotLine.options, + optionsLabel = options.label, + label = plotLine.label, + width = options.width, + to = options.to, + from = options.from, + isBand = defined(from) && defined(to), + value = options.value, + dashStyle = options.dashStyle, + svgElem = plotLine.svgElem, + path = [], + addEvent, + eventType, + xs, + ys, + x, + y, + color = options.color, + zIndex = options.zIndex, + events = options.events, + attribs = {}, + renderer = axis.chart.renderer; + + // logarithmic conversion + if (axis.isLog) { + from = log2lin(from); + to = log2lin(to); + value = log2lin(value); + } + + // plot line + if (width) { + path = axis.getPlotLinePath(value, width); + attribs = { + stroke: color, + 'stroke-width': width + }; + if (dashStyle) { + attribs.dashstyle = dashStyle; + } + } else if (isBand) { // plot band + + // keep within plot area + from = mathMax(from, axis.min - halfPointRange); + to = mathMin(to, axis.max + halfPointRange); + + path = axis.getPlotBandPath(from, to, options); + if (color) { + attribs.fill = color; + } + if (options.borderWidth) { + attribs.stroke = options.borderColor; + attribs['stroke-width'] = options.borderWidth; + } + } else { + return; + } + // zIndex + if (defined(zIndex)) { + attribs.zIndex = zIndex; + } + + // common for lines and bands + if (svgElem) { + if (path) { + svgElem.animate({ + d: path + }, null, svgElem.onGetPath); + } else { + svgElem.hide(); + svgElem.onGetPath = function () { + svgElem.show(); + }; + if (label) { + plotLine.label = label = label.destroy(); + } + } + } else if (path && path.length) { + plotLine.svgElem = svgElem = renderer.path(path) + .attr(attribs).add(); + + // events + if (events) { + addEvent = function (eventType) { + svgElem.on(eventType, function (e) { + events[eventType].apply(plotLine, [e]); + }); + }; + for (eventType in events) { + addEvent(eventType); + } + } + } + + // the plot band/line label + if (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) { + // apply defaults + optionsLabel = merge({ + align: horiz && isBand && 'center', + x: horiz ? !isBand && 4 : 10, + verticalAlign : !horiz && isBand && 'middle', + y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4, + rotation: horiz && !isBand && 90 + }, optionsLabel); + + // add the SVG element + if (!label) { + attribs = { + align: optionsLabel.textAlign || optionsLabel.align, + rotation: optionsLabel.rotation + }; + if (defined(zIndex)) { + attribs.zIndex = zIndex; + } + plotLine.label = label = renderer.text( + optionsLabel.text, + 0, + 0, + optionsLabel.useHTML + ) + .attr(attribs) + .css(optionsLabel.style) + .add(); + } + + // get the bounding box and align the label + xs = [path[1], path[4], pick(path[6], path[1])]; + ys = [path[2], path[5], pick(path[7], path[2])]; + x = arrayMin(xs); + y = arrayMin(ys); + + label.align(optionsLabel, false, { + x: x, + y: y, + width: arrayMax(xs) - x, + height: arrayMax(ys) - y + }); + label.show(); + + } else if (label) { // move out of sight + label.hide(); + } + + // chainable + return plotLine; + }, + + /** + * Remove the plot line or band + */ + destroy: function () { + // remove it from the lookup + erase(this.axis.plotLinesAndBands, this); + + delete this.axis; + destroyObjectProperties(this); + } +}; + +/** + * Object with members for extending the Axis prototype + */ + +AxisPlotLineOrBandExtension = { + + /** + * Create the path for a plot band + */ + getPlotBandPath: function (from, to) { + var toPath = this.getPlotLinePath(to), + path = this.getPlotLinePath(from); + + if (path && toPath) { + path.push( + toPath[4], + toPath[5], + toPath[1], + toPath[2] + ); + } else { // outside the axis area + path = null; + } + + return path; + }, + + addPlotBand: function (options) { + this.addPlotBandOrLine(options, 'plotBands'); + }, + + addPlotLine: function (options) { + this.addPlotBandOrLine(options, 'plotLines'); + }, + + /** + * Add a plot band or plot line after render time + * + * @param options {Object} The plotBand or plotLine configuration object + */ + addPlotBandOrLine: function (options, coll) { + var obj = new Highcharts.PlotLineOrBand(this, options).render(), + userOptions = this.userOptions; + + if (obj) { // #2189 + // Add it to the user options for exporting and Axis.update + if (coll) { + userOptions[coll] = userOptions[coll] || []; + userOptions[coll].push(options); + } + this.plotLinesAndBands.push(obj); + } + + return obj; + }, + + /** + * Remove a plot band or plot line from the chart by id + * @param {Object} id + */ + removePlotBandOrLine: function (id) { + var plotLinesAndBands = this.plotLinesAndBands, + options = this.options, + userOptions = this.userOptions, + i = plotLinesAndBands.length; + while (i--) { + if (plotLinesAndBands[i].id === id) { + plotLinesAndBands[i].destroy(); + } + } + each([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) { + i = arr.length; + while (i--) { + if (arr[i].id === id) { + erase(arr, arr[i]); + } + } + }); + } +}; + +/** + * Create a new axis object + * @param {Object} chart + * @param {Object} options + */ +function Axis() { + this.init.apply(this, arguments); +} + +Axis.prototype = { + + /** + * Default options for the X axis - the Y axis has extended defaults + */ + defaultOptions: { + // allowDecimals: null, + // alternateGridColor: null, + // categories: [], + dateTimeLabelFormats: { + millisecond: '%H:%M:%S.%L', + second: '%H:%M:%S', + minute: '%H:%M', + hour: '%H:%M', + day: '%e. %b', + week: '%e. %b', + month: '%b \'%y', + year: '%Y' + }, + endOnTick: false, + gridLineColor: '#C0C0C0', + // gridLineDashStyle: 'solid', + // gridLineWidth: 0, + // reversed: false, + + labels: defaultLabelOptions, + // { step: null }, + lineColor: '#C0D0E0', + lineWidth: 1, + //linkedTo: null, + //max: undefined, + //min: undefined, + minPadding: 0.01, + maxPadding: 0.01, + //minRange: null, + minorGridLineColor: '#E0E0E0', + // minorGridLineDashStyle: null, + minorGridLineWidth: 1, + minorTickColor: '#A0A0A0', + //minorTickInterval: null, + minorTickLength: 2, + minorTickPosition: 'outside', // inside or outside + //minorTickWidth: 0, + //opposite: false, + //offset: 0, + //plotBands: [{ + // events: {}, + // zIndex: 1, + // labels: { align, x, verticalAlign, y, style, rotation, textAlign } + //}], + //plotLines: [{ + // events: {} + // dashStyle: {} + // zIndex: + // labels: { align, x, verticalAlign, y, style, rotation, textAlign } + //}], + //reversed: false, + // showFirstLabel: true, + // showLastLabel: true, + startOfWeek: 1, + startOnTick: false, + tickColor: '#C0D0E0', + //tickInterval: null, + tickLength: 10, + tickmarkPlacement: 'between', // on or between + tickPixelInterval: 100, + tickPosition: 'outside', + tickWidth: 1, + title: { + //text: null, + align: 'middle', // low, middle or high + //margin: 0 for horizontal, 10 for vertical axes, + //rotation: 0, + //side: 'outside', + style: { + color: '#707070' + } + //x: 0, + //y: 0 + }, + type: 'linear' // linear, logarithmic or datetime + }, + + /** + * This options set extends the defaultOptions for Y axes + */ + defaultYAxisOptions: { + endOnTick: true, + gridLineWidth: 1, + tickPixelInterval: 72, + showLastLabel: true, + labels: { + x: -8, + y: 3 + }, + lineWidth: 0, + maxPadding: 0.05, + minPadding: 0.05, + startOnTick: true, + tickWidth: 0, + title: { + rotation: 270, + text: 'Values' + }, + stackLabels: { + enabled: false, + //align: dynamic, + //y: dynamic, + //x: dynamic, + //verticalAlign: dynamic, + //textAlign: dynamic, + //rotation: 0, + formatter: function () { + return numberFormat(this.total, -1); + }, + style: defaultLabelOptions.style + } + }, + + /** + * These options extend the defaultOptions for left axes + */ + defaultLeftAxisOptions: { + labels: { + x: -15, + y: null + }, + title: { + rotation: 270 + } + }, + + /** + * These options extend the defaultOptions for right axes + */ + defaultRightAxisOptions: { + labels: { + x: 15, + y: null + }, + title: { + rotation: 90 + } + }, + + /** + * These options extend the defaultOptions for bottom axes + */ + defaultBottomAxisOptions: { + labels: { + x: 0, + y: 20 + // overflow: undefined, + // staggerLines: null + }, + title: { + rotation: 0 + } + }, + /** + * These options extend the defaultOptions for left axes + */ + defaultTopAxisOptions: { + labels: { + x: 0, + y: -15 + // overflow: undefined + // staggerLines: null + }, + title: { + rotation: 0 + } + }, + + /** + * Initialize the axis + */ + init: function (chart, userOptions) { + + + var isXAxis = userOptions.isX, + axis = this; + + // Flag, is the axis horizontal + axis.horiz = chart.inverted ? !isXAxis : isXAxis; + + // Flag, isXAxis + axis.isXAxis = isXAxis; + axis.coll = isXAxis ? 'xAxis' : 'yAxis'; + + axis.opposite = userOptions.opposite; // needed in setOptions + axis.side = userOptions.side || (axis.horiz ? + (axis.opposite ? 0 : 2) : // top : bottom + (axis.opposite ? 1 : 3)); // right : left + + axis.setOptions(userOptions); + + + var options = this.options, + type = options.type, + isDatetimeAxis = type === 'datetime'; + + axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format + + + // Flag, stagger lines or not + axis.userOptions = userOptions; + + //axis.axisTitleMargin = UNDEFINED,// = options.title.margin, + axis.minPixelPadding = 0; + //axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series + //axis.ignoreMaxPadding = UNDEFINED; + + axis.chart = chart; + axis.reversed = options.reversed; + axis.zoomEnabled = options.zoomEnabled !== false; + + // Initial categories + axis.categories = options.categories || type === 'category'; + axis.names = []; + + // Elements + //axis.axisGroup = UNDEFINED; + //axis.gridGroup = UNDEFINED; + //axis.axisTitle = UNDEFINED; + //axis.axisLine = UNDEFINED; + + // Shorthand types + axis.isLog = type === 'logarithmic'; + axis.isDatetimeAxis = isDatetimeAxis; + + // Flag, if axis is linked to another axis + axis.isLinked = defined(options.linkedTo); + // Linked axis. + //axis.linkedParent = UNDEFINED; + + // Tick positions + //axis.tickPositions = UNDEFINED; // array containing predefined positions + // Tick intervals + //axis.tickInterval = UNDEFINED; + //axis.minorTickInterval = UNDEFINED; + + axis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0; + + // Major ticks + axis.ticks = {}; + axis.labelEdge = []; + // Minor ticks + axis.minorTicks = {}; + //axis.tickAmount = UNDEFINED; + + // List of plotLines/Bands + axis.plotLinesAndBands = []; + + // Alternate bands + axis.alternateBands = {}; + + // Axis metrics + //axis.left = UNDEFINED; + //axis.top = UNDEFINED; + //axis.width = UNDEFINED; + //axis.height = UNDEFINED; + //axis.bottom = UNDEFINED; + //axis.right = UNDEFINED; + //axis.transA = UNDEFINED; + //axis.transB = UNDEFINED; + //axis.oldTransA = UNDEFINED; + axis.len = 0; + //axis.oldMin = UNDEFINED; + //axis.oldMax = UNDEFINED; + //axis.oldUserMin = UNDEFINED; + //axis.oldUserMax = UNDEFINED; + //axis.oldAxisLength = UNDEFINED; + axis.minRange = axis.userMinRange = options.minRange || options.maxZoom; + axis.range = options.range; + axis.offset = options.offset || 0; + + + // Dictionary for stacks + axis.stacks = {}; + axis.oldStacks = {}; + + // Min and max in the data + //axis.dataMin = UNDEFINED, + //axis.dataMax = UNDEFINED, + + // The axis range + axis.max = null; + axis.min = null; + + // User set min and max + //axis.userMin = UNDEFINED, + //axis.userMax = UNDEFINED, + + // Crosshair options + axis.crosshair = pick(options.crosshair, splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1], false); + // Run Axis + + var eventType, + events = axis.options.events; + + // Register + if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update() + if (isXAxis && !this.isColorAxis) { // #2713 + chart.axes.splice(chart.xAxis.length, 0, axis); + } else { + chart.axes.push(axis); + } + + chart[axis.coll].push(axis); + } + + axis.series = axis.series || []; // populated by Series + + // inverted charts have reversed xAxes as default + if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) { + axis.reversed = true; + } + + axis.removePlotBand = axis.removePlotBandOrLine; + axis.removePlotLine = axis.removePlotBandOrLine; + + + // register event listeners + for (eventType in events) { + addEvent(axis, eventType, events[eventType]); + } + + // extend logarithmic axis + if (axis.isLog) { + axis.val2lin = log2lin; + axis.lin2val = lin2log; + } + }, + + /** + * Merge and set options + */ + setOptions: function (userOptions) { + this.options = merge( + this.defaultOptions, + this.isXAxis ? {} : this.defaultYAxisOptions, + [this.defaultTopAxisOptions, this.defaultRightAxisOptions, + this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side], + merge( + defaultOptions[this.coll], // if set in setOptions (#1053) + userOptions + ) + ); + }, + + /** + * The default label formatter. The context is a special config object for the label. + */ + defaultLabelFormatter: function () { + var axis = this.axis, + value = this.value, + categories = axis.categories, + dateTimeLabelFormat = this.dateTimeLabelFormat, + numericSymbols = defaultOptions.lang.numericSymbols, + i = numericSymbols && numericSymbols.length, + multi, + ret, + formatOption = axis.options.labels.format, + + // make sure the same symbol is added for all labels on a linear axis + numericSymbolDetector = axis.isLog ? value : axis.tickInterval; + + if (formatOption) { + ret = format(formatOption, this); + + } else if (categories) { + ret = value; + + } else if (dateTimeLabelFormat) { // datetime axis + ret = dateFormat(dateTimeLabelFormat, value); + + } else if (i && numericSymbolDetector >= 1000) { + // Decide whether we should add a numeric symbol like k (thousands) or M (millions). + // If we are to enable this in tooltip or other places as well, we can move this + // logic to the numberFormatter and enable it by a parameter. + while (i-- && ret === UNDEFINED) { + multi = Math.pow(1000, i + 1); + if (numericSymbolDetector >= multi && numericSymbols[i] !== null) { + ret = numberFormat(value / multi, -1) + numericSymbols[i]; + } + } + } + + if (ret === UNDEFINED) { + if (mathAbs(value) >= 10000) { // add thousands separators + ret = numberFormat(value, 0); + + } else { // small numbers + ret = numberFormat(value, -1, UNDEFINED, ''); // #2466 + } + } + + return ret; + }, + + /** + * Get the minimum and maximum for the series of each axis + */ + getSeriesExtremes: function () { + var axis = this, + chart = axis.chart; + + axis.hasVisibleSeries = false; + + // reset dataMin and dataMax in case we're redrawing + axis.dataMin = axis.dataMax = null; + + if (axis.buildStacks) { + axis.buildStacks(); + } + + // loop through this axis' series + each(axis.series, function (series) { + + if (series.visible || !chart.options.chart.ignoreHiddenSeries) { + + var seriesOptions = series.options, + xData, + threshold = seriesOptions.threshold, + seriesDataMin, + seriesDataMax; + + axis.hasVisibleSeries = true; + + // Validate threshold in logarithmic axes + if (axis.isLog && threshold <= 0) { + threshold = null; + } + + // Get dataMin and dataMax for X axes + if (axis.isXAxis) { + xData = series.xData; + if (xData.length) { + axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData)); + axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData)); + } + + // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data + } else { + + // Get this particular series extremes + series.getExtremes(); + seriesDataMax = series.dataMax; + seriesDataMin = series.dataMin; + + // Get the dataMin and dataMax so far. If percentage is used, the min and max are + // always 0 and 100. If seriesDataMin and seriesDataMax is null, then series + // doesn't have active y data, we continue with nulls + if (defined(seriesDataMin) && defined(seriesDataMax)) { + axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin); + axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax); + } + + // Adjust to threshold + if (defined(threshold)) { + if (axis.dataMin >= threshold) { + axis.dataMin = threshold; + axis.ignoreMinPadding = true; + } else if (axis.dataMax < threshold) { + axis.dataMax = threshold; + axis.ignoreMaxPadding = true; + } + } + } + } + }); + }, + + /** + * Translate from axis value to pixel position on the chart, or back + * + */ + translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) { + var axis = this, + sign = 1, + cvsOffset = 0, + localA = old ? axis.oldTransA : axis.transA, + localMin = old ? axis.oldMin : axis.min, + returnValue, + minPixelPadding = axis.minPixelPadding, + postTranslate = (axis.options.ordinal || (axis.isLog && handleLog)) && axis.lin2val; + + if (!localA) { + localA = axis.transA; + } + + // In vertical axes, the canvas coordinates start from 0 at the top like in + // SVG. + if (cvsCoord) { + sign *= -1; // canvas coordinates inverts the value + cvsOffset = axis.len; + } + + // Handle reversed axis + if (axis.reversed) { + sign *= -1; + cvsOffset -= sign * (axis.sector || axis.len); + } + + // From pixels to value + if (backwards) { // reverse translation + + val = val * sign + cvsOffset; + val -= minPixelPadding; + returnValue = val / localA + localMin; // from chart pixel to value + if (postTranslate) { // log and ordinal axes + returnValue = axis.lin2val(returnValue); + } + + // From value to pixels + } else { + if (postTranslate) { // log and ordinal axes + val = axis.val2lin(val); + } + if (pointPlacement === 'between') { + pointPlacement = 0.5; + } + returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) + + (isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0); + } + + return returnValue; + }, + + /** + * Utility method to translate an axis value to pixel position. + * @param {Number} value A value in terms of axis units + * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart + * or just the axis/pane itself. + */ + toPixels: function (value, paneCoordinates) { + return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos); + }, + + /* + * Utility method to translate a pixel position in to an axis value + * @param {Number} pixel The pixel value coordinate + * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the + * axis/pane itself. + */ + toValue: function (pixel, paneCoordinates) { + return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true); + }, + + /** + * Create the path for a plot line that goes from the given value on + * this axis, across the plot to the opposite side + * @param {Number} value + * @param {Number} lineWidth Used for calculation crisp line + * @param {Number] old Use old coordinates (for resizing and rescaling) + */ + getPlotLinePath: function (value, lineWidth, old, force, translatedValue) { + var axis = this, + chart = axis.chart, + axisLeft = axis.left, + axisTop = axis.top, + x1, + y1, + x2, + y2, + cHeight = (old && chart.oldChartHeight) || chart.chartHeight, + cWidth = (old && chart.oldChartWidth) || chart.chartWidth, + skip, + transB = axis.transB; + + translatedValue = pick(translatedValue, axis.translate(value, null, null, old)); + x1 = x2 = mathRound(translatedValue + transB); + y1 = y2 = mathRound(cHeight - translatedValue - transB); + + if (isNaN(translatedValue)) { // no min or max + skip = true; + + } else if (axis.horiz) { + y1 = axisTop; + y2 = cHeight - axis.bottom; + if (x1 < axisLeft || x1 > axisLeft + axis.width) { + skip = true; + } + } else { + x1 = axisLeft; + x2 = cWidth - axis.right; + + if (y1 < axisTop || y1 > axisTop + axis.height) { + skip = true; + } + } + return skip && !force ? + null : + chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 1); + }, + + /** + * Set the tick positions of a linear axis to round values like whole tens or every five. + */ + getLinearTickPositions: function (tickInterval, min, max) { + var pos, + lastPos, + roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval), + roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval), + tickPositions = []; + + // For single points, add a tick regardless of the relative position (#2662) + if (min === max && isNumber(min)) { + return [min]; + } + + // Populate the intermediate values + pos = roundedMin; + while (pos <= roundedMax) { + + // Place the tick on the rounded value + tickPositions.push(pos); + + // Always add the raw tickInterval, not the corrected one. + pos = correctFloat(pos + tickInterval); + + // If the interval is not big enough in the current min - max range to actually increase + // the loop variable, we need to break out to prevent endless loop. Issue #619 + if (pos === lastPos) { + break; + } + + // Record the last value + lastPos = pos; + } + return tickPositions; + }, + + /** + * Return the minor tick positions. For logarithmic axes, reuse the same logic + * as for major ticks. + */ + getMinorTickPositions: function () { + var axis = this, + options = axis.options, + tickPositions = axis.tickPositions, + minorTickInterval = axis.minorTickInterval, + minorTickPositions = [], + pos, + i, + len; + + if (axis.isLog) { + len = tickPositions.length; + for (i = 1; i < len; i++) { + minorTickPositions = minorTickPositions.concat( + axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true) + ); + } + } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314 + minorTickPositions = minorTickPositions.concat( + axis.getTimeTicks( + axis.normalizeTimeTickInterval(minorTickInterval), + axis.min, + axis.max, + options.startOfWeek + ) + ); + if (minorTickPositions[0] < axis.min) { + minorTickPositions.shift(); + } + } else { + for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) { + minorTickPositions.push(pos); + } + } + return minorTickPositions; + }, + + /** + * Adjust the min and max for the minimum range. Keep in mind that the series data is + * not yet processed, so we don't have information on data cropping and grouping, or + * updated axis.pointRange or series.pointRange. The data can't be processed until + * we have finally established min and max. + */ + adjustForMinRange: function () { + var axis = this, + options = axis.options, + min = axis.min, + max = axis.max, + zoomOffset, + spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange, + closestDataRange, + i, + distance, + xData, + loopLength, + minArgs, + maxArgs; + + // Set the automatic minimum range based on the closest point distance + if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) { + + if (defined(options.min) || defined(options.max)) { + axis.minRange = null; // don't do this again + + } else { + + // Find the closest distance between raw data points, as opposed to + // closestPointRange that applies to processed points (cropped and grouped) + each(axis.series, function (series) { + xData = series.xData; + loopLength = series.xIncrement ? 1 : xData.length - 1; + for (i = loopLength; i > 0; i--) { + distance = xData[i] - xData[i - 1]; + if (closestDataRange === UNDEFINED || distance < closestDataRange) { + closestDataRange = distance; + } + } + }); + axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin); + } + } + + // if minRange is exceeded, adjust + if (max - min < axis.minRange) { + var minRange = axis.minRange; + zoomOffset = (minRange - max + min) / 2; + + // if min and max options have been set, don't go beyond it + minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)]; + if (spaceAvailable) { // if space is available, stay within the data range + minArgs[2] = axis.dataMin; + } + min = arrayMax(minArgs); + + maxArgs = [min + minRange, pick(options.max, min + minRange)]; + if (spaceAvailable) { // if space is availabe, stay within the data range + maxArgs[2] = axis.dataMax; + } + + max = arrayMin(maxArgs); + + // now if the max is adjusted, adjust the min back + if (max - min < minRange) { + minArgs[0] = max - minRange; + minArgs[1] = pick(options.min, max - minRange); + min = arrayMax(minArgs); + } + } + + // Record modified extremes + axis.min = min; + axis.max = max; + }, + + /** + * Update translation information + */ + setAxisTranslation: function (saveOld) { + var axis = this, + range = axis.max - axis.min, + pointRange = axis.axisPointRange || 0, + closestPointRange, + minPointOffset = 0, + pointRangePadding = 0, + linkedParent = axis.linkedParent, + ordinalCorrection, + hasCategories = !!axis.categories, + transA = axis.transA; + + // Adjust translation for padding. Y axis with categories need to go through the same (#1784). + if (axis.isXAxis || hasCategories || pointRange) { + if (linkedParent) { + minPointOffset = linkedParent.minPointOffset; + pointRangePadding = linkedParent.pointRangePadding; + + } else { + each(axis.series, function (series) { + var seriesPointRange = hasCategories ? 1 : (axis.isXAxis ? series.pointRange : (axis.axisPointRange || 0)), // #2806 + pointPlacement = series.options.pointPlacement, + seriesClosestPointRange = series.closestPointRange; + + if (seriesPointRange > range) { // #1446 + seriesPointRange = 0; + } + pointRange = mathMax(pointRange, seriesPointRange); + + // minPointOffset is the value padding to the left of the axis in order to make + // room for points with a pointRange, typically columns. When the pointPlacement option + // is 'between' or 'on', this padding does not apply. + minPointOffset = mathMax( + minPointOffset, + isString(pointPlacement) ? 0 : seriesPointRange / 2 + ); + + // Determine the total padding needed to the length of the axis to make room for the + // pointRange. If the series' pointPlacement is 'on', no padding is added. + pointRangePadding = mathMax( + pointRangePadding, + pointPlacement === 'on' ? 0 : seriesPointRange + ); + + // Set the closestPointRange + if (!series.noSharedTooltip && defined(seriesClosestPointRange)) { + closestPointRange = defined(closestPointRange) ? + mathMin(closestPointRange, seriesClosestPointRange) : + seriesClosestPointRange; + } + }); + } + + // Record minPointOffset and pointRangePadding + ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853 + axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection; + axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection; + + // pointRange means the width reserved for each point, like in a column chart + axis.pointRange = mathMin(pointRange, range); + + // closestPointRange means the closest distance between points. In columns + // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange + // is some other value + axis.closestPointRange = closestPointRange; + } + + // Secondary values + if (saveOld) { + axis.oldTransA = transA; + } + axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1); + axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend + axis.minPixelPadding = transA * minPointOffset; + }, + + /** + * Set the tick positions to round values and optionally extend the extremes + * to the nearest tick + */ + setTickPositions: function (secondPass) { + var axis = this, + chart = axis.chart, + options = axis.options, + isLog = axis.isLog, + isDatetimeAxis = axis.isDatetimeAxis, + isXAxis = axis.isXAxis, + isLinked = axis.isLinked, + tickPositioner = axis.options.tickPositioner, + maxPadding = options.maxPadding, + minPadding = options.minPadding, + length, + linkedParentExtremes, + tickIntervalOption = options.tickInterval, + minTickIntervalOption = options.minTickInterval, + tickPixelIntervalOption = options.tickPixelInterval, + tickPositions, + keepTwoTicksOnly, + categories = axis.categories; + + // linked axis gets the extremes from the parent axis + if (isLinked) { + axis.linkedParent = chart[axis.coll][options.linkedTo]; + linkedParentExtremes = axis.linkedParent.getExtremes(); + axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin); + axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax); + if (options.type !== axis.linkedParent.options.type) { + error(11, 1); // Can't link axes of different type + } + } else { // initial min and max from the extreme data values + axis.min = pick(axis.userMin, options.min, axis.dataMin); + axis.max = pick(axis.userMax, options.max, axis.dataMax); + } + + if (isLog) { + if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978 + error(10, 1); // Can't plot negative values on log axis + } + axis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934 + axis.max = correctFloat(log2lin(axis.max)); + } + + // handle zoomed range + if (axis.range && defined(axis.max)) { + axis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618 + axis.userMax = axis.max; + + axis.range = null; // don't use it when running setExtremes + } + + // Hook for adjusting this.min and this.max. Used by bubble series. + if (axis.beforePadding) { + axis.beforePadding(); + } + + // adjust min and max for the minimum range + axis.adjustForMinRange(); + + // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding + // into account, we do this after computing tick interval (#1337). + if (!categories && !axis.axisPointRange && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) { + length = axis.max - axis.min; + if (length) { + if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) { + axis.min -= length * minPadding; + } + if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) { + axis.max += length * maxPadding; + } + } + } + + // Stay within floor and ceiling + if (isNumber(options.floor)) { + axis.min = mathMax(axis.min, options.floor); + } + if (isNumber(options.ceiling)) { + axis.max = mathMin(axis.max, options.ceiling); + } + + // get tickInterval + if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) { + axis.tickInterval = 1; + } else if (isLinked && !tickIntervalOption && + tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) { + axis.tickInterval = axis.linkedParent.tickInterval; + } else { + axis.tickInterval = pick( + tickIntervalOption, + categories ? // for categoried axis, 1 is default, for linear axis use tickPix + 1 : + // don't let it be more than the data range + (axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption) + ); + // For squished axes, set only two ticks + if (!defined(tickIntervalOption) && axis.len < tickPixelIntervalOption && !this.isRadial && + !this.isLog && !categories && options.startOnTick && options.endOnTick) { + keepTwoTicksOnly = true; + axis.tickInterval /= 4; // tick extremes closer to the real values + } + } + + // Now we're finished detecting min and max, crop and group series data. This + // is in turn needed in order to find tick positions in ordinal axes. + if (isXAxis && !secondPass) { + each(axis.series, function (series) { + series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax); + }); + } + + // set the translation factor used in translate function + axis.setAxisTranslation(true); + + // hook for ordinal axes and radial axes + if (axis.beforeSetTickPositions) { + axis.beforeSetTickPositions(); + } + + // hook for extensions, used in Highstock ordinal axes + if (axis.postProcessTickInterval) { + axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval); + } + + // In column-like charts, don't cramp in more ticks than there are points (#1943) + if (axis.pointRange) { + axis.tickInterval = mathMax(axis.pointRange, axis.tickInterval); + } + + // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined. + if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) { + axis.tickInterval = minTickIntervalOption; + } + + // for linear axes, get magnitude and normalize the interval + if (!isDatetimeAxis && !isLog) { // linear + if (!tickIntervalOption) { + axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options); + } + } + + // get minorTickInterval + axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ? + axis.tickInterval / 5 : options.minorTickInterval; + + // find the tick positions + axis.tickPositions = tickPositions = options.tickPositions ? + [].concat(options.tickPositions) : // Work on a copy (#1565) + (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max])); + if (!tickPositions) { + + // Too many ticks + if (!axis.ordinalPositions && (axis.max - axis.min) / axis.tickInterval > mathMax(2 * axis.len, 200)) { + error(19, true); + } + + if (isDatetimeAxis) { + tickPositions = axis.getTimeTicks( + axis.normalizeTimeTickInterval(axis.tickInterval, options.units), + axis.min, + axis.max, + options.startOfWeek, + axis.ordinalPositions, + axis.closestPointRange, + true + ); + } else if (isLog) { + tickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max); + } else { + tickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max); + } + + if (keepTwoTicksOnly) { + tickPositions.splice(1, tickPositions.length - 2); + } + + axis.tickPositions = tickPositions; + } + + if (!isLinked) { + + // reset min/max or remove extremes based on start/end on tick + var roundedMin = tickPositions[0], + roundedMax = tickPositions[tickPositions.length - 1], + minPointOffset = axis.minPointOffset || 0, + singlePad; + + if (options.startOnTick) { + axis.min = roundedMin; + } else if (axis.min - minPointOffset > roundedMin) { + tickPositions.shift(); + } + + if (options.endOnTick) { + axis.max = roundedMax; + } else if (axis.max + minPointOffset < roundedMax) { + tickPositions.pop(); + } + + // When there is only one point, or all points have the same value on this axis, then min + // and max are equal and tickPositions.length is 0 or 1. In this case, add some padding + // in order to center the point, but leave it with one tick. #1337. + if (tickPositions.length === 1) { + singlePad = mathAbs(axis.max) > 10e12 ? 1 : 0.001; // The lowest possible number to avoid extra padding on columns (#2619, #2846) + axis.min -= singlePad; + axis.max += singlePad; + } + } + }, + + /** + * Set the max ticks of either the x and y axis collection + */ + setMaxTicks: function () { + + var chart = this.chart, + maxTicks = chart.maxTicks || {}, + tickPositions = this.tickPositions, + key = this._maxTicksKey = [this.coll, this.pos, this.len].join('-'); + + if (!this.isLinked && !this.isDatetimeAxis && tickPositions && tickPositions.length > (maxTicks[key] || 0) && this.options.alignTicks !== false) { + maxTicks[key] = tickPositions.length; + } + chart.maxTicks = maxTicks; + }, + + /** + * When using multiple axes, adjust the number of ticks to match the highest + * number of ticks in that group + */ + adjustTickAmount: function () { + var axis = this, + chart = axis.chart, + key = axis._maxTicksKey, + tickPositions = axis.tickPositions, + maxTicks = chart.maxTicks; + + if (maxTicks && maxTicks[key] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && + axis.options.alignTicks !== false && this.min !== UNDEFINED) { + var oldTickAmount = axis.tickAmount, + calculatedTickAmount = tickPositions.length, + tickAmount; + + // set the axis-level tickAmount to use below + axis.tickAmount = tickAmount = maxTicks[key]; + + if (calculatedTickAmount < tickAmount) { + while (tickPositions.length < tickAmount) { + tickPositions.push(correctFloat( + tickPositions[tickPositions.length - 1] + axis.tickInterval + )); + } + axis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1); + axis.max = tickPositions[tickPositions.length - 1]; + + } + if (defined(oldTickAmount) && tickAmount !== oldTickAmount) { + axis.isDirty = true; + } + } + }, + + /** + * Set the scale based on data min and max, user set min and max or options + * + */ + setScale: function () { + var axis = this, + stacks = axis.stacks, + type, + i, + isDirtyData, + isDirtyAxisLength; + + axis.oldMin = axis.min; + axis.oldMax = axis.max; + axis.oldAxisLength = axis.len; + + // set the new axisLength + axis.setAxisSize(); + //axisLength = horiz ? axisWidth : axisHeight; + isDirtyAxisLength = axis.len !== axis.oldAxisLength; + + // is there new data? + each(axis.series, function (series) { + if (series.isDirtyData || series.isDirty || + series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well + isDirtyData = true; + } + }); + + // do we really need to go through all this? + if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw || + axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) { + + // reset stacks + if (!axis.isXAxis) { + for (type in stacks) { + for (i in stacks[type]) { + stacks[type][i].total = null; + stacks[type][i].cum = 0; + } + } + } + + axis.forceRedraw = false; + + // get data extremes if needed + axis.getSeriesExtremes(); + + // get fixed positions based on tickInterval + axis.setTickPositions(); + + // record old values to decide whether a rescale is necessary later on (#540) + axis.oldUserMin = axis.userMin; + axis.oldUserMax = axis.userMax; + + // Mark as dirty if it is not already set to dirty and extremes have changed. #595. + if (!axis.isDirty) { + axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax; + } + } else if (!axis.isXAxis) { + if (axis.oldStacks) { + stacks = axis.stacks = axis.oldStacks; + } + + // reset stacks + for (type in stacks) { + for (i in stacks[type]) { + stacks[type][i].cum = stacks[type][i].total; + } + } + } + + // Set the maximum tick amount + axis.setMaxTicks(); + }, + + /** + * Set the extremes and optionally redraw + * @param {Number} newMin + * @param {Number} newMax + * @param {Boolean} redraw + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + * @param {Object} eventArguments + * + */ + setExtremes: function (newMin, newMax, redraw, animation, eventArguments) { + var axis = this, + chart = axis.chart; + + redraw = pick(redraw, true); // defaults to true + + // Extend the arguments with min and max + eventArguments = extend(eventArguments, { + min: newMin, + max: newMax + }); + + // Fire the event + fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler + + axis.userMin = newMin; + axis.userMax = newMax; + axis.eventArgs = eventArguments; + + // Mark for running afterSetExtremes + axis.isDirtyExtremes = true; + + // redraw + if (redraw) { + chart.redraw(animation); + } + }); + }, + + /** + * Overridable method for zooming chart. Pulled out in a separate method to allow overriding + * in stock charts. + */ + zoom: function (newMin, newMax) { + var dataMin = this.dataMin, + dataMax = this.dataMax, + options = this.options; + + // Prevent pinch zooming out of range. Check for defined is for #1946. #1734. + if (!this.allowZoomOutside) { + if (defined(dataMin) && newMin <= mathMin(dataMin, pick(options.min, dataMin))) { + newMin = UNDEFINED; + } + if (defined(dataMax) && newMax >= mathMax(dataMax, pick(options.max, dataMax))) { + newMax = UNDEFINED; + } + } + + // In full view, displaying the reset zoom button is not required + this.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED; + + // Do it + this.setExtremes( + newMin, + newMax, + false, + UNDEFINED, + { trigger: 'zoom' } + ); + return true; + }, + + /** + * Update the axis metrics + */ + setAxisSize: function () { + var chart = this.chart, + options = this.options, + offsetLeft = options.offsetLeft || 0, + offsetRight = options.offsetRight || 0, + horiz = this.horiz, + width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight), + height = pick(options.height, chart.plotHeight), + top = pick(options.top, chart.plotTop), + left = pick(options.left, chart.plotLeft + offsetLeft), + percentRegex = /%$/; // docs + + // Check for percentage based input values + if (percentRegex.test(height)) { + height = parseInt(height, 10) / 100 * chart.plotHeight; + } + if (percentRegex.test(top)) { + top = parseInt(top, 10) / 100 * chart.plotHeight + chart.plotTop; + } + + // Expose basic values to use in Series object and navigator + this.left = left; + this.top = top; + this.width = width; + this.height = height; + this.bottom = chart.chartHeight - height - top; + this.right = chart.chartWidth - width - left; + + // Direction agnostic properties + this.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905 + this.pos = horiz ? left : top; // distance from SVG origin + }, + + /** + * Get the actual axis extremes + */ + getExtremes: function () { + var axis = this, + isLog = axis.isLog; + + return { + min: isLog ? correctFloat(lin2log(axis.min)) : axis.min, + max: isLog ? correctFloat(lin2log(axis.max)) : axis.max, + dataMin: axis.dataMin, + dataMax: axis.dataMax, + userMin: axis.userMin, + userMax: axis.userMax + }; + }, + + /** + * Get the zero plane either based on zero or on the min or max value. + * Used in bar and area plots + */ + getThreshold: function (threshold) { + var axis = this, + isLog = axis.isLog; + + var realMin = isLog ? lin2log(axis.min) : axis.min, + realMax = isLog ? lin2log(axis.max) : axis.max; + + if (realMin > threshold || threshold === null) { + threshold = realMin; + } else if (realMax < threshold) { + threshold = realMax; + } + + return axis.translate(threshold, 0, 1, 0, 1); + }, + + /** + * Compute auto alignment for the axis label based on which side the axis is on + * and the given rotation for the label + */ + autoLabelAlign: function (rotation) { + var ret, + angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360; + + if (angle > 15 && angle < 165) { + ret = 'right'; + } else if (angle > 195 && angle < 345) { + ret = 'left'; + } else { + ret = 'center'; + } + return ret; + }, + + /** + * Render the tick labels to a preliminary position to get their sizes + */ + getOffset: function () { + var axis = this, + chart = axis.chart, + renderer = chart.renderer, + options = axis.options, + tickPositions = axis.tickPositions, + ticks = axis.ticks, + horiz = axis.horiz, + side = axis.side, + invertedSide = chart.inverted ? [1, 0, 3, 2][side] : side, + hasData, + showAxis, + titleOffset = 0, + titleOffsetOption, + titleMargin = 0, + axisTitleOptions = options.title, + labelOptions = options.labels, + labelOffset = 0, // reset + axisOffset = chart.axisOffset, + clipOffset = chart.clipOffset, + directionFactor = [-1, 1, 1, -1][side], + n, + i, + autoStaggerLines = 1, + maxStaggerLines = pick(labelOptions.maxStaggerLines, 5), + sortedPositions, + lastRight, + overlap, + pos, + bBox, + x, + w, + lineNo, + lineHeightCorrection = side === 2 ? renderer.fontMetrics(labelOptions.style.fontSize).b : 0; + + // For reuse in Axis.render + axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions)); + axis.showAxis = showAxis = hasData || pick(options.showEmpty, true); + + // Set/reset staggerLines + axis.staggerLines = axis.horiz && labelOptions.staggerLines; + + // Create the axisGroup and gridGroup elements on first iteration + if (!axis.axisGroup) { + axis.gridGroup = renderer.g('grid') + .attr({ zIndex: options.gridZIndex || 1 }) + .add(); + axis.axisGroup = renderer.g('axis') + .attr({ zIndex: options.zIndex || 2 }) + .add(); + axis.labelGroup = renderer.g('axis-labels') + .attr({ zIndex: labelOptions.zIndex || 7 }) + .addClass(PREFIX + axis.coll.toLowerCase() + '-labels') + .add(); + } + + if (hasData || axis.isLinked) { + + // Set the explicit or automatic label alignment + axis.labelAlign = pick(labelOptions.align || axis.autoLabelAlign(labelOptions.rotation)); + + // Generate ticks + each(tickPositions, function (pos) { + if (!ticks[pos]) { + ticks[pos] = new Tick(axis, pos); + } else { + ticks[pos].addLabel(); // update labels depending on tick interval + } + }); + + // Handle automatic stagger lines + if (axis.horiz && !axis.staggerLines && maxStaggerLines && !labelOptions.rotation) { + sortedPositions = axis.reversed ? [].concat(tickPositions).reverse() : tickPositions; + while (autoStaggerLines < maxStaggerLines) { + lastRight = []; + overlap = false; + + for (i = 0; i < sortedPositions.length; i++) { + pos = sortedPositions[i]; + bBox = ticks[pos].label && ticks[pos].label.getBBox(); + w = bBox ? bBox.width : 0; + lineNo = i % autoStaggerLines; + + if (w) { + x = axis.translate(pos); // don't handle log + if (lastRight[lineNo] !== UNDEFINED && x < lastRight[lineNo]) { + overlap = true; + } + lastRight[lineNo] = x + w; + } + } + if (overlap) { + autoStaggerLines++; + } else { + break; + } + } + + if (autoStaggerLines > 1) { + axis.staggerLines = autoStaggerLines; + } + } + + + each(tickPositions, function (pos) { + // left side must be align: right and right side must have align: left for labels + if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign) { + + // get the highest offset + labelOffset = mathMax( + ticks[pos].getLabelSize(), + labelOffset + ); + } + + }); + if (axis.staggerLines) { + labelOffset *= axis.staggerLines; + axis.labelOffset = labelOffset; + } + + + } else { // doesn't have data + for (n in ticks) { + ticks[n].destroy(); + delete ticks[n]; + } + } + + if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { + if (!axis.axisTitle) { + axis.axisTitle = renderer.text( + axisTitleOptions.text, + 0, + 0, + axisTitleOptions.useHTML + ) + .attr({ + zIndex: 7, + rotation: axisTitleOptions.rotation || 0, + align: + axisTitleOptions.textAlign || + { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align] + }) + .addClass(PREFIX + this.coll.toLowerCase() + '-title') + .css(axisTitleOptions.style) + .add(axis.axisGroup); + axis.axisTitle.isNew = true; + } + + if (showAxis) { + titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width']; + titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10); + titleOffsetOption = axisTitleOptions.offset; + } + + // hide or show the title depending on whether showEmpty is set + axis.axisTitle[showAxis ? 'show' : 'hide'](); + } + + // handle automatic or user set offset + axis.offset = directionFactor * pick(options.offset, axisOffset[side]); + + axis.axisTitleMargin = + pick(titleOffsetOption, + labelOffset + titleMargin + + (labelOffset && (directionFactor * options.labels[horiz ? 'y' : 'x'] - lineHeightCorrection)) + ); + + axisOffset[side] = mathMax( + axisOffset[side], + axis.axisTitleMargin + titleOffset + directionFactor * axis.offset + ); + clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], mathFloor(options.lineWidth / 2) * 2); + }, + + /** + * Get the path for the axis line + */ + getLinePath: function (lineWidth) { + var chart = this.chart, + opposite = this.opposite, + offset = this.offset, + horiz = this.horiz, + lineLeft = this.left + (opposite ? this.width : 0) + offset, + lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset; + + if (opposite) { + lineWidth *= -1; // crispify the other way - #1480, #1687 + } + + return chart.renderer.crispLine([ + M, + horiz ? + this.left : + lineLeft, + horiz ? + lineTop : + this.top, + L, + horiz ? + chart.chartWidth - this.right : + lineLeft, + horiz ? + lineTop : + chart.chartHeight - this.bottom + ], lineWidth); + }, + + /** + * Position the title + */ + getTitlePosition: function () { + // compute anchor points for each of the title align options + var horiz = this.horiz, + axisLeft = this.left, + axisTop = this.top, + axisLength = this.len, + axisTitleOptions = this.options.title, + margin = horiz ? axisLeft : axisTop, + opposite = this.opposite, + offset = this.offset, + fontSize = pInt(axisTitleOptions.style.fontSize || 12), + + // the position in the length direction of the axis + alongAxis = { + low: margin + (horiz ? 0 : axisLength), + middle: margin + axisLength / 2, + high: margin + (horiz ? axisLength : 0) + }[axisTitleOptions.align], + + // the position in the perpendicular direction of the axis + offAxis = (horiz ? axisTop + this.height : axisLeft) + + (horiz ? 1 : -1) * // horizontal axis reverses the margin + (opposite ? -1 : 1) * // so does opposite axes + this.axisTitleMargin + + (this.side === 2 ? fontSize : 0); + + return { + x: horiz ? + alongAxis : + offAxis + (opposite ? this.width : 0) + offset + + (axisTitleOptions.x || 0), // x + y: horiz ? + offAxis - (opposite ? this.height : 0) + offset : + alongAxis + (axisTitleOptions.y || 0) // y + }; + }, + + /** + * Render the axis + */ + render: function () { + var axis = this, + horiz = axis.horiz, + reversed = axis.reversed, + chart = axis.chart, + renderer = chart.renderer, + options = axis.options, + isLog = axis.isLog, + isLinked = axis.isLinked, + tickPositions = axis.tickPositions, + sortedPositions, + axisTitle = axis.axisTitle, + ticks = axis.ticks, + minorTicks = axis.minorTicks, + alternateBands = axis.alternateBands, + stackLabelOptions = options.stackLabels, + alternateGridColor = options.alternateGridColor, + tickmarkOffset = axis.tickmarkOffset, + lineWidth = options.lineWidth, + linePath, + hasRendered = chart.hasRendered, + slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin), + hasData = axis.hasData, + showAxis = axis.showAxis, + from, + overflow = options.labels.overflow, + justifyLabels = axis.justifyLabels = horiz && overflow !== false, + to; + + // Reset + axis.labelEdge.length = 0; + axis.justifyToPlot = overflow === 'justify'; + + // Mark all elements inActive before we go over and mark the active ones + each([ticks, minorTicks, alternateBands], function (coll) { + var pos; + for (pos in coll) { + coll[pos].isActive = false; + } + }); + + // If the series has data draw the ticks. Else only the line and title + if (hasData || isLinked) { + + // minor ticks + if (axis.minorTickInterval && !axis.categories) { + each(axis.getMinorTickPositions(), function (pos) { + if (!minorTicks[pos]) { + minorTicks[pos] = new Tick(axis, pos, 'minor'); + } + + // render new ticks in old position + if (slideInTicks && minorTicks[pos].isNew) { + minorTicks[pos].render(null, true); + } + + minorTicks[pos].render(null, false, 1); + }); + } + + // Major ticks. Pull out the first item and render it last so that + // we can get the position of the neighbour label. #808. + if (tickPositions.length) { // #1300 + sortedPositions = tickPositions.slice(); + if ((horiz && reversed) || (!horiz && !reversed)) { + sortedPositions.reverse(); + } + if (justifyLabels) { + sortedPositions = sortedPositions.slice(1).concat([sortedPositions[0]]); + } + each(sortedPositions, function (pos, i) { + + // Reorganize the indices + if (justifyLabels) { + i = (i === sortedPositions.length - 1) ? 0 : i + 1; + } + + // linked axes need an extra check to find out if + if (!isLinked || (pos >= axis.min && pos <= axis.max)) { + + if (!ticks[pos]) { + ticks[pos] = new Tick(axis, pos); + } + + // render new ticks in old position + if (slideInTicks && ticks[pos].isNew) { + ticks[pos].render(i, true, 0.1); + } + + ticks[pos].render(i, false, 1); + } + + }); + // In a categorized axis, the tick marks are displayed between labels. So + // we need to add a tick mark and grid line at the left edge of the X axis. + if (tickmarkOffset && axis.min === 0) { + if (!ticks[-1]) { + ticks[-1] = new Tick(axis, -1, null, true); + } + ticks[-1].render(-1); + } + + } + + // alternate grid color + if (alternateGridColor) { + each(tickPositions, function (pos, i) { + if (i % 2 === 0 && pos < axis.max) { + if (!alternateBands[pos]) { + alternateBands[pos] = new Highcharts.PlotLineOrBand(axis); + } + from = pos + tickmarkOffset; // #949 + to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max; + alternateBands[pos].options = { + from: isLog ? lin2log(from) : from, + to: isLog ? lin2log(to) : to, + color: alternateGridColor + }; + alternateBands[pos].render(); + alternateBands[pos].isActive = true; + } + }); + } + + // custom plot lines and bands + if (!axis._addedPlotLB) { // only first time + each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) { + axis.addPlotBandOrLine(plotLineOptions); + }); + axis._addedPlotLB = true; + } + + } // end if hasData + + // Remove inactive ticks + each([ticks, minorTicks, alternateBands], function (coll) { + var pos, + i, + forDestruction = [], + delay = globalAnimation ? globalAnimation.duration || 500 : 0, + destroyInactiveItems = function () { + i = forDestruction.length; + while (i--) { + // When resizing rapidly, the same items may be destroyed in different timeouts, + // or the may be reactivated + if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) { + coll[forDestruction[i]].destroy(); + delete coll[forDestruction[i]]; + } + } + + }; + + for (pos in coll) { + + if (!coll[pos].isActive) { + // Render to zero opacity + coll[pos].render(pos, false, 0); + coll[pos].isActive = false; + forDestruction.push(pos); + } + } + + // When the objects are finished fading out, destroy them + if (coll === alternateBands || !chart.hasRendered || !delay) { + destroyInactiveItems(); + } else if (delay) { + setTimeout(destroyInactiveItems, delay); + } + }); + + // Static items. As the axis group is cleared on subsequent calls + // to render, these items are added outside the group. + // axis line + if (lineWidth) { + linePath = axis.getLinePath(lineWidth); + if (!axis.axisLine) { + axis.axisLine = renderer.path(linePath) + .attr({ + stroke: options.lineColor, + 'stroke-width': lineWidth, + zIndex: 7 + }) + .add(axis.axisGroup); + } else { + axis.axisLine.animate({ d: linePath }); + } + + // show or hide the line depending on options.showEmpty + axis.axisLine[showAxis ? 'show' : 'hide'](); + } + + if (axisTitle && showAxis) { + + axisTitle[axisTitle.isNew ? 'attr' : 'animate']( + axis.getTitlePosition() + ); + axisTitle.isNew = false; + } + + // Stacked totals: + if (stackLabelOptions && stackLabelOptions.enabled) { + axis.renderStackTotals(); + } + // End stacked totals + + axis.isDirty = false; + }, + + /** + * Redraw the axis to reflect changes in the data or axis extremes + */ + redraw: function () { + var axis = this, + chart = axis.chart, + pointer = chart.pointer; + + // hide tooltip and hover states + if (pointer) { + pointer.reset(true); + } + + // render the axis + axis.render(); + + // move plot lines and bands + each(axis.plotLinesAndBands, function (plotLine) { + plotLine.render(); + }); + + // mark associated series as dirty and ready for redraw + each(axis.series, function (series) { + series.isDirty = true; + }); + + }, + + /** + * Destroys an Axis instance. + */ + destroy: function (keepEvents) { + var axis = this, + stacks = axis.stacks, + stackKey, + plotLinesAndBands = axis.plotLinesAndBands, + i; + + // Remove the events + if (!keepEvents) { + removeEvent(axis); + } + + // Destroy each stack total + for (stackKey in stacks) { + destroyObjectProperties(stacks[stackKey]); + + stacks[stackKey] = null; + } + + // Destroy collections + each([axis.ticks, axis.minorTicks, axis.alternateBands], function (coll) { + destroyObjectProperties(coll); + }); + i = plotLinesAndBands.length; + while (i--) { // #1975 + plotLinesAndBands[i].destroy(); + } + + // Destroy local variables + each(['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup', 'cross', 'gridGroup', 'labelGroup'], function (prop) { + if (axis[prop]) { + axis[prop] = axis[prop].destroy(); + } + }); + + // Destroy crosshair + if (this.cross) { + this.cross.destroy(); + } + }, + + /** + * Draw the crosshair + */ + drawCrosshair: function (e, point) { + if (!this.crosshair) { return; }// Do not draw crosshairs if you don't have too. + + if ((defined(point) || !pick(this.crosshair.snap, true)) === false) { + this.hideCrosshair(); + return; + } + + var path, + options = this.crosshair, + animation = options.animation, + pos; + + // Get the path + if (!pick(options.snap, true)) { + pos = (this.horiz ? e.chartX - this.pos : this.len - e.chartY + this.pos); + } else if (defined(point)) { + /*jslint eqeq: true*/ + pos = (this.chart.inverted != this.horiz) ? point.plotX : this.len - point.plotY; + /*jslint eqeq: false*/ + } + + if (this.isRadial) { + path = this.getPlotLinePath(this.isXAxis ? point.x : pick(point.stackY, point.y)); + } else { + path = this.getPlotLinePath(null, null, null, null, pos); + } + + if (path === null) { + this.hideCrosshair(); + return; + } + + // Draw the cross + if (this.cross) { + this.cross + .attr({ visibility: VISIBLE })[animation ? 'animate' : 'attr']({ d: path }, animation); + } else { + var attribs = { + 'stroke-width': options.width || 1, + stroke: options.color || '#C0C0C0', + zIndex: options.zIndex || 2 + }; + if (options.dashStyle) { + attribs.dashstyle = options.dashStyle; + } + this.cross = this.chart.renderer.path(path).attr(attribs).add(); + } + }, + + /** + * Hide the crosshair. + */ + hideCrosshair: function () { + if (this.cross) { + this.cross.hide(); + } + } +}; // end Axis + +extend(Axis.prototype, AxisPlotLineOrBandExtension); + +/** + * 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 + */ +Axis.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek) { + var tickPositions = [], + i, + higherRanks = {}, + useUTC = defaultOptions.global.useUTC, + minYear, // used in months and years as a basis for Date.UTC() + minDate = new Date(min - timezoneOffset), + interval = normalizedInterval.unitRange, + count = normalizedInterval.count; + + if (defined(min)) { // #1300 + if (interval >= timeUnits[SECOND]) { // second + minDate.setMilliseconds(0); + minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 : + count * mathFloor(minDate.getSeconds() / count)); + } + + if (interval >= timeUnits[MINUTE]) { // minute + minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 : + count * mathFloor(minDate[getMinutes]() / count)); + } + + if (interval >= timeUnits[HOUR]) { // hour + minDate[setHours](interval >= timeUnits[DAY] ? 0 : + count * mathFloor(minDate[getHours]() / count)); + } + + if (interval >= timeUnits[DAY]) { // day + minDate[setDate](interval >= timeUnits[MONTH] ? 1 : + count * mathFloor(minDate[getDate]() / count)); + } + + if (interval >= timeUnits[MONTH]) { // month + minDate[setMonth](interval >= timeUnits[YEAR] ? 0 : + count * mathFloor(minDate[getMonth]() / count)); + minYear = minDate[getFullYear](); + } + + if (interval >= timeUnits[YEAR]) { // year + minYear -= minYear % count; + minDate[setFullYear](minYear); + } + + // week is a special case that runs outside the hierarchy + if (interval === timeUnits[WEEK]) { + // get start of current week, independent of count + minDate[setDate](minDate[getDate]() - minDate[getDay]() + + pick(startOfWeek, 1)); + } + + + // get tick positions + i = 1; + if (timezoneOffset) { + minDate = new Date(minDate.getTime() + timezoneOffset); + } + minYear = minDate[getFullYear](); + var time = minDate.getTime(), + minMonth = minDate[getMonth](), + minDateDate = minDate[getDate](), + localTimezoneOffset = useUTC ? + timezoneOffset : + (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950 + + // iterate and add tick positions at appropriate values + while (time < max) { + tickPositions.push(time); + + // if the interval is years, use Date.UTC to increase years + if (interval === timeUnits[YEAR]) { + time = makeTime(minYear + i * count, 0); + + // if the interval is months, use Date.UTC to increase months + } else if (interval === timeUnits[MONTH]) { + 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] || interval === timeUnits[WEEK])) { + time = makeTime(minYear, minMonth, minDateDate + + i * count * (interval === timeUnits[DAY] ? 1 : 7)); + + // else, the interval is fixed and we use simple addition + } else { + time += interval * count; + } + + i++; + } + + // push the last time + tickPositions.push(time); + + + // mark new days if the time is dividible by day (#1649, #1760) + each(grep(tickPositions, function (time) { + return interval <= timeUnits[HOUR] && time % timeUnits[DAY] === localTimezoneOffset; + }), function (time) { + higherRanks[time] = DAY; + }); + } + + + // record information on the chosen unit - for dynamic label formatter + tickPositions.info = extend(normalizedInterval, { + higherRanks: higherRanks, + totalRange: interval * count + }); + + return tickPositions; +}; + +/** + * 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. + * #662, #697. + */ +Axis.prototype.normalizeTimeTickInterval = function (tickInterval, unitsOption) { + var units = unitsOption || [[ + MILLISECOND, // unit name + [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples + ], [ + SECOND, + [1, 2, 5, 10, 15, 30] + ], [ + MINUTE, + [1, 2, 5, 10, 15, 30] + ], [ + HOUR, + [1, 2, 3, 4, 6, 8, 12] + ], [ + DAY, + [1, 2] + ], [ + WEEK, + [1, 2] + ], [ + MONTH, + [1, 2, 3, 4, 6] + ], [ + YEAR, + null + ]], + unit = units[units.length - 1], // default unit is years + interval = timeUnits[unit[0]], + multiples = unit[1], + count, + i; + + // loop through the units to find the one that best fits the tickInterval + for (i = 0; i < units.length; i++) { + unit = units[i]; + interval = timeUnits[unit[0]]; + multiples = unit[1]; + + + if (units[i + 1]) { + // lessThan is in the middle between the highest multiple and the next unit. + var lessThan = (interval * multiples[multiples.length - 1] + + timeUnits[units[i + 1][0]]) / 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] && tickInterval < 5 * interval) { + multiples = [1, 2, 5]; + } + + // get the count + count = normalizeTickInterval( + tickInterval / interval, + multiples, + unit[0] === YEAR ? mathMax(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360 + ); + + return { + unitRange: interval, + count: count, + unitName: unit[0] + }; +};/** + * Methods defined on the Axis prototype + */ + +/** + * Set the tick positions of a logarithmic axis + */ +Axis.prototype.getLogTickPositions = function (interval, min, max, minor) { + var axis = this, + options = axis.options, + axisLength = axis.len, + // Since we use this method for both major and minor ticks, + // use a local variable and return the result + positions = []; + + // Reset + if (!minor) { + axis._minorAutoInterval = null; + } + + // First case: All ticks fall on whole logarithms: 1, 10, 100 etc. + if (interval >= 0.5) { + interval = mathRound(interval); + positions = axis.getLinearTickPositions(interval, min, max); + + // Second case: We need intermediary ticks. For example + // 1, 2, 4, 6, 8, 10, 20, 40 etc. + } else if (interval >= 0.08) { + var roundedMin = mathFloor(min), + intermediate, + i, + j, + len, + pos, + lastPos, + break2; + + if (interval > 0.3) { + intermediate = [1, 2, 4]; + } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc + intermediate = [1, 2, 4, 6, 8]; + } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc + intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + } + + for (i = roundedMin; i < max + 1 && !break2; i++) { + len = intermediate.length; + for (j = 0; j < len && !break2; j++) { + pos = log2lin(lin2log(i) * intermediate[j]); + + if (pos > min && (!minor || lastPos <= max)) { // #1670 + positions.push(lastPos); + } + + if (lastPos > max) { + break2 = true; + } + lastPos = pos; + } + } + + // Third case: We are so deep in between whole logarithmic values that + // we might as well handle the tick positions like a linear axis. For + // example 1.01, 1.02, 1.03, 1.04. + } else { + var realMin = lin2log(min), + realMax = lin2log(max), + tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'], + filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption, + tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), + totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength; + + interval = pick( + filteredTickIntervalOption, + axis._minorAutoInterval, + (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1) + ); + + interval = normalizeTickInterval( + interval, + null, + getMagnitude(interval) + ); + + positions = map(axis.getLinearTickPositions( + interval, + realMin, + realMax + ), log2lin); + + if (!minor) { + axis._minorAutoInterval = interval / 5; + } + } + + // Set the axis-level tickInterval variable + if (!minor) { + axis.tickInterval = interval; + } + return positions; +};/** + * The tooltip object + * @param {Object} chart The chart instance + * @param {Object} options Tooltip options + */ +var Tooltip = Highcharts.Tooltip = function () { + this.init.apply(this, arguments); +}; + +Tooltip.prototype = { + + init: function (chart, options) { + + var borderWidth = options.borderWidth, + style = options.style, + padding = pInt(style.padding); + + // Save the chart and options + this.chart = chart; + this.options = options; + + // Keep track of the current series + //this.currentSeries = UNDEFINED; + + // List of crosshairs + this.crosshairs = []; + + // Current values of x and y when animating + this.now = { x: 0, y: 0 }; + + // The tooltip is initially hidden + this.isHidden = true; + + + // create the label + this.label = chart.renderer.label('', 0, 0, options.shape || 'callout', null, null, options.useHTML, null, 'tooltip') + .attr({ + padding: padding, + fill: options.backgroundColor, + 'stroke-width': borderWidth, + r: options.borderRadius, + zIndex: 8 + }) + .css(style) + .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117) + .add() + .attr({ y: -9999 }); // #2301, #2657 + + // When using canVG the shadow shows up as a gray circle + // even if the tooltip is hidden. + if (!useCanVG) { + this.label.shadow(options.shadow); + } + + // Public property for getting the shared state. + this.shared = options.shared; + }, + + /** + * Destroy the tooltip and its elements. + */ + destroy: function () { + // Destroy and clear local variables + if (this.label) { + this.label = this.label.destroy(); + } + clearTimeout(this.hideTimer); + clearTimeout(this.tooltipTimeout); + }, + + /** + * Provide a soft movement for the tooltip + * + * @param {Number} x + * @param {Number} y + * @private + */ + move: function (x, y, anchorX, anchorY) { + var tooltip = this, + now = tooltip.now, + animate = tooltip.options.animation !== false && !tooltip.isHidden, + skipAnchor = tooltip.followPointer || tooltip.len > 1; + + // get intermediate values for animation + extend(now, { + x: animate ? (2 * now.x + x) / 3 : x, + y: animate ? (now.y + y) / 2 : y, + anchorX: skipAnchor ? UNDEFINED : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX, + anchorY: skipAnchor ? UNDEFINED : animate ? (now.anchorY + anchorY) / 2 : anchorY + }); + + // move to the intermediate value + tooltip.label.attr(now); + + + // run on next tick of the mouse tracker + if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) { + + // never allow two timeouts + clearTimeout(this.tooltipTimeout); + + // set the fixed interval ticking for the smooth tooltip + this.tooltipTimeout = setTimeout(function () { + // The interval function may still be running during destroy, so check that the chart is really there before calling. + if (tooltip) { + tooltip.move(x, y, anchorX, anchorY); + } + }, 32); + + } + }, + + /** + * Hide the tooltip + */ + hide: function () { + var tooltip = this, + hoverPoints; + + clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766) + if (!this.isHidden) { + hoverPoints = this.chart.hoverPoints; + + this.hideTimer = setTimeout(function () { + tooltip.label.fadeOut(); + tooltip.isHidden = true; + }, pick(this.options.hideDelay, 500)); + + // hide previous hoverPoints and set new + if (hoverPoints) { + each(hoverPoints, function (point) { + point.setState(); + }); + } + + this.chart.hoverPoints = null; + } + }, + + /** + * Extendable method to get the anchor position of the tooltip + * from a point or set of points + */ + getAnchor: function (points, mouseEvent) { + var ret, + chart = this.chart, + inverted = chart.inverted, + plotTop = chart.plotTop, + plotX = 0, + plotY = 0, + yAxis; + + points = splat(points); + + // Pie uses a special tooltipPos + ret = points[0].tooltipPos; + + // When tooltip follows mouse, relate the position to the mouse + if (this.followPointer && mouseEvent) { + if (mouseEvent.chartX === UNDEFINED) { + mouseEvent = chart.pointer.normalize(mouseEvent); + } + ret = [ + mouseEvent.chartX - chart.plotLeft, + mouseEvent.chartY - plotTop + ]; + } + // When shared, use the average position + if (!ret) { + each(points, function (point) { + yAxis = point.series.yAxis; + plotX += point.plotX; + plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) + + (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151 + }); + + plotX /= points.length; + plotY /= points.length; + + ret = [ + inverted ? chart.plotWidth - plotY : plotX, + this.shared && !inverted && points.length > 1 && mouseEvent ? + mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424) + inverted ? chart.plotHeight - plotX : plotY + ]; + } + + return map(ret, mathRound); + }, + + /** + * Place the tooltip in a chart without spilling over + * and not covering the point it self. + */ + getPosition: function (boxWidth, boxHeight, point) { + + var chart = this.chart, + distance = this.distance, + ret = {}, + swapped, + first = ['y', chart.chartHeight, boxHeight, point.plotY + chart.plotTop], + second = ['x', chart.chartWidth, boxWidth, point.plotX + chart.plotLeft], + // The far side is right or bottom + preferFarSide = point.ttBelow || (chart.inverted && !point.negative) || (!chart.inverted && point.negative), + /** + * Handle the preferred dimension. When the preferred dimension is tooltip + * on top or bottom of the point, it will look for space there. + */ + firstDimension = function (dim, outerSize, innerSize, point) { + var roomLeft = innerSize < point - distance, + roomRight = point + distance + innerSize < outerSize, + alignedLeft = point - distance - innerSize, + alignedRight = point + distance; + + if (preferFarSide && roomRight) { + ret[dim] = alignedRight; + } else if (!preferFarSide && roomLeft) { + ret[dim] = alignedLeft; + } else if (roomLeft) { + ret[dim] = alignedLeft; + } else if (roomRight) { + ret[dim] = alignedRight; + } else { + return false; + } + }, + /** + * Handle the secondary dimension. If the preferred dimension is tooltip + * on top or bottom of the point, the second dimension is to align the tooltip + * above the point, trying to align center but allowing left or right + * align within the chart box. + */ + secondDimension = function (dim, outerSize, innerSize, point) { + // Too close to the edge, return false and swap dimensions + if (point < distance || point > outerSize - distance) { + return false; + + // Align left/top + } else if (point < innerSize / 2) { + ret[dim] = 1; + // Align right/bottom + } else if (point > outerSize - innerSize / 2) { + ret[dim] = outerSize - innerSize - 2; + // Align center + } else { + ret[dim] = point - innerSize / 2; + } + }, + /** + * Swap the dimensions + */ + swap = function (count) { + var temp = first; + first = second; + second = temp; + swapped = count; + }, + run = function () { + if (firstDimension.apply(0, first) !== false) { + if (secondDimension.apply(0, second) === false && !swapped) { + swap(true); + run(); + } + } else if (!swapped) { + swap(true); + run(); + } else { + ret.x = ret.y = 0; + } + }; + + // Under these conditions, prefer the tooltip on the side of the point + if (chart.inverted || this.len > 1) { + swap(); + } + run(); + + return ret; + + }, + + /** + * In case no user defined formatter is given, this will be used. Note that the context + * here is an object holding point, series, x, y etc. + */ + defaultFormatter: function (tooltip) { + var items = this.points || splat(this), + series = items[0].series, + s; + + // build the header + s = [tooltip.tooltipHeaderFormatter(items[0])]; + + // build the values + each(items, function (item) { + series = item.series; + s.push((series.tooltipFormatter && series.tooltipFormatter(item)) || + item.point.tooltipFormatter(series.tooltipOptions.pointFormat)); + }); + + // footer + s.push(tooltip.options.footerFormat || ''); + + return s.join(''); + }, + + /** + * Refresh the tooltip's text and position. + * @param {Object} point + */ + refresh: function (point, mouseEvent) { + var tooltip = this, + chart = tooltip.chart, + label = tooltip.label, + options = tooltip.options, + x, + y, + anchor, + textConfig = {}, + text, + pointConfig = [], + formatter = options.formatter || tooltip.defaultFormatter, + hoverPoints = chart.hoverPoints, + borderColor, + shared = tooltip.shared, + currentSeries; + + clearTimeout(this.hideTimer); + + // get the reference point coordinates (pie charts use tooltipPos) + tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer; + anchor = tooltip.getAnchor(point, mouseEvent); + x = anchor[0]; + y = anchor[1]; + + // shared tooltip, array is sent over + if (shared && !(point.series && point.series.noSharedTooltip)) { + + // hide previous hoverPoints and set new + + chart.hoverPoints = point; + if (hoverPoints) { + each(hoverPoints, function (point) { + point.setState(); + }); + } + + each(point, function (item) { + item.setState(HOVER_STATE); + + pointConfig.push(item.getLabelConfig()); + }); + + textConfig = { + x: point[0].category, + y: point[0].y + }; + textConfig.points = pointConfig; + this.len = pointConfig.length; + point = point[0]; + + // single point tooltip + } else { + textConfig = point.getLabelConfig(); + } + text = formatter.call(textConfig, tooltip); + + // register the current series + currentSeries = point.series; + this.distance = pick(currentSeries.tooltipOptions.distance, 16); + + // update the inner HTML + if (text === false) { + this.hide(); + } else { + + // show it + if (tooltip.isHidden) { + stop(label); + label.attr('opacity', 1).show(); + } + + // update text + label.attr({ + text: text + }); + + // set the stroke color of the box + borderColor = options.borderColor || point.color || currentSeries.color || '#606060'; + label.attr({ + stroke: borderColor + }); + + tooltip.updatePosition({ plotX: x, plotY: y, negative: point.negative, ttBelow: point.ttBelow }); + + this.isHidden = false; + } + fireEvent(chart, 'tooltipRefresh', { + text: text, + x: x + chart.plotLeft, + y: y + chart.plotTop, + borderColor: borderColor + }); + }, + + /** + * Find the new position and perform the move + */ + updatePosition: function (point) { + var chart = this.chart, + label = this.label, + pos = (this.options.positioner || this.getPosition).call( + this, + label.width, + label.height, + point + ); + + // do the move + this.move( + mathRound(pos.x), + mathRound(pos.y), + point.plotX + chart.plotLeft, + point.plotY + chart.plotTop + ); + }, + + + /** + * Format the header of the tooltip + */ + tooltipHeaderFormatter: function (point) { + var series = point.series, + tooltipOptions = series.tooltipOptions, + dateTimeLabelFormats = tooltipOptions.dateTimeLabelFormats, + xDateFormat = tooltipOptions.xDateFormat, + xAxis = series.xAxis, + isDateTime = xAxis && xAxis.options.type === 'datetime' && isNumber(point.key), + headerFormat = tooltipOptions.headerFormat, + closestPointRange = xAxis && xAxis.closestPointRange, + n; + + // Guess the best date format based on the closest point distance (#568) + if (isDateTime && !xDateFormat) { + if (closestPointRange) { + for (n in timeUnits) { + if (timeUnits[n] >= closestPointRange || + // If the point is placed every day at 23:59, we need to show + // the minutes as well. This logic only works for time units less than + // a day, since all higher time units are dividable by those. #2637. + (timeUnits[n] <= timeUnits[DAY] && point.key % timeUnits[n] > 0)) { + xDateFormat = dateTimeLabelFormats[n]; + break; + } + } + } else { + xDateFormat = dateTimeLabelFormats.day; + } + + xDateFormat = xDateFormat || dateTimeLabelFormats.year; // #2546, 2581 + + } + + // Insert the header date format if any + if (isDateTime && xDateFormat) { + headerFormat = headerFormat.replace('{point.key}', '{point.key:' + xDateFormat + '}'); + } + + return format(headerFormat, { + point: point, + series: series + }); + } +}; + +var hoverChartIndex; + +// Global flag for touch support +hasTouch = doc.documentElement.ontouchstart !== UNDEFINED; + +/** + * The mouse tracker object. All methods starting with "on" are primary DOM event handlers. + * Subsequent methods should be named differently from what they are doing. + * @param {Object} chart The Chart instance + * @param {Object} options The root options object + */ +var Pointer = Highcharts.Pointer = function (chart, options) { + this.init(chart, options); +}; + +Pointer.prototype = { + /** + * Initialize Pointer + */ + init: function (chart, options) { + + var chartOptions = options.chart, + chartEvents = chartOptions.events, + zoomType = useCanVG ? '' : chartOptions.zoomType, + inverted = chart.inverted, + zoomX, + zoomY; + + // Store references + this.options = options; + this.chart = chart; + + // Zoom status + this.zoomX = zoomX = /x/.test(zoomType); + this.zoomY = zoomY = /y/.test(zoomType); + this.zoomHor = (zoomX && !inverted) || (zoomY && inverted); + this.zoomVert = (zoomY && !inverted) || (zoomX && inverted); + this.hasZoom = zoomX || zoomY; + + // Do we need to handle click on a touch device? + this.runChartClick = chartEvents && !!chartEvents.click; + + this.pinchDown = []; + this.lastValidTouch = {}; + + if (Highcharts.Tooltip && options.tooltip.enabled) { + chart.tooltip = new Tooltip(chart, options.tooltip); + this.followTouchMove = options.tooltip.followTouchMove; + } + + this.setDOMEvents(); + }, + + /** + * Add crossbrowser support for chartX and chartY + * @param {Object} e The event object in standard browsers + */ + normalize: function (e, chartPosition) { + var chartX, + chartY, + ePos; + + // common IE normalizing + e = e || window.event; + + // Framework specific normalizing (#1165) + e = washMouseEvent(e); + + // More IE normalizing, needs to go after washMouseEvent + if (!e.target) { + e.target = e.srcElement; + } + + // iOS (#2757) + ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e; + + // Get mouse position + if (!chartPosition) { + this.chartPosition = chartPosition = offset(this.chart.container); + } + + // chartX and chartY + if (ePos.pageX === UNDEFINED) { // IE < 9. #886. + chartX = mathMax(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is + // for IE10 quirks mode within framesets + chartY = e.y; + } else { + chartX = ePos.pageX - chartPosition.left; + chartY = ePos.pageY - chartPosition.top; + } + + return extend(e, { + chartX: mathRound(chartX), + chartY: mathRound(chartY) + }); + }, + + /** + * Get the click position in terms of axis values. + * + * @param {Object} e A pointer event + */ + getCoordinates: function (e) { + var coordinates = { + xAxis: [], + yAxis: [] + }; + + each(this.chart.axes, function (axis) { + coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({ + axis: axis, + value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY']) + }); + }); + return coordinates; + }, + + /** + * Return the index in the tooltipPoints array, corresponding to pixel position in + * the plot area. + */ + getIndex: function (e) { + var chart = this.chart; + return chart.inverted ? + chart.plotHeight + chart.plotTop - e.chartY : + e.chartX - chart.plotLeft; + }, + + /** + * With line type charts with a single tracker, get the point closest to the mouse. + * Run Point.onMouseOver and display tooltip for the point or points. + */ + runPointActions: function (e) { + var pointer = this, + chart = pointer.chart, + series = chart.series, + tooltip = chart.tooltip, + followPointer, + point, + points, + hoverPoint = chart.hoverPoint, + hoverSeries = chart.hoverSeries, + i, + j, + distance = chart.chartWidth, + index = pointer.getIndex(e), + anchor; + + // shared tooltip + if (tooltip && pointer.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) { + points = []; + + // loop over all series and find the ones with points closest to the mouse + i = series.length; + for (j = 0; j < i; j++) { + if (series[j].visible && + series[j].options.enableMouseTracking !== false && + !series[j].noSharedTooltip && series[j].singularTooltips !== true && series[j].tooltipPoints.length) { + point = series[j].tooltipPoints[index]; + if (point && point.series) { // not a dummy point, #1544 + point._dist = mathAbs(index - point.clientX); + distance = mathMin(distance, point._dist); + points.push(point); + } + } + } + // remove furthest points + i = points.length; + while (i--) { + if (points[i]._dist > distance) { + points.splice(i, 1); + } + } + // refresh the tooltip if necessary + if (points.length && (points[0].clientX !== pointer.hoverX)) { + tooltip.refresh(points, e); + pointer.hoverX = points[0].clientX; + } + } + + // Separate tooltip and general mouse events + followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer; + if (hoverSeries && hoverSeries.tracker && !followPointer) { // #2584, #2830 + + // get the point + point = hoverSeries.tooltipPoints[index]; + + // a new point is hovered, refresh the tooltip + if (point && point !== hoverPoint) { + + // trigger the events + point.onMouseOver(e); + + } + + } else if (tooltip && followPointer && !tooltip.isHidden) { + anchor = tooltip.getAnchor([{}], e); + tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] }); + } + + // Start the event listener to pick up the tooltip + if (tooltip && !pointer._onDocumentMouseMove) { + pointer._onDocumentMouseMove = function (e) { + if (charts[hoverChartIndex]) { + charts[hoverChartIndex].pointer.onDocumentMouseMove(e); + } + }; + addEvent(doc, 'mousemove', pointer._onDocumentMouseMove); + } + + // Draw independent crosshairs + each(chart.axes, function (axis) { + axis.drawCrosshair(e, pick(point, hoverPoint)); + }); + }, + + + + /** + * Reset the tracking by hiding the tooltip, the hover series state and the hover point + * + * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible + */ + reset: function (allowMove) { + var pointer = this, + chart = pointer.chart, + hoverSeries = chart.hoverSeries, + hoverPoint = chart.hoverPoint, + tooltip = chart.tooltip, + tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint; + + // Narrow in allowMove + allowMove = allowMove && tooltip && tooltipPoints; + + // Check if the points have moved outside the plot area, #1003 + if (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) { + allowMove = false; + } + + // Just move the tooltip, #349 + if (allowMove) { + tooltip.refresh(tooltipPoints); + if (hoverPoint) { // #2500 + hoverPoint.setState(hoverPoint.state, true); + } + + // Full reset + } else { + + if (hoverPoint) { + hoverPoint.onMouseOut(); + } + + if (hoverSeries) { + hoverSeries.onMouseOut(); + } + + if (tooltip) { + tooltip.hide(); + } + + if (pointer._onDocumentMouseMove) { + removeEvent(doc, 'mousemove', pointer._onDocumentMouseMove); + pointer._onDocumentMouseMove = null; + } + + // Remove crosshairs + each(chart.axes, function (axis) { + axis.hideCrosshair(); + }); + + pointer.hoverX = null; + + } + }, + + /** + * Scale series groups to a certain scale and translation + */ + scaleGroups: function (attribs, clip) { + + var chart = this.chart, + seriesAttribs; + + // Scale each series + each(chart.series, function (series) { + seriesAttribs = attribs || series.getPlotBox(); // #1701 + if (series.xAxis && series.xAxis.zoomEnabled) { + series.group.attr(seriesAttribs); + if (series.markerGroup) { + series.markerGroup.attr(seriesAttribs); + series.markerGroup.clip(clip ? chart.clipRect : null); + } + if (series.dataLabelsGroup) { + series.dataLabelsGroup.attr(seriesAttribs); + } + } + }); + + // Clip + chart.clipRect.attr(clip || chart.clipBox); + }, + + /** + * Start a drag operation + */ + dragStart: function (e) { + var chart = this.chart; + + // Record the start position + chart.mouseIsDown = e.type; + chart.cancelClick = false; + chart.mouseDownX = this.mouseDownX = e.chartX; + chart.mouseDownY = this.mouseDownY = e.chartY; + }, + + /** + * Perform a drag operation in response to a mousemove event while the mouse is down + */ + drag: function (e) { + + var chart = this.chart, + chartOptions = chart.options.chart, + chartX = e.chartX, + chartY = e.chartY, + zoomHor = this.zoomHor, + zoomVert = this.zoomVert, + plotLeft = chart.plotLeft, + plotTop = chart.plotTop, + plotWidth = chart.plotWidth, + plotHeight = chart.plotHeight, + clickedInside, + size, + mouseDownX = this.mouseDownX, + mouseDownY = this.mouseDownY; + + // If the mouse is outside the plot area, adjust to cooordinates + // inside to prevent the selection marker from going outside + if (chartX < plotLeft) { + chartX = plotLeft; + } else if (chartX > plotLeft + plotWidth) { + chartX = plotLeft + plotWidth; + } + + if (chartY < plotTop) { + chartY = plotTop; + } else if (chartY > plotTop + plotHeight) { + chartY = plotTop + plotHeight; + } + + // determine if the mouse has moved more than 10px + this.hasDragged = Math.sqrt( + Math.pow(mouseDownX - chartX, 2) + + Math.pow(mouseDownY - chartY, 2) + ); + + if (this.hasDragged > 10) { + clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop); + + // make a selection + if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) { + if (!this.selectionMarker) { + this.selectionMarker = chart.renderer.rect( + plotLeft, + plotTop, + zoomHor ? 1 : plotWidth, + zoomVert ? 1 : plotHeight, + 0 + ) + .attr({ + fill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)', + zIndex: 7 + }) + .add(); + } + } + + // adjust the width of the selection marker + if (this.selectionMarker && zoomHor) { + size = chartX - mouseDownX; + this.selectionMarker.attr({ + width: mathAbs(size), + x: (size > 0 ? 0 : size) + mouseDownX + }); + } + // adjust the height of the selection marker + if (this.selectionMarker && zoomVert) { + size = chartY - mouseDownY; + this.selectionMarker.attr({ + height: mathAbs(size), + y: (size > 0 ? 0 : size) + mouseDownY + }); + } + + // panning + if (clickedInside && !this.selectionMarker && chartOptions.panning) { + chart.pan(e, chartOptions.panning); + } + } + }, + + /** + * On mouse up or touch end across the entire document, drop the selection. + */ + drop: function (e) { + var chart = this.chart, + hasPinched = this.hasPinched; + + if (this.selectionMarker) { + var selectionData = { + xAxis: [], + yAxis: [], + originalEvent: e.originalEvent || e + }, + selectionBox = this.selectionMarker, + selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x, + selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y, + selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width, + selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height, + runZoom; + + // a selection has been made + if (this.hasDragged || hasPinched) { + + // record each axis' min and max + each(chart.axes, function (axis) { + if (axis.zoomEnabled) { + var horiz = axis.horiz, + selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)), + selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight)); + + if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859 + selectionData[axis.coll].push({ + axis: axis, + min: mathMin(selectionMin, selectionMax), // for reversed axes, + max: mathMax(selectionMin, selectionMax) + }); + runZoom = true; + } + } + }); + if (runZoom) { + fireEvent(chart, 'selection', selectionData, function (args) { + chart.zoom(extend(args, hasPinched ? { animation: false } : null)); + }); + } + + } + this.selectionMarker = this.selectionMarker.destroy(); + + // Reset scaling preview + if (hasPinched) { + this.scaleGroups(); + } + } + + // Reset all + if (chart) { // it may be destroyed on mouse up - #877 + css(chart.container, { cursor: chart._cursor }); + chart.cancelClick = this.hasDragged > 10; // #370 + chart.mouseIsDown = this.hasDragged = this.hasPinched = false; + this.pinchDown = []; + } + }, + + onContainerMouseDown: function (e) { + + e = this.normalize(e); + + // issue #295, dragging not always working in Firefox + if (e.preventDefault) { + e.preventDefault(); + } + + this.dragStart(e); + }, + + + + onDocumentMouseUp: function (e) { + if (charts[hoverChartIndex]) { + charts[hoverChartIndex].pointer.drop(e); + } + }, + + /** + * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea. + * Issue #149 workaround. The mouseleave event does not always fire. + */ + onDocumentMouseMove: function (e) { + var chart = this.chart, + chartPosition = this.chartPosition, + hoverSeries = chart.hoverSeries; + + e = this.normalize(e, chartPosition); + + // If we're outside, hide the tooltip + if (chartPosition && hoverSeries && !this.inClass(e.target, 'highcharts-tracker') && + !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { + this.reset(); + } + }, + + /** + * When mouse leaves the container, hide the tooltip. + */ + onContainerMouseLeave: function () { + var chart = charts[hoverChartIndex]; + if (chart) { + chart.pointer.reset(); + chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix + } + }, + + // The mousemove, touchmove and touchstart event handler + onContainerMouseMove: function (e) { + + var chart = this.chart; + + hoverChartIndex = chart.index; + + // normalize + e = this.normalize(e); + + if (chart.mouseIsDown === 'mousedown') { + this.drag(e); + } + + // Show the tooltip and run mouse over events (#977) + if ((this.inClass(e.target, 'highcharts-tracker') || + chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) { + this.runPointActions(e); + } + }, + + /** + * Utility to detect whether an element has, or has a parent with, a specific + * class name. Used on detection of tracker objects and on deciding whether + * hovering the tooltip should cause the active series to mouse out. + */ + inClass: function (element, className) { + var elemClassName; + while (element) { + elemClassName = attr(element, 'class'); + if (elemClassName) { + if (elemClassName.indexOf(className) !== -1) { + return true; + } else if (elemClassName.indexOf(PREFIX + 'container') !== -1) { + return false; + } + } + element = element.parentNode; + } + }, + + onTrackerMouseOut: function (e) { + var series = this.chart.hoverSeries, + relatedTarget = e.relatedTarget || e.toElement, + relatedSeries = relatedTarget && relatedTarget.point && relatedTarget.point.series; // #2499 + + if (series && !series.options.stickyTracking && !this.inClass(relatedTarget, PREFIX + 'tooltip') && + relatedSeries !== series) { + series.onMouseOut(); + } + }, + + onContainerClick: function (e) { + var chart = this.chart, + hoverPoint = chart.hoverPoint, + plotLeft = chart.plotLeft, + plotTop = chart.plotTop; + + e = this.normalize(e); + e.cancelBubble = true; // IE specific + + if (!chart.cancelClick) { + + // On tracker click, fire the series and point events. #783, #1583 + if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) { + + // the series click event + fireEvent(hoverPoint.series, 'click', extend(e, { + point: hoverPoint + })); + + // the point click event + if (chart.hoverPoint) { // it may be destroyed (#1844) + hoverPoint.firePointEvent('click', e); + } + + // When clicking outside a tracker, fire a chart event + } else { + extend(e, this.getCoordinates(e)); + + // fire a click event in the chart + if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) { + fireEvent(chart, 'click', e); + } + } + + + } + }, + + /** + * Set the JS DOM events on the container and document. This method should contain + * a one-to-one assignment between methods and their handlers. Any advanced logic should + * be moved to the handler reflecting the event's name. + */ + setDOMEvents: function () { + + var pointer = this, + container = pointer.chart.container; + + container.onmousedown = function (e) { + pointer.onContainerMouseDown(e); + }; + container.onmousemove = function (e) { + pointer.onContainerMouseMove(e); + }; + container.onclick = function (e) { + pointer.onContainerClick(e); + }; + addEvent(container, 'mouseleave', pointer.onContainerMouseLeave); + if (chartCount === 1) { + addEvent(doc, 'mouseup', pointer.onDocumentMouseUp); + } + if (hasTouch) { + container.ontouchstart = function (e) { + pointer.onContainerTouchStart(e); + }; + container.ontouchmove = function (e) { + pointer.onContainerTouchMove(e); + }; + if (chartCount === 1) { + addEvent(doc, 'touchend', pointer.onDocumentTouchEnd); + } + } + + }, + + /** + * Destroys the Pointer object and disconnects DOM events. + */ + destroy: function () { + var prop; + + removeEvent(this.chart.container, 'mouseleave', this.onContainerMouseLeave); + if (!chartCount) { + removeEvent(doc, 'mouseup', this.onDocumentMouseUp); + removeEvent(doc, 'touchend', this.onDocumentTouchEnd); + } + + // memory and CPU leak + clearInterval(this.tooltipTimeout); + + for (prop in this) { + this[prop] = null; + } + } +}; + + +/* Support for touch devices */ +extend(Highcharts.Pointer.prototype, { + + /** + * Run translation operations + */ + pinchTranslate: function (pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) { + if (this.zoomHor || this.pinchHor) { + this.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); + } + if (this.zoomVert || this.pinchVert) { + this.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); + } + }, + + /** + * Run translation operations for each direction (horizontal and vertical) independently + */ + pinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch, forcedScale) { + var chart = this.chart, + xy = horiz ? 'x' : 'y', + XY = horiz ? 'X' : 'Y', + sChartXY = 'chart' + XY, + wh = horiz ? 'width' : 'height', + plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')], + selectionWH, + selectionXY, + clipXY, + scale = forcedScale || 1, + inverted = chart.inverted, + bounds = chart.bounds[horiz ? 'h' : 'v'], + singleTouch = pinchDown.length === 1, + touch0Start = pinchDown[0][sChartXY], + touch0Now = touches[0][sChartXY], + touch1Start = !singleTouch && pinchDown[1][sChartXY], + touch1Now = !singleTouch && touches[1][sChartXY], + outOfBounds, + transformScale, + scaleKey, + setScale = function () { + if (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis + scale = forcedScale || mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start); + } + + clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start; + selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale; + }; + + // Set the scale, first pass + setScale(); + + selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not + + // Out of bounds + if (selectionXY < bounds.min) { + selectionXY = bounds.min; + outOfBounds = true; + } else if (selectionXY + selectionWH > bounds.max) { + selectionXY = bounds.max - selectionWH; + outOfBounds = true; + } + + // Is the chart dragged off its bounds, determined by dataMin and dataMax? + if (outOfBounds) { + + // Modify the touchNow position in order to create an elastic drag movement. This indicates + // to the user that the chart is responsive but can't be dragged further. + touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]); + if (!singleTouch) { + touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]); + } + + // Set the scale, second pass to adapt to the modified touchNow positions + setScale(); + + } else { + lastValidTouch[xy] = [touch0Now, touch1Now]; + } + + // Set geometry for clipping, selection and transformation + if (!inverted) { // TODO: implement clipping for inverted charts + clip[xy] = clipXY - plotLeftTop; + clip[wh] = selectionWH; + } + scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY; + transformScale = inverted ? 1 / scale : scale; + + selectionMarker[wh] = selectionWH; + selectionMarker[xy] = selectionXY; + transform[scaleKey] = scale; + transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start)); + }, + + /** + * Handle touch events with two touches + */ + pinch: function (e) { + + var self = this, + chart = self.chart, + pinchDown = self.pinchDown, + followTouchMove = self.followTouchMove, + touches = e.touches, + touchesLength = touches.length, + lastValidTouch = self.lastValidTouch, + hasZoom = self.hasZoom, + selectionMarker = self.selectionMarker, + transform = {}, + fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') && + chart.runTrackerClick) || chart.runChartClick), + clip = {}; + + // On touch devices, only proceed to trigger click if a handler is defined + if ((hasZoom || followTouchMove) && !fireClickEvent) { + e.preventDefault(); + } + + // Normalize each touch + map(touches, function (e) { + return self.normalize(e); + }); + + // Register the touch start position + if (e.type === 'touchstart') { + each(touches, function (e, i) { + pinchDown[i] = { chartX: e.chartX, chartY: e.chartY }; + }); + lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX]; + lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY]; + + // Identify the data bounds in pixels + each(chart.axes, function (axis) { + if (axis.zoomEnabled) { + var bounds = chart.bounds[axis.horiz ? 'h' : 'v'], + minPixelPadding = axis.minPixelPadding, + min = axis.toPixels(axis.dataMin), + max = axis.toPixels(axis.dataMax), + absMin = mathMin(min, max), + absMax = mathMax(min, max); + + // Store the bounds for use in the touchmove handler + bounds.min = mathMin(axis.pos, absMin - minPixelPadding); + bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding); + } + }); + + // Event type is touchmove, handle panning and pinching + } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first + + + // Set the marker + if (!selectionMarker) { + self.selectionMarker = selectionMarker = extend({ + destroy: noop + }, chart.plotBox); + } + + self.pinchTranslate(pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); + + self.hasPinched = hasZoom; + + // Scale and translate the groups to provide visual feedback during pinching + self.scaleGroups(transform, clip); + + // Optionally move the tooltip on touchmove + if (!hasZoom && followTouchMove && touchesLength === 1) { + this.runPointActions(self.normalize(e)); + } + } + }, + + onContainerTouchStart: function (e) { + var chart = this.chart; + + hoverChartIndex = chart.index; + + if (e.touches.length === 1) { + + e = this.normalize(e); + + if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { + + // Run mouse events and display tooltip etc + this.runPointActions(e); + + this.pinch(e); + + } else { + // Hide the tooltip on touching outside the plot area (#1203) + this.reset(); + } + + } else if (e.touches.length === 2) { + this.pinch(e); + } + }, + + onContainerTouchMove: function (e) { + if (e.touches.length === 1 || e.touches.length === 2) { + this.pinch(e); + } + }, + + onDocumentTouchEnd: function (e) { + if (charts[hoverChartIndex]) { + charts[hoverChartIndex].pointer.drop(e); + } + } + +}); +if (win.PointerEvent || win.MSPointerEvent) { + + // The touches object keeps track of the points being touched at all times + var touches = {}, + hasPointerEvent = !!win.PointerEvent, + getWebkitTouches = function () { + var key, fake = []; + fake.item = function (i) { return this[i]; }; + for (key in touches) { + if (touches.hasOwnProperty(key)) { + fake.push({ + pageX: touches[key].pageX, + pageY: touches[key].pageY, + target: touches[key].target + }); + } + } + return fake; + }, + translateMSPointer = function (e, method, wktype, callback) { + var p; + e = e.originalEvent || e; + if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[hoverChartIndex]) { + callback(e); + p = charts[hoverChartIndex].pointer; + p[method]({ + type: wktype, + target: e.currentTarget, + preventDefault: noop, + touches: getWebkitTouches() + }); + } + }; + + /** + * Extend the Pointer prototype with methods for each event handler and more + */ + extend(Pointer.prototype, { + onContainerPointerDown: function (e) { + translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) { + touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY, target: e.currentTarget }; + }); + }, + onContainerPointerMove: function (e) { + translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function (e) { + touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY }; + if (!touches[e.pointerId].target) { + touches[e.pointerId].target = e.currentTarget; + } + }); + }, + onDocumentPointerUp: function (e) { + translateMSPointer(e, 'onContainerTouchEnd', 'touchend', function (e) { + delete touches[e.pointerId]; + }); + }, + + /** + * Add or remove the MS Pointer specific events + */ + batchMSEvents: function (fn) { + fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown); + fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove); + fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp); + } + }); + + // Disable default IE actions for pinch and such on chart element + wrap(Pointer.prototype, 'init', function (proceed, chart, options) { + proceed.call(this, chart, options); + if (this.hasZoom || this.followTouchMove) { + css(chart.container, { + '-ms-touch-action': NONE, + 'touch-action': NONE + }); + } + }); + + // Add IE specific touch events to chart + wrap(Pointer.prototype, 'setDOMEvents', function (proceed) { + proceed.apply(this); + if (this.hasZoom || this.followTouchMove) { + this.batchMSEvents(addEvent); + } + }); + // Destroy MS events also + wrap(Pointer.prototype, 'destroy', function (proceed) { + this.batchMSEvents(removeEvent); + proceed.call(this); + }); +} +/** + * The overview of the chart's series + */ +var Legend = Highcharts.Legend = function (chart, options) { + this.init(chart, options); +}; + +Legend.prototype = { + + /** + * Initialize the legend + */ + init: function (chart, options) { + + var legend = this, + itemStyle = options.itemStyle, + padding = pick(options.padding, 8), + itemMarginTop = options.itemMarginTop || 0; + + this.options = options; + + if (!options.enabled) { + return; + } + + legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype + legend.itemStyle = itemStyle; + legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle); + legend.itemMarginTop = itemMarginTop; + legend.padding = padding; + legend.initialItemX = padding; + legend.initialItemY = padding - 5; // 5 is the number of pixels above the text + legend.maxItemWidth = 0; + legend.chart = chart; + legend.itemHeight = 0; + legend.lastLineHeight = 0; + legend.symbolWidth = pick(options.symbolWidth, 16); + legend.pages = []; + + + // Render it + legend.render(); + + // move checkboxes + addEvent(legend.chart, 'endResize', function () { + legend.positionCheckboxes(); + }); + + }, + + /** + * Set the colors for the legend item + * @param {Object} item A Series or Point instance + * @param {Object} visible Dimmed or colored + */ + colorizeItem: function (item, visible) { + var legend = this, + options = legend.options, + legendItem = item.legendItem, + legendLine = item.legendLine, + legendSymbol = item.legendSymbol, + hiddenColor = legend.itemHiddenStyle.color, + textColor = visible ? options.itemStyle.color : hiddenColor, + symbolColor = visible ? (item.legendColor || item.color || '#CCC') : hiddenColor, + markerOptions = item.options && item.options.marker, + symbolAttr = { fill: symbolColor }, + key, + val; + + if (legendItem) { + legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE + } + if (legendLine) { + legendLine.attr({ stroke: symbolColor }); + } + + if (legendSymbol) { + + // Apply marker options + if (markerOptions && legendSymbol.isMarker) { // #585 + symbolAttr.stroke = symbolColor; + markerOptions = item.convertAttribs(markerOptions); + for (key in markerOptions) { + val = markerOptions[key]; + if (val !== UNDEFINED) { + symbolAttr[key] = val; + } + } + } + + legendSymbol.attr(symbolAttr); + } + }, + + /** + * Position the legend item + * @param {Object} item A Series or Point instance + */ + positionItem: function (item) { + var legend = this, + options = legend.options, + symbolPadding = options.symbolPadding, + ltr = !options.rtl, + legendItemPos = item._legendItemPos, + itemX = legendItemPos[0], + itemY = legendItemPos[1], + checkbox = item.checkbox; + + if (item.legendGroup) { + item.legendGroup.translate( + ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4, + itemY + ); + } + + if (checkbox) { + checkbox.x = itemX; + checkbox.y = itemY; + } + }, + + /** + * Destroy a single legend item + * @param {Object} item The series or point + */ + destroyItem: function (item) { + var checkbox = item.checkbox; + + // destroy SVG elements + each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) { + if (item[key]) { + item[key] = item[key].destroy(); + } + }); + + if (checkbox) { + discardElement(item.checkbox); + } + }, + + /** + * Destroys the legend. + */ + destroy: function () { + var legend = this, + legendGroup = legend.group, + box = legend.box; + + if (box) { + legend.box = box.destroy(); + } + + if (legendGroup) { + legend.group = legendGroup.destroy(); + } + }, + + /** + * Position the checkboxes after the width is determined + */ + positionCheckboxes: function (scrollOffset) { + var alignAttr = this.group.alignAttr, + translateY, + clipHeight = this.clipHeight || this.legendHeight; + + if (alignAttr) { + translateY = alignAttr.translateY; + each(this.allItems, function (item) { + var checkbox = item.checkbox, + top; + + if (checkbox) { + top = (translateY + checkbox.y + (scrollOffset || 0) + 3); + css(checkbox, { + left: (alignAttr.translateX + item.checkboxOffset + checkbox.x - 20) + PX, + top: top + PX, + display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE + }); + } + }); + } + }, + + /** + * Render the legend title on top of the legend + */ + renderTitle: function () { + var options = this.options, + padding = this.padding, + titleOptions = options.title, + titleHeight = 0, + bBox; + + if (titleOptions.text) { + if (!this.title) { + this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title') + .attr({ zIndex: 1 }) + .css(titleOptions.style) + .add(this.group); + } + bBox = this.title.getBBox(); + titleHeight = bBox.height; + this.offsetWidth = bBox.width; // #1717 + this.contentGroup.attr({ translateY: titleHeight }); + } + this.titleHeight = titleHeight; + }, + + /** + * Render a single specific legend item + * @param {Object} item A series or point + */ + renderItem: function (item) { + var legend = this, + chart = legend.chart, + renderer = chart.renderer, + options = legend.options, + horizontal = options.layout === 'horizontal', + symbolWidth = legend.symbolWidth, + symbolPadding = options.symbolPadding, + itemStyle = legend.itemStyle, + itemHiddenStyle = legend.itemHiddenStyle, + padding = legend.padding, + itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, // docs + ltr = !options.rtl, + itemHeight, + widthOption = options.width, + itemMarginBottom = options.itemMarginBottom || 0, + itemMarginTop = legend.itemMarginTop, + initialItemX = legend.initialItemX, + bBox, + itemWidth, + li = item.legendItem, + series = item.series && item.series.drawLegendSymbol ? item.series : item, + seriesOptions = series.options, + showCheckbox = legend.createCheckboxForItem && seriesOptions && seriesOptions.showCheckbox, + useHTML = options.useHTML; + + if (!li) { // generate it once, later move it + + // Generate the group box + // A group to hold the symbol and text. Text is to be appended in Legend class. + item.legendGroup = renderer.g('legend-item') + .attr({ zIndex: 1 }) + .add(legend.scrollGroup); + + // Draw the legend symbol inside the group box + series.drawLegendSymbol(legend, item); + + // Generate the list item text and add it to the group + item.legendItem = li = renderer.text( + options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item), + ltr ? symbolWidth + symbolPadding : -symbolPadding, + legend.baseline, + useHTML + ) + .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021) + .attr({ + align: ltr ? 'left' : 'right', + zIndex: 2 + }) + .add(item.legendGroup); + + if (legend.setItemEvents) { + legend.setItemEvents(item, li, useHTML, itemStyle, itemHiddenStyle); + } + + // Colorize the items + legend.colorizeItem(item, item.visible); + + // add the HTML checkbox on top + if (showCheckbox) { + legend.createCheckboxForItem(item); + } + } + + // calculate the positions for the next line + bBox = li.getBBox(); + + itemWidth = item.checkboxOffset = + options.itemWidth || + item.legendItemWidth || + symbolWidth + symbolPadding + bBox.width + itemDistance + (showCheckbox ? 20 : 0); + legend.itemHeight = itemHeight = mathRound(item.legendItemHeight || bBox.height); + + // if the item exceeds the width, start a new line + if (horizontal && legend.itemX - initialItemX + itemWidth > + (widthOption || (chart.chartWidth - 2 * padding - initialItemX - options.x))) { + legend.itemX = initialItemX; + legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom; + legend.lastLineHeight = 0; // reset for next line + } + + // If the item exceeds the height, start a new column + /*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) { + legend.itemY = legend.initialItemY; + legend.itemX += legend.maxItemWidth; + legend.maxItemWidth = 0; + }*/ + + // Set the edge positions + legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth); + legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom; + legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915 + + // cache the position of the newly generated or reordered items + item._legendItemPos = [legend.itemX, legend.itemY]; + + // advance + if (horizontal) { + legend.itemX += itemWidth; + + } else { + legend.itemY += itemMarginTop + itemHeight + itemMarginBottom; + legend.lastLineHeight = itemHeight; + } + + // the width of the widest item + legend.offsetWidth = widthOption || mathMax( + (horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding, + legend.offsetWidth + ); + }, + + /** + * Get all items, which is one item per series for normal series and one item per point + * for pie series. + */ + getAllItems: function () { + var allItems = []; + each(this.chart.series, function (series) { + var seriesOptions = series.options; + + // Handle showInLegend. If the series is linked to another series, defaults to false. + if (!pick(seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? UNDEFINED : false, true)) { + return; + } + + // use points or series for the legend item depending on legendType + allItems = allItems.concat( + series.legendItems || + (seriesOptions.legendType === 'point' ? + series.data : + series) + ); + }); + return allItems; + }, + + /** + * Render the legend. This method can be called both before and after + * chart.render. If called after, it will only rearrange items instead + * of creating new ones. + */ + render: function () { + var legend = this, + chart = legend.chart, + renderer = chart.renderer, + legendGroup = legend.group, + allItems, + display, + legendWidth, + legendHeight, + box = legend.box, + options = legend.options, + padding = legend.padding, + legendBorderWidth = options.borderWidth, + legendBackgroundColor = options.backgroundColor; + + legend.itemX = legend.initialItemX; + legend.itemY = legend.initialItemY; + legend.offsetWidth = 0; + legend.lastItemY = 0; + + if (!legendGroup) { + legend.group = legendGroup = renderer.g('legend') + .attr({ zIndex: 7 }) + .add(); + legend.contentGroup = renderer.g() + .attr({ zIndex: 1 }) // above background + .add(legendGroup); + legend.scrollGroup = renderer.g() + .add(legend.contentGroup); + } + + legend.renderTitle(); + + // add each series or point + allItems = legend.getAllItems(); + + // sort by legendIndex + stableSort(allItems, function (a, b) { + return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0); + }); + + // reversed legend + if (options.reversed) { + allItems.reverse(); + } + + legend.allItems = allItems; + legend.display = display = !!allItems.length; + + // render the items + each(allItems, function (item) { + legend.renderItem(item); + }); + + // Draw the border + legendWidth = options.width || legend.offsetWidth; + legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight; + + + legendHeight = legend.handleOverflow(legendHeight); + + if (legendBorderWidth || legendBackgroundColor) { + legendWidth += padding; + legendHeight += padding; + + if (!box) { + legend.box = box = renderer.rect( + 0, + 0, + legendWidth, + legendHeight, + options.borderRadius, + legendBorderWidth || 0 + ).attr({ + stroke: options.borderColor, + 'stroke-width': legendBorderWidth || 0, + fill: legendBackgroundColor || NONE + }) + .add(legendGroup) + .shadow(options.shadow); + box.isNew = true; + + } else if (legendWidth > 0 && legendHeight > 0) { + box[box.isNew ? 'attr' : 'animate']( + box.crisp({ width: legendWidth, height: legendHeight }) + ); + box.isNew = false; + } + + // hide the border if no items + box[display ? 'show' : 'hide'](); + } + + legend.legendWidth = legendWidth; + legend.legendHeight = legendHeight; + + // Now that the legend width and height are established, put the items in the + // final position + each(allItems, function (item) { + legend.positionItem(item); + }); + + // 1.x compatibility: positioning based on style + /*var props = ['left', 'right', 'top', 'bottom'], + prop, + i = 4; + while (i--) { + prop = props[i]; + if (options.style[prop] && options.style[prop] !== 'auto') { + options[i < 2 ? 'align' : 'verticalAlign'] = prop; + options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1); + } + }*/ + + if (display) { + legendGroup.align(extend({ + width: legendWidth, + height: legendHeight + }, options), true, 'spacingBox'); + } + + if (!chart.isResizing) { + this.positionCheckboxes(); + } + }, + + /** + * Set up the overflow handling by adding navigation with up and down arrows below the + * legend. + */ + handleOverflow: function (legendHeight) { + var legend = this, + chart = this.chart, + renderer = chart.renderer, + options = this.options, + optionsY = options.y, + alignTop = options.verticalAlign === 'top', + spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding, + maxHeight = options.maxHeight, + clipHeight, + clipRect = this.clipRect, + navOptions = options.navigation, + animation = pick(navOptions.animation, true), + arrowSize = navOptions.arrowSize || 12, + nav = this.nav, + pages = this.pages, + lastY, + allItems = this.allItems; + + // Adjust the height + if (options.layout === 'horizontal') { + spaceHeight /= 2; + } + if (maxHeight) { + spaceHeight = mathMin(spaceHeight, maxHeight); + } + + // Reset the legend height and adjust the clipping rectangle + pages.length = 0; + if (legendHeight > spaceHeight && !options.useHTML) { + + this.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight - this.padding; + this.currentPage = pick(this.currentPage, 1); + this.fullHeight = legendHeight; + + // Fill pages with Y positions so that the top of each a legend item defines + // the scroll top for each page (#2098) + each(allItems, function (item, i) { + var y = item._legendItemPos[1], + h = mathRound(item.legendItem.getBBox().height), + len = pages.length; + + if (!len || (y - pages[len - 1] > clipHeight && (lastY || y) !== pages[len - 1])) { + pages.push(lastY || y); + len++; + } + + if (i === allItems.length - 1 && y + h - pages[len - 1] > clipHeight) { + pages.push(y); + } + if (y !== lastY) { + lastY = y; + } + }); + + // Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787) + if (!clipRect) { + clipRect = legend.clipRect = renderer.clipRect(0, this.padding, 9999, 0); + legend.contentGroup.clip(clipRect); + } + clipRect.attr({ + height: clipHeight + }); + + // Add navigation elements + if (!nav) { + this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group); + this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize) + .on('click', function () { + legend.scroll(-1, animation); + }) + .add(nav); + this.pager = renderer.text('', 15, 10) + .css(navOptions.style) + .add(nav); + this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize) + .on('click', function () { + legend.scroll(1, animation); + }) + .add(nav); + } + + // Set initial position + legend.scroll(0); + + legendHeight = spaceHeight; + + } else if (nav) { + clipRect.attr({ + height: chart.chartHeight + }); + nav.hide(); + this.scrollGroup.attr({ + translateY: 1 + }); + this.clipHeight = 0; // #1379 + } + + return legendHeight; + }, + + /** + * Scroll the legend by a number of pages + * @param {Object} scrollBy + * @param {Object} animation + */ + scroll: function (scrollBy, animation) { + var pages = this.pages, + pageCount = pages.length, + currentPage = this.currentPage + scrollBy, + clipHeight = this.clipHeight, + navOptions = this.options.navigation, + activeColor = navOptions.activeColor, + inactiveColor = navOptions.inactiveColor, + pager = this.pager, + padding = this.padding, + scrollOffset; + + // When resizing while looking at the last page + if (currentPage > pageCount) { + currentPage = pageCount; + } + + if (currentPage > 0) { + + if (animation !== UNDEFINED) { + setAnimation(animation, this.chart); + } + + this.nav.attr({ + translateX: padding, + translateY: clipHeight + this.padding + 7 + this.titleHeight, + visibility: VISIBLE + }); + this.up.attr({ + fill: currentPage === 1 ? inactiveColor : activeColor + }) + .css({ + cursor: currentPage === 1 ? 'default' : 'pointer' + }); + pager.attr({ + text: currentPage + '/' + pageCount + }); + this.down.attr({ + x: 18 + this.pager.getBBox().width, // adjust to text width + fill: currentPage === pageCount ? inactiveColor : activeColor + }) + .css({ + cursor: currentPage === pageCount ? 'default' : 'pointer' + }); + + scrollOffset = -pages[currentPage - 1] + this.initialItemY; + + this.scrollGroup.animate({ + translateY: scrollOffset + }); + + this.currentPage = currentPage; + this.positionCheckboxes(scrollOffset); + } + + } + +}; + +/* + * LegendSymbolMixin + */ + +var LegendSymbolMixin = Highcharts.LegendSymbolMixin = { + + /** + * Get the series' symbol in the legend + * + * @param {Object} legend The legend object + * @param {Object} item The series (this) or point + */ + drawRectangle: function (legend, item) { + var symbolHeight = legend.options.symbolHeight || 12; + + item.legendSymbol = this.chart.renderer.rect( + 0, + legend.baseline - 5 - (symbolHeight / 2), + legend.symbolWidth, + symbolHeight, + legend.options.symbolRadius || 0 + ).attr({ + zIndex: 3 + }).add(item.legendGroup); + + }, + + /** + * Get the series' symbol in the legend. This method should be overridable to create custom + * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols. + * + * @param {Object} legend The legend object + */ + drawLineMarker: function (legend) { + + var options = this.options, + markerOptions = options.marker, + radius, + legendOptions = legend.options, + legendSymbol, + symbolWidth = legend.symbolWidth, + renderer = this.chart.renderer, + legendItemGroup = this.legendGroup, + verticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3), + attr; + + // Draw the line + if (options.lineWidth) { + attr = { + 'stroke-width': options.lineWidth + }; + if (options.dashStyle) { + attr.dashstyle = options.dashStyle; + } + this.legendLine = renderer.path([ + M, + 0, + verticalCenter, + L, + symbolWidth, + verticalCenter + ]) + .attr(attr) + .add(legendItemGroup); + } + + // Draw the marker + if (markerOptions && markerOptions.enabled !== false) { + radius = markerOptions.radius; + this.legendSymbol = legendSymbol = renderer.symbol( + this.symbol, + (symbolWidth / 2) - radius, + verticalCenter - radius, + 2 * radius, + 2 * radius + ) + .add(legendItemGroup); + legendSymbol.isMarker = true; + } + } +}; + +// Workaround for #2030, horizontal legend items not displaying in IE11 Preview, +// and for #2580, a similar drawing flaw in Firefox 26. +// TODO: Explore if there's a general cause for this. The problem may be related +// to nested group elements, as the legend item texts are within 4 group elements. +if (/Trident\/7\.0/.test(userAgent) || isFirefox) { + wrap(Legend.prototype, 'positionItem', function (proceed, item) { + var legend = this, + runPositionItem = function () { // If chart destroyed in sync, this is undefined (#2030) + if (item._legendItemPos) { + proceed.call(legend, item); + } + }; + + // Do it now, for export and to get checkbox placement + runPositionItem(); + + // Do it after to work around the core issue + setTimeout(runPositionItem); + }); +} +/** + * The chart class + * @param {Object} options + * @param {Function} callback Function to run when the chart has loaded + */ +function Chart() { + this.init.apply(this, arguments); +} + +Chart.prototype = { + + /** + * Initialize the chart + */ + init: function (userOptions, callback) { + + // Handle regular options + var options, + seriesOptions = userOptions.series; // skip merging data points to increase performance + + userOptions.series = null; + options = merge(defaultOptions, userOptions); // do the merge + options.series = userOptions.series = seriesOptions; // set back the series data + this.userOptions = userOptions; + + var optionsChart = options.chart; + + // Create margin & spacing array + this.margin = this.splashArray('margin', optionsChart); + this.spacing = this.splashArray('spacing', optionsChart); + + var chartEvents = optionsChart.events; + + //this.runChartClick = chartEvents && !!chartEvents.click; + this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom + + this.callback = callback; + this.isResizing = 0; + this.options = options; + //chartTitleOptions = UNDEFINED; + //chartSubtitleOptions = UNDEFINED; + + this.axes = []; + this.series = []; + this.hasCartesianSeries = optionsChart.showAxes; + //this.axisOffset = UNDEFINED; + //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes + //this.inverted = UNDEFINED; + //this.loadingShown = UNDEFINED; + //this.container = UNDEFINED; + //this.chartWidth = UNDEFINED; + //this.chartHeight = UNDEFINED; + //this.marginRight = UNDEFINED; + //this.marginBottom = UNDEFINED; + //this.containerWidth = UNDEFINED; + //this.containerHeight = UNDEFINED; + //this.oldChartWidth = UNDEFINED; + //this.oldChartHeight = UNDEFINED; + + //this.renderTo = UNDEFINED; + //this.renderToClone = UNDEFINED; + + //this.spacingBox = UNDEFINED + + //this.legend = UNDEFINED; + + // Elements + //this.chartBackground = UNDEFINED; + //this.plotBackground = UNDEFINED; + //this.plotBGImage = UNDEFINED; + //this.plotBorder = UNDEFINED; + //this.loadingDiv = UNDEFINED; + //this.loadingSpan = UNDEFINED; + + var chart = this, + eventType; + + // Add the chart to the global lookup + chart.index = charts.length; + charts.push(chart); + chartCount++; + + // Set up auto resize + if (optionsChart.reflow !== false) { + addEvent(chart, 'load', function () { + chart.initReflow(); + }); + } + + // Chart event handlers + if (chartEvents) { + for (eventType in chartEvents) { + addEvent(chart, eventType, chartEvents[eventType]); + } + } + + chart.xAxis = []; + chart.yAxis = []; + + // Expose methods and variables + chart.animation = useCanVG ? false : pick(optionsChart.animation, true); + chart.pointCount = 0; + chart.counters = new ChartCounters(); + + chart.firstRender(); + }, + + /** + * Initialize an individual series, called internally before render time + */ + initSeries: function (options) { + var chart = this, + optionsChart = chart.options.chart, + type = options.type || optionsChart.type || optionsChart.defaultSeriesType, + series, + constr = seriesTypes[type]; + + // No such series type + if (!constr) { + error(17, true); + } + + series = new constr(); + series.init(this, options); + return series; + }, + + /** + * Check whether a given point is within the plot area + * + * @param {Number} plotX Pixel x relative to the plot area + * @param {Number} plotY Pixel y relative to the plot area + * @param {Boolean} inverted Whether the chart is inverted + */ + isInsidePlot: function (plotX, plotY, inverted) { + var x = inverted ? plotY : plotX, + y = inverted ? plotX : plotY; + + return x >= 0 && + x <= this.plotWidth && + y >= 0 && + y <= this.plotHeight; + }, + + /** + * Adjust all axes tick amounts + */ + adjustTickAmounts: function () { + if (this.options.chart.alignTicks !== false) { + each(this.axes, function (axis) { + axis.adjustTickAmount(); + }); + } + this.maxTicks = null; + }, + + /** + * Redraw legend, axes or series based on updated data + * + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + */ + redraw: function (animation) { + var chart = this, + axes = chart.axes, + series = chart.series, + pointer = chart.pointer, + legend = chart.legend, + redrawLegend = chart.isDirtyLegend, + hasStackedSeries, + hasDirtyStacks, + isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed? + seriesLength = series.length, + i = seriesLength, + serie, + renderer = chart.renderer, + isHiddenChart = renderer.isHidden(), + afterRedraw = []; + + setAnimation(animation, chart); + + if (isHiddenChart) { + chart.cloneRenderTo(); + } + + // Adjust title layout (reflow multiline text) + chart.layOutTitles(); + + // link stacked series + while (i--) { + serie = series[i]; + + if (serie.options.stacking) { + hasStackedSeries = true; + + if (serie.isDirty) { + hasDirtyStacks = true; + break; + } + } + } + if (hasDirtyStacks) { // mark others as dirty + i = seriesLength; + while (i--) { + serie = series[i]; + if (serie.options.stacking) { + serie.isDirty = true; + } + } + } + + // handle updated data in the series + each(series, function (serie) { + if (serie.isDirty) { // prepare the data so axis can read it + if (serie.options.legendType === 'point') { + redrawLegend = true; + } + } + }); + + // handle added or removed series + if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed + // draw legend graphics + legend.render(); + + chart.isDirtyLegend = false; + } + + // reset stacks + if (hasStackedSeries) { + chart.getStacks(); + } + + + if (chart.hasCartesianSeries) { + if (!chart.isResizing) { + + // reset maxTicks + chart.maxTicks = null; + + // set axes scales + each(axes, function (axis) { + axis.setScale(); + }); + } + + chart.adjustTickAmounts(); + chart.getMargins(); + + // If one axis is dirty, all axes must be redrawn (#792, #2169) + each(axes, function (axis) { + if (axis.isDirty) { + isDirtyBox = true; + } + }); + + // redraw axes + each(axes, function (axis) { + + // Fire 'afterSetExtremes' only if extremes are set + if (axis.isDirtyExtremes) { // #821 + axis.isDirtyExtremes = false; + afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119) + fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751 + delete axis.eventArgs; + }); + } + + if (isDirtyBox || hasStackedSeries) { + axis.redraw(); + } + }); + + + } + // the plot areas size has changed + if (isDirtyBox) { + chart.drawChartBox(); + } + + + // redraw affected series + each(series, function (serie) { + if (serie.isDirty && serie.visible && + (!serie.isCartesian || serie.xAxis)) { // issue #153 + serie.redraw(); + } + }); + + // move tooltip or reset + if (pointer) { + pointer.reset(true); + } + + // redraw if canvas + renderer.draw(); + + // fire the event + fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw + + if (isHiddenChart) { + chart.cloneRenderTo(true); + } + + // Fire callbacks that are put on hold until after the redraw + each(afterRedraw, function (callback) { + callback.call(); + }); + }, + + /** + * Get an axis, series or point object by id. + * @param id {String} The id as given in the configuration options + */ + get: function (id) { + var chart = this, + axes = chart.axes, + series = chart.series; + + var i, + j, + points; + + // search axes + for (i = 0; i < axes.length; i++) { + if (axes[i].options.id === id) { + return axes[i]; + } + } + + // search series + for (i = 0; i < series.length; i++) { + if (series[i].options.id === id) { + return series[i]; + } + } + + // search points + for (i = 0; i < series.length; i++) { + points = series[i].points || []; + for (j = 0; j < points.length; j++) { + if (points[j].id === id) { + return points[j]; + } + } + } + return null; + }, + + /** + * Create the Axis instances based on the config options + */ + getAxes: function () { + var chart = this, + options = this.options, + xAxisOptions = options.xAxis = splat(options.xAxis || {}), + yAxisOptions = options.yAxis = splat(options.yAxis || {}), + optionsArray, + axis; + + // make sure the options are arrays and add some members + each(xAxisOptions, function (axis, i) { + axis.index = i; + axis.isX = true; + }); + + each(yAxisOptions, function (axis, i) { + axis.index = i; + }); + + // concatenate all axis options into one array + optionsArray = xAxisOptions.concat(yAxisOptions); + + each(optionsArray, function (axisOptions) { + axis = new Axis(chart, axisOptions); + }); + + chart.adjustTickAmounts(); + }, + + + /** + * Get the currently selected points from all series + */ + getSelectedPoints: function () { + var points = []; + each(this.series, function (serie) { + points = points.concat(grep(serie.points || [], function (point) { + return point.selected; + })); + }); + return points; + }, + + /** + * Get the currently selected series + */ + getSelectedSeries: function () { + return grep(this.series, function (serie) { + return serie.selected; + }); + }, + + /** + * Generate stacks for each series and calculate stacks total values + */ + getStacks: function () { + var chart = this; + + // reset stacks for each yAxis + each(chart.yAxis, function (axis) { + if (axis.stacks && axis.hasVisibleSeries) { + axis.oldStacks = axis.stacks; + } + }); + + each(chart.series, function (series) { + if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) { + series.stackKey = series.type + pick(series.options.stack, ''); + } + }); + }, + + /** + * Show the title and subtitle of the chart + * + * @param titleOptions {Object} New title options + * @param subtitleOptions {Object} New subtitle options + * + */ + setTitle: function (titleOptions, subtitleOptions, redraw) { + var chart = this, + options = chart.options, + chartTitleOptions, + chartSubtitleOptions; + + chartTitleOptions = options.title = merge(options.title, titleOptions); + chartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions); + + // add title and subtitle + each([ + ['title', titleOptions, chartTitleOptions], + ['subtitle', subtitleOptions, chartSubtitleOptions] + ], function (arr) { + var name = arr[0], + title = chart[name], + titleOptions = arr[1], + chartTitleOptions = arr[2]; + + if (title && titleOptions) { + chart[name] = title = title.destroy(); // remove old + } + + if (chartTitleOptions && chartTitleOptions.text && !title) { + chart[name] = chart.renderer.text( + chartTitleOptions.text, + 0, + 0, + chartTitleOptions.useHTML + ) + .attr({ + align: chartTitleOptions.align, + 'class': PREFIX + name, + zIndex: chartTitleOptions.zIndex || 4 + }) + .css(chartTitleOptions.style) + .add(); + } + }); + chart.layOutTitles(redraw); + }, + + /** + * Lay out the chart titles and cache the full offset height for use in getMargins + */ + layOutTitles: function (redraw) { + var titleOffset = 0, + title = this.title, + subtitle = this.subtitle, + options = this.options, + titleOptions = options.title, + subtitleOptions = options.subtitle, + requiresDirtyBox, + autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button + + if (title) { + title + .css({ width: (titleOptions.width || autoWidth) + PX }) + .align(extend({ y: 15 }, titleOptions), false, 'spacingBox'); + + if (!titleOptions.floating && !titleOptions.verticalAlign) { + titleOffset = title.getBBox().height; + } + } + if (subtitle) { + subtitle + .css({ width: (subtitleOptions.width || autoWidth) + PX }) + .align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox'); + + if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) { + titleOffset = mathCeil(titleOffset + subtitle.getBBox().height); + } + } + + requiresDirtyBox = this.titleOffset !== titleOffset; + this.titleOffset = titleOffset; // used in getMargins + + if (!this.isDirtyBox && requiresDirtyBox) { + this.isDirtyBox = requiresDirtyBox; + // Redraw if necessary (#2719, #2744) + if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) { + this.redraw(); + } + } + }, + + /** + * Get chart width and height according to options and container size + */ + getChartSize: function () { + var chart = this, + optionsChart = chart.options.chart, + widthOption = optionsChart.width, + heightOption = optionsChart.height, + renderTo = chart.renderToClone || chart.renderTo; + + // get inner width and height from jQuery (#824) + if (!defined(widthOption)) { + chart.containerWidth = adapterRun(renderTo, 'width'); + } + if (!defined(heightOption)) { + chart.containerHeight = adapterRun(renderTo, 'height'); + } + + chart.chartWidth = mathMax(0, widthOption || chart.containerWidth || 600); // #1393, 1460 + chart.chartHeight = mathMax(0, pick(heightOption, + // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7: + chart.containerHeight > 19 ? chart.containerHeight : 400)); + }, + + /** + * Create a clone of the chart's renderTo div and place it outside the viewport to allow + * size computation on chart.render and chart.redraw + */ + cloneRenderTo: function (revert) { + var clone = this.renderToClone, + container = this.container; + + // Destroy the clone and bring the container back to the real renderTo div + if (revert) { + if (clone) { + this.renderTo.appendChild(container); + discardElement(clone); + delete this.renderToClone; + } + + // Set up the clone + } else { + if (container && container.parentNode === this.renderTo) { + this.renderTo.removeChild(container); // do not clone this + } + this.renderToClone = clone = this.renderTo.cloneNode(0); + css(clone, { + position: ABSOLUTE, + top: '-9999px', + display: 'block' // #833 + }); + if (clone.style.setProperty) { // #2631 + clone.style.setProperty('display', 'block', 'important'); + } + doc.body.appendChild(clone); + if (container) { + clone.appendChild(container); + } + } + }, + + /** + * Get the containing element, determine the size and create the inner container + * div to hold the chart + */ + getContainer: function () { + var chart = this, + container, + optionsChart = chart.options.chart, + chartWidth, + chartHeight, + renderTo, + indexAttrName = 'data-highcharts-chart', + oldChartIndex, + containerId; + + chart.renderTo = renderTo = optionsChart.renderTo; + containerId = PREFIX + idCounter++; + + if (isString(renderTo)) { + chart.renderTo = renderTo = doc.getElementById(renderTo); + } + + // Display an error if the renderTo is wrong + if (!renderTo) { + error(13, true); + } + + // If the container already holds a chart, destroy it. The check for hasRendered is there + // because web pages that are saved to disk from the browser, will preserve the data-highcharts-chart + // attribute and the SVG contents, but not an interactive chart. So in this case, + // charts[oldChartIndex] will point to the wrong chart if any (#2609). + oldChartIndex = pInt(attr(renderTo, indexAttrName)); + if (!isNaN(oldChartIndex) && charts[oldChartIndex] && charts[oldChartIndex].hasRendered) { + charts[oldChartIndex].destroy(); + } + + // Make a reference to the chart from the div + attr(renderTo, indexAttrName, chart.index); + + // remove previous chart + renderTo.innerHTML = ''; + + // If the container doesn't have an offsetWidth, it has or is a child of a node + // that has display:none. We need to temporarily move it out to a visible + // state to determine the size, else the legend and tooltips won't render + // properly. The allowClone option is used in sparklines as a micro optimization, + // saving about 1-2 ms each chart. + if (!optionsChart.skipClone && !renderTo.offsetWidth) { + chart.cloneRenderTo(); + } + + // get the width and height + chart.getChartSize(); + chartWidth = chart.chartWidth; + chartHeight = chart.chartHeight; + + // create the inner container + chart.container = container = createElement(DIV, { + className: PREFIX + 'container' + + (optionsChart.className ? ' ' + optionsChart.className : ''), + id: containerId + }, extend({ + position: RELATIVE, + overflow: HIDDEN, // needed for context menu (avoid scrollbars) and + // content overflow in IE + width: chartWidth + PX, + height: chartHeight + PX, + textAlign: 'left', + lineHeight: 'normal', // #427 + zIndex: 0, // #1072 + '-webkit-tap-highlight-color': 'rgba(0,0,0,0)' + }, optionsChart.style), + chart.renderToClone || renderTo + ); + + // cache the cursor (#1650) + chart._cursor = container.style.cursor; + + // Initialize the renderer + chart.renderer = + optionsChart.forExport ? // force SVG, used for SVG export + new SVGRenderer(container, chartWidth, chartHeight, optionsChart.style, true) : + new Renderer(container, chartWidth, chartHeight, optionsChart.style); + + if (useCanVG) { + // If we need canvg library, extend and configure the renderer + // to get the tracker for translating mouse events + chart.renderer.create(chart, container, chartWidth, chartHeight); + } + }, + + /** + * Calculate margins by rendering axis labels in a preliminary position. Title, + * subtitle and legend have already been rendered at this stage, but will be + * moved into their final positions + */ + getMargins: function () { + var chart = this, + spacing = chart.spacing, + axisOffset, + legend = chart.legend, + margin = chart.margin, + legendOptions = chart.options.legend, + legendMargin = pick(legendOptions.margin, 20), + legendX = legendOptions.x, + legendY = legendOptions.y, + align = legendOptions.align, + verticalAlign = legendOptions.verticalAlign, + titleOffset = chart.titleOffset; + + chart.resetMargins(); + axisOffset = chart.axisOffset; + + // Adjust for title and subtitle + if (titleOffset && !defined(margin[0])) { + chart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacing[0]); + } + + // Adjust for legend + if (legend.display && !legendOptions.floating) { + if (align === 'right') { // horizontal alignment handled first + if (!defined(margin[1])) { + chart.marginRight = mathMax( + chart.marginRight, + legend.legendWidth - legendX + legendMargin + spacing[1] + ); + } + } else if (align === 'left') { + if (!defined(margin[3])) { + chart.plotLeft = mathMax( + chart.plotLeft, + legend.legendWidth + legendX + legendMargin + spacing[3] + ); + } + + } else if (verticalAlign === 'top') { + if (!defined(margin[0])) { + chart.plotTop = mathMax( + chart.plotTop, + legend.legendHeight + legendY + legendMargin + spacing[0] + ); + } + + } else if (verticalAlign === 'bottom') { + if (!defined(margin[2])) { + chart.marginBottom = mathMax( + chart.marginBottom, + legend.legendHeight - legendY + legendMargin + spacing[2] + ); + } + } + } + + // adjust for scroller + if (chart.extraBottomMargin) { + chart.marginBottom += chart.extraBottomMargin; + } + if (chart.extraTopMargin) { + chart.plotTop += chart.extraTopMargin; + } + + // pre-render axes to get labels offset width + if (chart.hasCartesianSeries) { + each(chart.axes, function (axis) { + axis.getOffset(); + }); + } + + if (!defined(margin[3])) { + chart.plotLeft += axisOffset[3]; + } + if (!defined(margin[0])) { + chart.plotTop += axisOffset[0]; + } + if (!defined(margin[2])) { + chart.marginBottom += axisOffset[2]; + } + if (!defined(margin[1])) { + chart.marginRight += axisOffset[1]; + } + + chart.setChartSize(); + + }, + + /** + * Resize the chart to its container if size is not explicitly set + */ + reflow: function (e) { + var chart = this, + optionsChart = chart.options.chart, + renderTo = chart.renderTo, + width = optionsChart.width || adapterRun(renderTo, 'width'), + height = optionsChart.height || adapterRun(renderTo, 'height'), + target = e ? e.target : win, // #805 - MooTools doesn't supply e + doReflow = function () { + if (chart.container) { // It may have been destroyed in the meantime (#1257) + chart.setSize(width, height, false); + chart.hasUserSize = null; + } + }; + + // Width and height checks for display:none. Target is doc in IE8 and Opera, + // win in Firefox, Chrome and IE9. + if (!chart.hasUserSize && width && height && (target === win || target === doc)) { + if (width !== chart.containerWidth || height !== chart.containerHeight) { + clearTimeout(chart.reflowTimeout); + if (e) { // Called from window.resize + chart.reflowTimeout = setTimeout(doReflow, 100); + } else { // Called directly (#2224) + doReflow(); + } + } + chart.containerWidth = width; + chart.containerHeight = height; + } + }, + + /** + * Add the event handlers necessary for auto resizing + */ + initReflow: function () { + var chart = this, + reflow = function (e) { + chart.reflow(e); + }; + + + addEvent(win, 'resize', reflow); + addEvent(chart, 'destroy', function () { + removeEvent(win, 'resize', reflow); + }); + }, + + /** + * Resize the chart to a given width and height + * @param {Number} width + * @param {Number} height + * @param {Object|Boolean} animation + */ + setSize: function (width, height, animation) { + var chart = this, + chartWidth, + chartHeight, + fireEndResize; + + // Handle the isResizing counter + chart.isResizing += 1; + fireEndResize = function () { + if (chart) { + fireEvent(chart, 'endResize', null, function () { + chart.isResizing -= 1; + }); + } + }; + + // set the animation for the current process + setAnimation(animation, chart); + + chart.oldChartHeight = chart.chartHeight; + chart.oldChartWidth = chart.chartWidth; + if (defined(width)) { + chart.chartWidth = chartWidth = mathMax(0, mathRound(width)); + chart.hasUserSize = !!chartWidth; + } + if (defined(height)) { + chart.chartHeight = chartHeight = mathMax(0, mathRound(height)); + } + + // Resize the container with the global animation applied if enabled (#2503) + (globalAnimation ? animate : css)(chart.container, { + width: chartWidth + PX, + height: chartHeight + PX + }, globalAnimation); + + chart.setChartSize(true); + chart.renderer.setSize(chartWidth, chartHeight, animation); + + // handle axes + chart.maxTicks = null; + each(chart.axes, function (axis) { + axis.isDirty = true; + axis.setScale(); + }); + + // make sure non-cartesian series are also handled + each(chart.series, function (serie) { + serie.isDirty = true; + }); + + chart.isDirtyLegend = true; // force legend redraw + chart.isDirtyBox = true; // force redraw of plot and chart border + + chart.layOutTitles(); // #2857 + chart.getMargins(); + + chart.redraw(animation); + + + chart.oldChartHeight = null; + fireEvent(chart, 'resize'); + + // fire endResize and set isResizing back + // If animation is disabled, fire without delay + if (globalAnimation === false) { + fireEndResize(); + } else { // else set a timeout with the animation duration + setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500); + } + }, + + /** + * Set the public chart properties. This is done before and after the pre-render + * to determine margin sizes + */ + setChartSize: function (skipAxes) { + var chart = this, + inverted = chart.inverted, + renderer = chart.renderer, + chartWidth = chart.chartWidth, + chartHeight = chart.chartHeight, + optionsChart = chart.options.chart, + spacing = chart.spacing, + clipOffset = chart.clipOffset, + clipX, + clipY, + plotLeft, + plotTop, + plotWidth, + plotHeight, + plotBorderWidth; + + chart.plotLeft = plotLeft = mathRound(chart.plotLeft); + chart.plotTop = plotTop = mathRound(chart.plotTop); + chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight)); + chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom)); + + chart.plotSizeX = inverted ? plotHeight : plotWidth; + chart.plotSizeY = inverted ? plotWidth : plotHeight; + + chart.plotBorderWidth = optionsChart.plotBorderWidth || 0; + + // Set boxes used for alignment + chart.spacingBox = renderer.spacingBox = { + x: spacing[3], + y: spacing[0], + width: chartWidth - spacing[3] - spacing[1], + height: chartHeight - spacing[0] - spacing[2] + }; + chart.plotBox = renderer.plotBox = { + x: plotLeft, + y: plotTop, + width: plotWidth, + height: plotHeight + }; + + plotBorderWidth = 2 * mathFloor(chart.plotBorderWidth / 2); + clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2); + clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2); + chart.clipBox = { + x: clipX, + y: clipY, + width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX), + height: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY) + }; + + if (!skipAxes) { + each(chart.axes, function (axis) { + axis.setAxisSize(); + axis.setAxisTranslation(); + }); + } + }, + + /** + * Initial margins before auto size margins are applied + */ + resetMargins: function () { + var chart = this, + spacing = chart.spacing, + margin = chart.margin; + + chart.plotTop = pick(margin[0], spacing[0]); + chart.marginRight = pick(margin[1], spacing[1]); + chart.marginBottom = pick(margin[2], spacing[2]); + chart.plotLeft = pick(margin[3], spacing[3]); + chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left + chart.clipOffset = [0, 0, 0, 0]; + }, + + /** + * Draw the borders and backgrounds for chart and plot area + */ + drawChartBox: function () { + var chart = this, + optionsChart = chart.options.chart, + renderer = chart.renderer, + chartWidth = chart.chartWidth, + chartHeight = chart.chartHeight, + chartBackground = chart.chartBackground, + plotBackground = chart.plotBackground, + plotBorder = chart.plotBorder, + plotBGImage = chart.plotBGImage, + chartBorderWidth = optionsChart.borderWidth || 0, + chartBackgroundColor = optionsChart.backgroundColor, + plotBackgroundColor = optionsChart.plotBackgroundColor, + plotBackgroundImage = optionsChart.plotBackgroundImage, + plotBorderWidth = optionsChart.plotBorderWidth || 0, + mgn, + bgAttr, + plotLeft = chart.plotLeft, + plotTop = chart.plotTop, + plotWidth = chart.plotWidth, + plotHeight = chart.plotHeight, + plotBox = chart.plotBox, + clipRect = chart.clipRect, + clipBox = chart.clipBox; + + // Chart area + mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0); + + if (chartBorderWidth || chartBackgroundColor) { + if (!chartBackground) { + + bgAttr = { + fill: chartBackgroundColor || NONE + }; + if (chartBorderWidth) { // #980 + bgAttr.stroke = optionsChart.borderColor; + bgAttr['stroke-width'] = chartBorderWidth; + } + chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn, + optionsChart.borderRadius, chartBorderWidth) + .attr(bgAttr) + .addClass(PREFIX + 'background') + .add() + .shadow(optionsChart.shadow); + + } else { // resize + chartBackground.animate( + chartBackground.crisp({ width: chartWidth - mgn, height: chartHeight - mgn }) + ); + } + } + + + // Plot background + if (plotBackgroundColor) { + if (!plotBackground) { + chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0) + .attr({ + fill: plotBackgroundColor + }) + .add() + .shadow(optionsChart.plotShadow); + } else { + plotBackground.animate(plotBox); + } + } + if (plotBackgroundImage) { + if (!plotBGImage) { + chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight) + .add(); + } else { + plotBGImage.animate(plotBox); + } + } + + // Plot clip + if (!clipRect) { + chart.clipRect = renderer.clipRect(clipBox); + } else { + clipRect.animate({ + width: clipBox.width, + height: clipBox.height + }); + } + + // Plot area border + if (plotBorderWidth) { + if (!plotBorder) { + chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, -plotBorderWidth) + .attr({ + stroke: optionsChart.plotBorderColor, + 'stroke-width': plotBorderWidth, + fill: NONE, + zIndex: 1 + }) + .add(); + } else { + plotBorder.animate( + plotBorder.crisp({ x: plotLeft, y: plotTop, width: plotWidth, height: plotHeight }) + ); + } + } + + // reset + chart.isDirtyBox = false; + }, + + /** + * Detect whether a certain chart property is needed based on inspecting its options + * and series. This mainly applies to the chart.invert property, and in extensions to + * the chart.angular and chart.polar properties. + */ + propFromSeries: function () { + var chart = this, + optionsChart = chart.options.chart, + klass, + seriesOptions = chart.options.series, + i, + value; + + + each(['inverted', 'angular', 'polar'], function (key) { + + // The default series type's class + klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType]; + + // Get the value from available chart-wide properties + value = ( + chart[key] || // 1. it is set before + optionsChart[key] || // 2. it is set in the options + (klass && klass.prototype[key]) // 3. it's default series class requires it + ); + + // 4. Check if any the chart's series require it + i = seriesOptions && seriesOptions.length; + while (!value && i--) { + klass = seriesTypes[seriesOptions[i].type]; + if (klass && klass.prototype[key]) { + value = true; + } + } + + // Set the chart property + chart[key] = value; + }); + + }, + + /** + * Link two or more series together. This is done initially from Chart.render, + * and after Chart.addSeries and Series.remove. + */ + linkSeries: function () { + var chart = this, + chartSeries = chart.series; + + // Reset links + each(chartSeries, function (series) { + series.linkedSeries.length = 0; + }); + + // Apply new links + each(chartSeries, function (series) { + var linkedTo = series.options.linkedTo; + if (isString(linkedTo)) { + if (linkedTo === ':previous') { + linkedTo = chart.series[series.index - 1]; + } else { + linkedTo = chart.get(linkedTo); + } + if (linkedTo) { + linkedTo.linkedSeries.push(series); + series.linkedParent = linkedTo; + } + } + }); + }, + + /** + * Render series for the chart + */ + renderSeries: function () { + each(this.series, function (serie) { + serie.translate(); + if (serie.setTooltipPoints) { + serie.setTooltipPoints(); + } + serie.render(); + }); + }, + + /** + * Render all graphics for the chart + */ + render: function () { + var chart = this, + axes = chart.axes, + renderer = chart.renderer, + options = chart.options; + + var labels = options.labels, + credits = options.credits, + creditsHref; + + // Title + chart.setTitle(); + + + // Legend + chart.legend = new Legend(chart, options.legend); + + chart.getStacks(); // render stacks + + // Get margins by pre-rendering axes + // set axes scales + each(axes, function (axis) { + axis.setScale(); + }); + + chart.getMargins(); + + chart.maxTicks = null; // reset for second pass + each(axes, function (axis) { + axis.setTickPositions(true); // update to reflect the new margins + axis.setMaxTicks(); + }); + chart.adjustTickAmounts(); + chart.getMargins(); // second pass to check for new labels + + + // Draw the borders and backgrounds + chart.drawChartBox(); + + + // Axes + if (chart.hasCartesianSeries) { + each(axes, function (axis) { + axis.render(); + }); + } + + // The series + if (!chart.seriesGroup) { + chart.seriesGroup = renderer.g('series-group') + .attr({ zIndex: 3 }) + .add(); + } + chart.renderSeries(); + + // Labels + if (labels.items) { + each(labels.items, function (label) { + var style = extend(labels.style, label.style), + x = pInt(style.left) + chart.plotLeft, + y = pInt(style.top) + chart.plotTop + 12; + + // delete to prevent rewriting in IE + delete style.left; + delete style.top; + + renderer.text( + label.html, + x, + y + ) + .attr({ zIndex: 2 }) + .css(style) + .add(); + + }); + } + + // Credits + if (credits.enabled && !chart.credits) { + creditsHref = credits.href; + chart.credits = renderer.text( + credits.text, + 0, + 0 + ) + .on('click', function () { + if (creditsHref) { + location.href = creditsHref; + } + }) + .attr({ + align: credits.position.align, + zIndex: 8 + }) + .css(credits.style) + .add() + .align(credits.position); + } + + // Set flag + chart.hasRendered = true; + + }, + + /** + * Clean up memory usage + */ + destroy: function () { + var chart = this, + axes = chart.axes, + series = chart.series, + container = chart.container, + i, + parentNode = container && container.parentNode; + + // fire the chart.destoy event + fireEvent(chart, 'destroy'); + + // Delete the chart from charts lookup array + charts[chart.index] = UNDEFINED; + chartCount--; + chart.renderTo.removeAttribute('data-highcharts-chart'); + + // remove events + removeEvent(chart); + + // ==== Destroy collections: + // Destroy axes + i = axes.length; + while (i--) { + axes[i] = axes[i].destroy(); + } + + // Destroy each series + i = series.length; + while (i--) { + series[i] = series[i].destroy(); + } + + // ==== Destroy chart properties: + each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', + 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller', + 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) { + var prop = chart[name]; + + if (prop && prop.destroy) { + chart[name] = prop.destroy(); + } + }); + + // remove container and all SVG + if (container) { // can break in IE when destroyed before finished loading + container.innerHTML = ''; + removeEvent(container); + if (parentNode) { + discardElement(container); + } + + } + + // clean it all up + for (i in chart) { + delete chart[i]; + } + + }, + + + /** + * VML namespaces can't be added until after complete. Listening + * for Perini's doScroll hack is not enough. + */ + isReadyToRender: function () { + var chart = this; + + // Note: in spite of JSLint's complaints, win == win.top is required + /*jslint eqeq: true*/ + if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) { + /*jslint eqeq: false*/ + if (useCanVG) { + // Delay rendering until canvg library is downloaded and ready + CanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL); + } else { + doc.attachEvent('onreadystatechange', function () { + doc.detachEvent('onreadystatechange', chart.firstRender); + if (doc.readyState === 'complete') { + chart.firstRender(); + } + }); + } + return false; + } + return true; + }, + + /** + * Prepare for first rendering after all data are loaded + */ + firstRender: function () { + var chart = this, + options = chart.options, + callback = chart.callback; + + // Check whether the chart is ready to render + if (!chart.isReadyToRender()) { + return; + } + + // Create the container + chart.getContainer(); + + // Run an early event after the container and renderer are established + fireEvent(chart, 'init'); + + + chart.resetMargins(); + chart.setChartSize(); + + // Set the common chart properties (mainly invert) from the given series + chart.propFromSeries(); + + // get axes + chart.getAxes(); + + // Initialize the series + each(options.series || [], function (serieOptions) { + chart.initSeries(serieOptions); + }); + + chart.linkSeries(); + + // Run an event after axes and series are initialized, but before render. At this stage, + // the series data is indexed and cached in the xData and yData arrays, so we can access + // those before rendering. Used in Highstock. + fireEvent(chart, 'beforeRender'); + + // depends on inverted and on margins being set + if (Highcharts.Pointer) { + chart.pointer = new Pointer(chart, options); + } + + chart.render(); + + // add canvas + chart.renderer.draw(); + // run callbacks + if (callback) { + callback.apply(chart, [chart]); + } + each(chart.callbacks, function (fn) { + fn.apply(chart, [chart]); + }); + + + // If the chart was rendered outside the top container, put it back in + chart.cloneRenderTo(true); + + fireEvent(chart, 'load'); + + }, + + /** + * Creates arrays for spacing and margin from given options. + */ + splashArray: function (target, options) { + var oVar = options[target], + tArray = isObject(oVar) ? oVar : [oVar, oVar, oVar, oVar]; + + return [pick(options[target + 'Top'], tArray[0]), + pick(options[target + 'Right'], tArray[1]), + pick(options[target + 'Bottom'], tArray[2]), + pick(options[target + 'Left'], tArray[3])]; + } +}; // end Chart + +// Hook for exporting module +Chart.prototype.callbacks = []; + +var CenteredSeriesMixin = Highcharts.CenteredSeriesMixin = { + /** + * Get the center of the pie based on the size and center options relative to the + * plot area. Borrowed by the polar and gauge series types. + */ + getCenter: function () { + + var options = this.options, + chart = this.chart, + slicingRoom = 2 * (options.slicedOffset || 0), + handleSlicingRoom, + plotWidth = chart.plotWidth - 2 * slicingRoom, + plotHeight = chart.plotHeight - 2 * slicingRoom, + centerOption = options.center, + positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0], + smallestSize = mathMin(plotWidth, plotHeight), + isPercent; + + return map(positions, function (length, i) { + isPercent = /%$/.test(length); + handleSlicingRoom = i < 2 || (i === 2 && isPercent); + return (isPercent ? + // i == 0: centerX, relative to width + // i == 1: centerY, relative to height + // i == 2: size, relative to smallestSize + // i == 4: innerSize, relative to smallestSize + [plotWidth, plotHeight, smallestSize, smallestSize][i] * + pInt(length) / 100 : + length) + (handleSlicingRoom ? slicingRoom : 0); + }); + } +}; + +/** + * The Point object and prototype. Inheritable and used as base for PiePoint + */ +var Point = function () {}; +Point.prototype = { + + /** + * Initialize the point + * @param {Object} series The series object containing this point + * @param {Object} options The data in either number, array or object format + */ + init: function (series, options, x) { + + var point = this, + colors; + point.series = series; + point.applyOptions(options, x); + point.pointAttr = {}; + + if (series.options.colorByPoint) { + colors = series.options.colors || series.chart.options.colors; + point.color = point.color || colors[series.colorCounter++]; + // loop back to zero + if (series.colorCounter === colors.length) { + series.colorCounter = 0; + } + } + + series.chart.pointCount++; + return point; + }, + /** + * Apply the options containing the x and y data and possible some extra properties. + * This is called on point init or from point.update. + * + * @param {Object} options + */ + applyOptions: function (options, x) { + var point = this, + series = point.series, + pointValKey = series.pointValKey; + + options = Point.prototype.optionsToObject.call(this, options); + + // copy options directly to point + extend(point, options); + point.options = point.options ? extend(point.options, options) : options; + + // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low. + if (pointValKey) { + point.y = point[pointValKey]; + } + + // If no x is set by now, get auto incremented value. All points must have an + // x value, however the y value can be null to create a gap in the series + if (point.x === UNDEFINED && series) { + point.x = x === UNDEFINED ? series.autoIncrement() : x; + } + + return point; + }, + + /** + * Transform number or array configs into objects + */ + optionsToObject: function (options) { + var ret = {}, + series = this.series, + pointArrayMap = series.pointArrayMap || ['y'], + valueCount = pointArrayMap.length, + firstItemType, + i = 0, + j = 0; + + if (typeof options === 'number' || options === null) { + ret[pointArrayMap[0]] = options; + + } else if (isArray(options)) { + // with leading x value + if (options.length > valueCount) { + firstItemType = typeof options[0]; + if (firstItemType === 'string') { + ret.name = options[0]; + } else if (firstItemType === 'number') { + ret.x = options[0]; + } + i++; + } + while (j < valueCount) { + ret[pointArrayMap[j++]] = options[i++]; + } + } else if (typeof options === 'object') { + ret = options; + + // This is the fastest way to detect if there are individual point dataLabels that need + // to be considered in drawDataLabels. These can only occur in object configs. + if (options.dataLabels) { + series._hasPointLabels = true; + } + + // Same approach as above for markers + if (options.marker) { + series._hasPointMarkers = true; + } + } + return ret; + }, + + /** + * Destroy a point to clear memory. Its reference still stays in series.data. + */ + destroy: function () { + var point = this, + series = point.series, + chart = series.chart, + hoverPoints = chart.hoverPoints, + prop; + + chart.pointCount--; + + if (hoverPoints) { + point.setState(); + erase(hoverPoints, point); + if (!hoverPoints.length) { + chart.hoverPoints = null; + } + + } + if (point === chart.hoverPoint) { + point.onMouseOut(); + } + + // remove all events + if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive + removeEvent(point); + point.destroyElements(); + } + + if (point.legendItem) { // pies have legend items + chart.legend.destroyItem(point); + } + + for (prop in point) { + point[prop] = null; + } + + + }, + + /** + * Destroy SVG elements associated with the point + */ + destroyElements: function () { + var point = this, + props = ['graphic', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'], + prop, + i = 6; + while (i--) { + prop = props[i]; + if (point[prop]) { + point[prop] = point[prop].destroy(); + } + } + }, + + /** + * Return the configuration hash needed for the data label and tooltip formatters + */ + getLabelConfig: function () { + var point = this; + return { + x: point.category, + y: point.y, + key: point.name || point.category, + series: point.series, + point: point, + percentage: point.percentage, + total: point.total || point.stackTotal + }; + }, + + /** + * Extendable method for formatting each point's tooltip line + * + * @return {String} A string to be concatenated in to the common tooltip text + */ + tooltipFormatter: function (pointFormat) { + + // Insert options for valueDecimals, valuePrefix, and valueSuffix + var series = this.series, + seriesTooltipOptions = series.tooltipOptions, + valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''), + valuePrefix = seriesTooltipOptions.valuePrefix || '', + valueSuffix = seriesTooltipOptions.valueSuffix || ''; + + // Loop over the point array map and replace unformatted values with sprintf formatting markup + each(series.pointArrayMap || ['y'], function (key) { + key = '{point.' + key; // without the closing bracket + if (valuePrefix || valueSuffix) { + pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix); + } + pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}'); + }); + + return format(pointFormat, { + point: this, + series: this.series + }); + }, + + /** + * Fire an event on the Point object. Must not be renamed to fireEvent, as this + * causes a name clash in MooTools + * @param {String} eventType + * @param {Object} eventArgs Additional event arguments + * @param {Function} defaultFunction Default event handler + */ + firePointEvent: function (eventType, eventArgs, defaultFunction) { + var point = this, + series = this.series, + seriesOptions = series.options; + + // load event handlers on demand to save time on mouseover/out + if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) { + this.importEvents(); + } + + // add default handler if in selection mode + if (eventType === 'click' && seriesOptions.allowPointSelect) { + defaultFunction = function (event) { + // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera + point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); + }; + } + + fireEvent(this, eventType, eventArgs, defaultFunction); + } +};/** + * @classDescription The base function which all other series types inherit from. The data in the series is stored + * in various arrays. + * + * - First, series.options.data contains all the original config options for + * each point whether added by options or methods like series.addPoint. + * - Next, series.data contains those values converted to points, but in case the series data length + * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It + * only contains the points that have been created on demand. + * - Then there's series.points that contains all currently visible point objects. In case of cropping, + * the cropped-away points are not part of this array. The series.points array starts at series.cropStart + * compared to series.data and series.options.data. If however the series data is grouped, these can't + * be correlated one to one. + * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points. + * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points. + * + * @param {Object} chart + * @param {Object} options + */ +var Series = function () {}; + +Series.prototype = { + + isCartesian: true, + type: 'line', + pointClass: Point, + sorted: true, // requires the data to be sorted + requireSorting: true, + pointAttrToOptions: { // mapping between SVG attributes and the corresponding options + stroke: 'lineColor', + 'stroke-width': 'lineWidth', + fill: 'fillColor', + r: 'radius' + }, + axisTypes: ['xAxis', 'yAxis'], + colorCounter: 0, + parallelArrays: ['x', 'y'], // each point's x and y values are stored in this.xData and this.yData + init: function (chart, options) { + var series = this, + eventType, + events, + chartSeries = chart.series, + sortByIndex = function (a, b) { + return pick(a.options.index, a._i) - pick(b.options.index, b._i); + }; + + series.chart = chart; + series.options = options = series.setOptions(options); // merge with plotOptions + series.linkedSeries = []; + + // bind the axes + series.bindAxes(); + + // set some variables + extend(series, { + name: options.name, + state: NORMAL_STATE, + pointAttr: {}, + visible: options.visible !== false, // true by default + selected: options.selected === true // false by default + }); + + // special + if (useCanVG) { + options.animation = false; + } + + // register event listeners + events = options.events; + for (eventType in events) { + addEvent(series, eventType, events[eventType]); + } + if ( + (events && events.click) || + (options.point && options.point.events && options.point.events.click) || + options.allowPointSelect + ) { + chart.runTrackerClick = true; + } + + series.getColor(); + series.getSymbol(); + + // Set the data + each(series.parallelArrays, function (key) { + series[key + 'Data'] = []; + }); + series.setData(options.data, false); + + // Mark cartesian + if (series.isCartesian) { + chart.hasCartesianSeries = true; + } + + // Register it in the chart + chartSeries.push(series); + series._i = chartSeries.length - 1; + + // Sort series according to index option (#248, #1123, #2456) + stableSort(chartSeries, sortByIndex); + if (this.yAxis) { + stableSort(this.yAxis.series, sortByIndex); + } + + each(chartSeries, function (series, i) { + series.index = i; + series.name = series.name || 'Series ' + (i + 1); + }); + + }, + + /** + * Set the xAxis and yAxis properties of cartesian series, and register the series + * in the axis.series array + */ + bindAxes: function () { + var series = this, + seriesOptions = series.options, + chart = series.chart, + axisOptions; + + each(series.axisTypes || [], function (AXIS) { // repeat for xAxis and yAxis + + each(chart[AXIS], function (axis) { // loop through the chart's axis objects + axisOptions = axis.options; + + // apply if the series xAxis or yAxis option mathches the number of the + // axis, or if undefined, use the first axis + if ((seriesOptions[AXIS] === axisOptions.index) || + (seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) || + (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) { + + // register this series in the axis.series lookup + axis.series.push(series); + + // set this series.xAxis or series.yAxis reference + series[AXIS] = axis; + + // mark dirty for redraw + axis.isDirty = true; + } + }); + + // The series needs an X and an Y axis + if (!series[AXIS] && series.optionalAxis !== AXIS) { + error(18, true); + } + + }); + }, + + /** + * For simple series types like line and column, the data values are held in arrays like + * xData and yData for quick lookup to find extremes and more. For multidimensional series + * like bubble and map, this can be extended with arrays like zData and valueData by + * adding to the series.parallelArrays array. + */ + updateParallelArrays: function (point, i) { + var series = point.series, + args = arguments, + fn = typeof i === 'number' ? + // Insert the value in the given position + function (key) { + var val = key === 'y' && series.toYData ? series.toYData(point) : point[key]; + series[key + 'Data'][i] = val; + } : + // Apply the method specified in i with the following arguments as arguments + function (key) { + Array.prototype[i].apply(series[key + 'Data'], Array.prototype.slice.call(args, 2)); + }; + + each(series.parallelArrays, fn); + }, + + /** + * Return an auto incremented x value based on the pointStart and pointInterval options. + * This is only used if an x value is not given for the point that calls autoIncrement. + */ + autoIncrement: function () { + var series = this, + options = series.options, + xIncrement = series.xIncrement; + + xIncrement = pick(xIncrement, options.pointStart, 0); + + series.pointInterval = pick(series.pointInterval, options.pointInterval, 1); + + series.xIncrement = xIncrement + series.pointInterval; + return xIncrement; + }, + + /** + * Divide the series data into segments divided by null values. + */ + getSegments: function () { + var series = this, + lastNull = -1, + segments = [], + i, + points = series.points, + pointsLength = points.length; + + if (pointsLength) { // no action required for [] + + // if connect nulls, just remove null points + if (series.options.connectNulls) { + i = pointsLength; + while (i--) { + if (points[i].y === null) { + points.splice(i, 1); + } + } + if (points.length) { + segments = [points]; + } + + // else, split on null points + } else { + each(points, function (point, i) { + if (point.y === null) { + if (i > lastNull + 1) { + segments.push(points.slice(lastNull + 1, i)); + } + lastNull = i; + } else if (i === pointsLength - 1) { // last value + segments.push(points.slice(lastNull + 1, i + 1)); + } + }); + } + } + + // register it + series.segments = segments; + }, + + /** + * Set the series options by merging from the options tree + * @param {Object} itemOptions + */ + setOptions: function (itemOptions) { + var chart = this.chart, + chartOptions = chart.options, + plotOptions = chartOptions.plotOptions, + userOptions = chart.userOptions || {}, + userPlotOptions = userOptions.plotOptions || {}, + typeOptions = plotOptions[this.type], + options; + + this.userOptions = itemOptions; + + options = merge( + typeOptions, + plotOptions.series, + itemOptions + ); + + // The tooltip options are merged between global and series specific options + this.tooltipOptions = merge( + defaultOptions.tooltip, + defaultOptions.plotOptions[this.type].tooltip, + userOptions.tooltip, + userPlotOptions.series && userPlotOptions.series.tooltip, + userPlotOptions[this.type] && userPlotOptions[this.type].tooltip, + itemOptions.tooltip + ); + + // Delete marker object if not allowed (#1125) + if (typeOptions.marker === null) { + delete options.marker; + } + + return options; + + }, + /** + * Get the series' color + */ + getColor: function () { + var options = this.options, + userOptions = this.userOptions, + defaultColors = this.chart.options.colors, + counters = this.chart.counters, + color, + colorIndex; + + color = options.color || defaultPlotOptions[this.type].color; + + if (!color && !options.colorByPoint) { + if (defined(userOptions._colorIndex)) { // after Series.update() + colorIndex = userOptions._colorIndex; + } else { + userOptions._colorIndex = counters.color; + colorIndex = counters.color++; + } + color = defaultColors[colorIndex]; + } + + this.color = color; + counters.wrapColor(defaultColors.length); + }, + /** + * Get the series' symbol + */ + getSymbol: function () { + var series = this, + userOptions = series.userOptions, + seriesMarkerOption = series.options.marker, + chart = series.chart, + defaultSymbols = chart.options.symbols, + counters = chart.counters, + symbolIndex; + + series.symbol = seriesMarkerOption.symbol; + if (!series.symbol) { + if (defined(userOptions._symbolIndex)) { // after Series.update() + symbolIndex = userOptions._symbolIndex; + } else { + userOptions._symbolIndex = counters.symbol; + symbolIndex = counters.symbol++; + } + series.symbol = defaultSymbols[symbolIndex]; + } + + // don't substract radius in image symbols (#604) + if (/^url/.test(series.symbol)) { + seriesMarkerOption.radius = 0; + } + counters.wrapSymbol(defaultSymbols.length); + }, + + drawLegendSymbol: LegendSymbolMixin.drawLineMarker, + + /** + * Replace the series data with a new set of data + * @param {Object} data + * @param {Object} redraw + */ + setData: function (data, redraw, animation, updatePoints) { + var series = this, + oldData = series.points, + oldDataLength = (oldData && oldData.length) || 0, + dataLength, + options = series.options, + chart = series.chart, + firstPoint = null, + xAxis = series.xAxis, + hasCategories = xAxis && !!xAxis.categories, + tooltipPoints = series.tooltipPoints, + i, + turboThreshold = options.turboThreshold, + pt, + xData = this.xData, + yData = this.yData, + pointArrayMap = series.pointArrayMap, + valueCount = pointArrayMap && pointArrayMap.length; + + data = data || []; + dataLength = data.length; + redraw = pick(redraw, true); + + // If the point count is the same as is was, just run Point.update which is + // cheaper, allows animation, and keeps references to points. + if (updatePoints !== false && dataLength && oldDataLength === dataLength && !series.cropped && !series.hasGroupedData) { + each(data, function (point, i) { + oldData[i].update(point, false); + }); + + } else { + + // Reset properties + series.xIncrement = null; + series.pointRange = hasCategories ? 1 : options.pointRange; + + series.colorCounter = 0; // for series with colorByPoint (#1547) + + // Update parallel arrays + each(this.parallelArrays, function (key) { + series[key + 'Data'].length = 0; + }); + + // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The + // first value is tested, and we assume that all the rest are defined the same + // way. Although the 'for' loops are similar, they are repeated inside each + // if-else conditional for max performance. + if (turboThreshold && dataLength > turboThreshold) { + + // find the first non-null point + i = 0; + while (firstPoint === null && i < dataLength) { + firstPoint = data[i]; + i++; + } + + + if (isNumber(firstPoint)) { // assume all points are numbers + var x = pick(options.pointStart, 0), + pointInterval = pick(options.pointInterval, 1); + + for (i = 0; i < dataLength; i++) { + xData[i] = x; + yData[i] = data[i]; + x += pointInterval; + } + series.xIncrement = x; + } else if (isArray(firstPoint)) { // assume all points are arrays + if (valueCount) { // [x, low, high] or [x, o, h, l, c] + for (i = 0; i < dataLength; i++) { + pt = data[i]; + xData[i] = pt[0]; + yData[i] = pt.slice(1, valueCount + 1); + } + } else { // [x, y] + for (i = 0; i < dataLength; i++) { + pt = data[i]; + xData[i] = pt[0]; + yData[i] = pt[1]; + } + } + } else { + error(12); // Highcharts expects configs to be numbers or arrays in turbo mode + } + } else { + for (i = 0; i < dataLength; i++) { + if (data[i] !== UNDEFINED) { // stray commas in oldIE + pt = { series: series }; + series.pointClass.prototype.applyOptions.apply(pt, [data[i]]); + series.updateParallelArrays(pt, i); + if (hasCategories && pt.name) { + xAxis.names[pt.x] = pt.name; // #2046 + } + } + } + } + + // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON + if (isString(yData[0])) { + error(14, true); + } + + series.data = []; + series.options.data = data; + //series.zData = zData; + + // destroy old points + i = oldDataLength; + while (i--) { + if (oldData[i] && oldData[i].destroy) { + oldData[i].destroy(); + } + } + if (tooltipPoints) { // #2594 + tooltipPoints.length = 0; + } + + // reset minRange (#878) + if (xAxis) { + xAxis.minRange = xAxis.userMinRange; + } + + // redraw + series.isDirty = series.isDirtyData = chart.isDirtyBox = true; + animation = false; + } + + if (redraw) { + chart.redraw(animation); + } + }, + + /** + * Process the data by cropping away unused data points if the series is longer + * than the crop threshold. This saves computing time for lage series. + */ + processData: function (force) { + var series = this, + processedXData = series.xData, // copied during slice operation below + processedYData = series.yData, + dataLength = processedXData.length, + croppedData, + cropStart = 0, + cropped, + distance, + closestPointRange, + xAxis = series.xAxis, + i, // loop variable + options = series.options, + cropThreshold = options.cropThreshold, + activePointCount = 0, + isCartesian = series.isCartesian, + min, + max; + + // If the series data or axes haven't changed, don't go through this. Return false to pass + // the message on to override methods like in data grouping. + if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) { + return false; + } + + + // optionally filter out points outside the plot area + if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) { + + min = xAxis.min; + max = xAxis.max; + + // it's outside current extremes + if (processedXData[dataLength - 1] < min || processedXData[0] > max) { + processedXData = []; + processedYData = []; + + // only crop if it's actually spilling out + } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) { + croppedData = this.cropData(series.xData, series.yData, min, max); + processedXData = croppedData.xData; + processedYData = croppedData.yData; + cropStart = croppedData.start; + cropped = true; + activePointCount = processedXData.length; + } + } + + + // Find the closest distance between processed points + for (i = processedXData.length - 1; i >= 0; i--) { + distance = processedXData[i] - processedXData[i - 1]; + + if (!cropped && processedXData[i] > min && processedXData[i] < max) { + activePointCount++; + } + if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) { + closestPointRange = distance; + + // Unsorted data is not supported by the line tooltip, as well as data grouping and + // navigation in Stock charts (#725) and width calculation of columns (#1900) + } else if (distance < 0 && series.requireSorting) { + error(15); + } + } + + // Record the properties + series.cropped = cropped; // undefined or true + series.cropStart = cropStart; + series.processedXData = processedXData; + series.processedYData = processedYData; + series.activePointCount = activePointCount; + + if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC + series.pointRange = closestPointRange || 1; + } + series.closestPointRange = closestPointRange; + + }, + + /** + * Iterate over xData and crop values between min and max. Returns object containing crop start/end + * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range + */ + cropData: function (xData, yData, min, max) { + var dataLength = xData.length, + cropStart = 0, + cropEnd = dataLength, + cropShoulder = pick(this.cropShoulder, 1), // line-type series need one point outside + i; + + // iterate up to find slice start + for (i = 0; i < dataLength; i++) { + if (xData[i] >= min) { + cropStart = mathMax(0, i - cropShoulder); + break; + } + } + + // proceed to find slice end + for (; i < dataLength; i++) { + if (xData[i] > max) { + cropEnd = i + cropShoulder; + break; + } + } + + return { + xData: xData.slice(cropStart, cropEnd), + yData: yData.slice(cropStart, cropEnd), + start: cropStart, + end: cropEnd + }; + }, + + + /** + * Generate the data point after the data has been processed by cropping away + * unused points and optionally grouped in Highcharts Stock. + */ + generatePoints: function () { + var series = this, + options = series.options, + dataOptions = options.data, + data = series.data, + dataLength, + processedXData = series.processedXData, + processedYData = series.processedYData, + pointClass = series.pointClass, + processedDataLength = processedXData.length, + cropStart = series.cropStart || 0, + cursor, + hasGroupedData = series.hasGroupedData, + point, + points = [], + i; + + if (!data && !hasGroupedData) { + var arr = []; + arr.length = dataOptions.length; + data = series.data = arr; + } + + for (i = 0; i < processedDataLength; i++) { + cursor = cropStart + i; + if (!hasGroupedData) { + if (data[cursor]) { + point = data[cursor]; + } else if (dataOptions[cursor] !== UNDEFINED) { // #970 + data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]); + } + points[i] = point; + } else { + // splat the y data in case of ohlc data array + points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i]))); + } + } + + // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when + // swithching view from non-grouped data to grouped data (#637) + if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) { + for (i = 0; i < dataLength; i++) { + if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points + i += processedDataLength; + } + if (data[i]) { + data[i].destroyElements(); + data[i].plotX = UNDEFINED; // #1003 + } + } + } + + series.data = data; + series.points = points; + }, + + /** + * Calculate Y extremes for visible data + */ + getExtremes: function (yData) { + var xAxis = this.xAxis, + yAxis = this.yAxis, + xData = this.processedXData, + yDataLength, + activeYData = [], + activeCounter = 0, + xExtremes = xAxis.getExtremes(), // #2117, need to compensate for log X axis + xMin = xExtremes.min, + xMax = xExtremes.max, + validValue, + withinRange, + dataMin, + dataMax, + x, + y, + i, + j; + + yData = yData || this.stackedYData || this.processedYData; + yDataLength = yData.length; + + for (i = 0; i < yDataLength; i++) { + + x = xData[i]; + y = yData[i]; + + // For points within the visible range, including the first point outside the + // visible range, consider y extremes + validValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0)); + withinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin && + (xData[i - 1] || x) <= xMax); + + if (validValue && withinRange) { + + j = y.length; + if (j) { // array, like ohlc or range data + while (j--) { + if (y[j] !== null) { + activeYData[activeCounter++] = y[j]; + } + } + } else { + activeYData[activeCounter++] = y; + } + } + } + this.dataMin = pick(dataMin, arrayMin(activeYData)); + this.dataMax = pick(dataMax, arrayMax(activeYData)); + }, + + /** + * Translate data points from raw data values to chart specific positioning data + * needed later in drawPoints, drawGraph and drawTracker. + */ + translate: function () { + if (!this.processedXData) { // hidden series + this.processData(); + } + this.generatePoints(); + var series = this, + options = series.options, + stacking = options.stacking, + xAxis = series.xAxis, + categories = xAxis.categories, + yAxis = series.yAxis, + points = series.points, + dataLength = points.length, + hasModifyValue = !!series.modifyValue, + i, + pointPlacement = options.pointPlacement, + dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement), + threshold = options.threshold; + + // Translate each point + for (i = 0; i < dataLength; i++) { + var point = points[i], + xValue = point.x, + yValue = point.y, + yBottom = point.low, + stack = stacking && yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey], + pointStack, + stackValues; + + // Discard disallowed y values for log axes + if (yAxis.isLog && yValue <= 0) { + point.y = yValue = null; + } + + // Get the plotX translation + point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags'); // Math.round fixes #591 + + + // Calculate the bottom y value for stacked series + if (stacking && series.visible && stack && stack[xValue]) { + + pointStack = stack[xValue]; + stackValues = pointStack.points[series.index + ',' + i]; + yBottom = stackValues[0]; + yValue = stackValues[1]; + + if (yBottom === 0) { + yBottom = pick(threshold, yAxis.min); + } + if (yAxis.isLog && yBottom <= 0) { // #1200, #1232 + yBottom = null; + } + + point.total = point.stackTotal = pointStack.total; + point.percentage = pointStack.total && (point.y / pointStack.total * 100); + point.stackY = yValue; + + // Place the stack label + pointStack.setOffset(series.pointXOffset || 0, series.barW || 0); + + } + + // Set translated yBottom or remove it + point.yBottom = defined(yBottom) ? + yAxis.translate(yBottom, 0, 1, 0, 1) : + null; + + // general hook, used for Highstock compare mode + if (hasModifyValue) { + yValue = series.modifyValue(yValue, point); + } + + // Set the the plotY value, reset it for redraws + point.plotY = (typeof yValue === 'number' && yValue !== Infinity) ? + //mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591 + yAxis.translate(yValue, 0, 1, 0, 1) : + UNDEFINED; + + // Set client related positions for mouse tracking + point.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514 + + point.negative = point.y < (threshold || 0); + + // some API data + point.category = categories && categories[point.x] !== UNDEFINED ? + categories[point.x] : point.x; + + } + + // now that we have the cropped data, build the segments + series.getSegments(); + }, + + /** + * Animate in the series + */ + animate: function (init) { + var series = this, + chart = series.chart, + renderer = chart.renderer, + clipRect, + markerClipRect, + animation = series.options.animation, + clipBox = series.clipBox || chart.clipBox, + inverted = chart.inverted, + sharedClipKey; + + // Animation option is set to true + if (animation && !isObject(animation)) { + animation = defaultPlotOptions[series.type].animation; + } + sharedClipKey = ['_sharedClip', animation.duration, animation.easing, clipBox.height].join(','); + + // Initialize the animation. Set up the clipping rectangle. + if (init) { + + // If a clipping rectangle with the same properties is currently present in the chart, use that. + clipRect = chart[sharedClipKey]; + markerClipRect = chart[sharedClipKey + 'm']; + if (!clipRect) { + chart[sharedClipKey] = clipRect = renderer.clipRect( + extend(clipBox, { width: 0 }) + ); + + chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect( + -99, // include the width of the first marker + inverted ? -chart.plotLeft : -chart.plotTop, + 99, + inverted ? chart.chartWidth : chart.chartHeight + ); + } + series.group.clip(clipRect); + series.markerGroup.clip(markerClipRect); + series.sharedClipKey = sharedClipKey; + + // Run the animation + } else { + clipRect = chart[sharedClipKey]; + if (clipRect) { + clipRect.animate({ + width: chart.plotSizeX + }, animation); + } + if (chart[sharedClipKey + 'm']) { + chart[sharedClipKey + 'm'].animate({ + width: chart.plotSizeX + 99 + }, animation); + } + + // Delete this function to allow it only once + series.animate = null; + + } + }, + + /** + * This runs after animation to land on the final plot clipping + */ + afterAnimate: function () { + var chart = this.chart, + sharedClipKey = this.sharedClipKey, + group = this.group, + clipBox = this.clipBox; + + if (group && this.options.clip !== false) { + if (!sharedClipKey || !clipBox) { + group.clip(clipBox ? chart.renderer.clipRect(clipBox) : chart.clipRect); + } + this.markerGroup.clip(); // no clip + } + + fireEvent(this, 'afterAnimate'); + + // Remove the shared clipping rectancgle when all series are shown + setTimeout(function () { + if (sharedClipKey && chart[sharedClipKey]) { + if (!clipBox) { + chart[sharedClipKey] = chart[sharedClipKey].destroy(); + } + if (chart[sharedClipKey + 'm']) { + chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy(); + } + } + }, 100); + }, + + /** + * Draw the markers + */ + drawPoints: function () { + var series = this, + pointAttr, + points = series.points, + chart = series.chart, + plotX, + plotY, + i, + point, + radius, + symbol, + isImage, + graphic, + options = series.options, + seriesMarkerOptions = options.marker, + seriesPointAttr = series.pointAttr[''], + pointMarkerOptions, + enabled, + isInside, + markerGroup = series.markerGroup, + globallyEnabled = pick( + seriesMarkerOptions.enabled, + series.activePointCount < (0.5 * series.xAxis.len / seriesMarkerOptions.radius) + ); + + if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) { + + i = points.length; + while (i--) { + point = points[i]; + plotX = mathFloor(point.plotX); // #1843 + plotY = point.plotY; + graphic = point.graphic; + pointMarkerOptions = point.marker || {}; + enabled = (globallyEnabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled; + isInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858 + + // only draw the point if y is defined + if (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { + + // shortcuts + pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || seriesPointAttr; + radius = pointAttr.r; + symbol = pick(pointMarkerOptions.symbol, series.symbol); + isImage = symbol.indexOf('url') === 0; + + if (graphic) { // update + graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled + .animate(extend({ + x: plotX - radius, + y: plotY - radius + }, graphic.symbolName ? { // don't apply to image symbols #507 + width: 2 * radius, + height: 2 * radius + } : {})); + } else if (isInside && (radius > 0 || isImage)) { + point.graphic = graphic = chart.renderer.symbol( + symbol, + plotX - radius, + plotY - radius, + 2 * radius, + 2 * radius + ) + .attr(pointAttr) + .add(markerGroup); + } + + } else if (graphic) { + point.graphic = graphic.destroy(); // #1269 + } + } + } + + }, + + /** + * Convert state properties from API naming conventions to SVG attributes + * + * @param {Object} options API options object + * @param {Object} base1 SVG attribute object to inherit from + * @param {Object} base2 Second level SVG attribute object to inherit from + */ + convertAttribs: function (options, base1, base2, base3) { + var conversion = this.pointAttrToOptions, + attr, + option, + obj = {}; + + options = options || {}; + base1 = base1 || {}; + base2 = base2 || {}; + base3 = base3 || {}; + + for (attr in conversion) { + option = conversion[attr]; + obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]); + } + return obj; + }, + + /** + * Get the state attributes. Each series type has its own set of attributes + * that are allowed to change on a point's state change. Series wide attributes are stored for + * all series, and additionally point specific attributes are stored for all + * points with individual marker options. If such options are not defined for the point, + * a reference to the series wide attributes is stored in point.pointAttr. + */ + getAttribs: function () { + var series = this, + seriesOptions = series.options, + normalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions, + stateOptions = normalOptions.states, + stateOptionsHover = stateOptions[HOVER_STATE], + pointStateOptionsHover, + seriesColor = series.color, + normalDefaults = { + stroke: seriesColor, + fill: seriesColor + }, + points = series.points || [], // #927 + i, + point, + seriesPointAttr = [], + pointAttr, + pointAttrToOptions = series.pointAttrToOptions, + hasPointSpecificOptions = series.hasPointSpecificOptions, + negativeColor = seriesOptions.negativeColor, + defaultLineColor = normalOptions.lineColor, + defaultFillColor = normalOptions.fillColor, + turboThreshold = seriesOptions.turboThreshold, + attr, + key; + + // series type specific modifications + if (seriesOptions.marker) { // line, spline, area, areaspline, scatter + + // if no hover radius is given, default to normal radius + 2 + stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2; + stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1; + + } else { // column, bar, pie + + // if no hover color is given, brighten the normal color + stateOptionsHover.color = stateOptionsHover.color || + Color(stateOptionsHover.color || seriesColor) + .brighten(stateOptionsHover.brightness).get(); + } + + // general point attributes for the series normal state + seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults); + + // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius + each([HOVER_STATE, SELECT_STATE], function (state) { + seriesPointAttr[state] = + series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]); + }); + + // set it + series.pointAttr = seriesPointAttr; + + + // Generate the point-specific attribute collections if specific point + // options are given. If not, create a referance to the series wide point + // attributes + i = points.length; + if (!turboThreshold || i < turboThreshold || hasPointSpecificOptions) { + while (i--) { + point = points[i]; + normalOptions = (point.options && point.options.marker) || point.options; + if (normalOptions && normalOptions.enabled === false) { + normalOptions.radius = 0; + } + + if (point.negative && negativeColor) { + point.color = point.fillColor = negativeColor; + } + + hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868 + + // check if the point has specific visual options + if (point.options) { + for (key in pointAttrToOptions) { + if (defined(normalOptions[pointAttrToOptions[key]])) { + hasPointSpecificOptions = true; + } + } + } + + // a specific marker config object is defined for the individual point: + // create it's own attribute collection + if (hasPointSpecificOptions) { + normalOptions = normalOptions || {}; + pointAttr = []; + stateOptions = normalOptions.states || {}; // reassign for individual point + pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {}; + + // Handle colors for column and pies + if (!seriesOptions.marker) { // column, bar, point + // If no hover color is given, brighten the normal color. #1619, #2579 + pointStateOptionsHover.color = pointStateOptionsHover.color || (!point.options.color && stateOptionsHover.color) || + Color(point.color) + .brighten(pointStateOptionsHover.brightness || stateOptionsHover.brightness) + .get(); + } + + // normal point state inherits series wide normal state + attr = { color: point.color }; // #868 + if (!defaultFillColor) { // Individual point color or negative color markers (#2219) + attr.fillColor = point.color; + } + if (!defaultLineColor) { + attr.lineColor = point.color; // Bubbles take point color, line markers use white + } + pointAttr[NORMAL_STATE] = series.convertAttribs(extend(attr, normalOptions), seriesPointAttr[NORMAL_STATE]); + + // inherit from point normal and series hover + pointAttr[HOVER_STATE] = series.convertAttribs( + stateOptions[HOVER_STATE], + seriesPointAttr[HOVER_STATE], + pointAttr[NORMAL_STATE] + ); + + // inherit from point normal and series hover + pointAttr[SELECT_STATE] = series.convertAttribs( + stateOptions[SELECT_STATE], + seriesPointAttr[SELECT_STATE], + pointAttr[NORMAL_STATE] + ); + + + // no marker config object is created: copy a reference to the series-wide + // attribute collection + } else { + pointAttr = seriesPointAttr; + } + + point.pointAttr = pointAttr; + } + } + }, + + /** + * Clear DOM objects and free up memory + */ + destroy: function () { + var series = this, + chart = series.chart, + issue134 = /AppleWebKit\/533/.test(userAgent), + destroy, + i, + data = series.data || [], + point, + prop, + axis; + + // add event hook + fireEvent(series, 'destroy'); + + // remove all events + removeEvent(series); + + // erase from axes + each(series.axisTypes || [], function (AXIS) { + axis = series[AXIS]; + if (axis) { + erase(axis.series, series); + axis.isDirty = axis.forceRedraw = true; + } + }); + + // remove legend items + if (series.legendItem) { + series.chart.legend.destroyItem(series); + } + + // destroy all points with their elements + i = data.length; + while (i--) { + point = data[i]; + if (point && point.destroy) { + point.destroy(); + } + } + series.points = null; + + // Clear the animation timeout if we are destroying the series during initial animation + clearTimeout(series.animationTimeout); + + // destroy all SVGElements associated to the series + each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker', + 'graphNeg', 'areaNeg', 'posClip', 'negClip'], function (prop) { + if (series[prop]) { + + // issue 134 workaround + destroy = issue134 && prop === 'group' ? + 'hide' : + 'destroy'; + + series[prop][destroy](); + } + }); + + // remove from hoverSeries + if (chart.hoverSeries === series) { + chart.hoverSeries = null; + } + erase(chart.series, series); + + // clear all members + for (prop in series) { + delete series[prop]; + } + }, + + /** + * Return the graph path of a segment + */ + getSegmentPath: function (segment) { + var series = this, + segmentPath = [], + step = series.options.step; + + // build the segment line + each(segment, function (point, i) { + + var plotX = point.plotX, + plotY = point.plotY, + lastPoint; + + if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object + segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i)); + + } else { + + // moveTo or lineTo + segmentPath.push(i ? L : M); + + // step line? + if (step && i) { + lastPoint = segment[i - 1]; + if (step === 'right') { + segmentPath.push( + lastPoint.plotX, + plotY + ); + + } else if (step === 'center') { + segmentPath.push( + (lastPoint.plotX + plotX) / 2, + lastPoint.plotY, + (lastPoint.plotX + plotX) / 2, + plotY + ); + + } else { + segmentPath.push( + plotX, + lastPoint.plotY + ); + } + } + + // normal line to next point + segmentPath.push( + point.plotX, + point.plotY + ); + } + }); + + return segmentPath; + }, + + /** + * Get the graph path + */ + getGraphPath: function () { + var series = this, + graphPath = [], + segmentPath, + singlePoints = []; // used in drawTracker + + // Divide into segments and build graph and area paths + each(series.segments, function (segment) { + + segmentPath = series.getSegmentPath(segment); + + // add the segment to the graph, or a single point for tracking + if (segment.length > 1) { + graphPath = graphPath.concat(segmentPath); + } else { + singlePoints.push(segment[0]); + } + }); + + // Record it for use in drawGraph and drawTracker, and return graphPath + series.singlePoints = singlePoints; + series.graphPath = graphPath; + + return graphPath; + + }, + + /** + * Draw the actual graph + */ + drawGraph: function () { + var series = this, + options = this.options, + props = [['graph', options.lineColor || this.color]], + lineWidth = options.lineWidth, + dashStyle = options.dashStyle, + roundCap = options.linecap !== 'square', + graphPath = this.getGraphPath(), + negativeColor = options.negativeColor; + + if (negativeColor) { + props.push(['graphNeg', negativeColor]); + } + + // draw the graph + each(props, function (prop, i) { + var graphKey = prop[0], + graph = series[graphKey], + attribs; + + if (graph) { + stop(graph); // cancel running animations, #459 + graph.animate({ d: graphPath }); + + } else if (lineWidth && graphPath.length) { // #1487 + attribs = { + stroke: prop[1], + 'stroke-width': lineWidth, + fill: NONE, + zIndex: 1 // #1069 + }; + if (dashStyle) { + attribs.dashstyle = dashStyle; + } else if (roundCap) { + attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round'; + } + + series[graphKey] = series.chart.renderer.path(graphPath) + .attr(attribs) + .add(series.group) + .shadow(!i && options.shadow); + } + }); + }, + + /** + * Clip the graphs into the positive and negative coloured graphs + */ + clipNeg: function () { + var options = this.options, + chart = this.chart, + renderer = chart.renderer, + negativeColor = options.negativeColor || options.negativeFillColor, + translatedThreshold, + posAttr, + negAttr, + graph = this.graph, + area = this.area, + posClip = this.posClip, + negClip = this.negClip, + chartWidth = chart.chartWidth, + chartHeight = chart.chartHeight, + chartSizeMax = mathMax(chartWidth, chartHeight), + yAxis = this.yAxis, + above, + below; + + if (negativeColor && (graph || area)) { + translatedThreshold = mathRound(yAxis.toPixels(options.threshold || 0, true)); + if (translatedThreshold < 0) { + chartSizeMax -= translatedThreshold; // #2534 + } + above = { + x: 0, + y: 0, + width: chartSizeMax, + height: translatedThreshold + }; + below = { + x: 0, + y: translatedThreshold, + width: chartSizeMax, + height: chartSizeMax + }; + + if (chart.inverted) { + + above.height = below.y = chart.plotWidth - translatedThreshold; + if (renderer.isVML) { + above = { + x: chart.plotWidth - translatedThreshold - chart.plotLeft, + y: 0, + width: chartWidth, + height: chartHeight + }; + below = { + x: translatedThreshold + chart.plotLeft - chartWidth, + y: 0, + width: chart.plotLeft + translatedThreshold, + height: chartWidth + }; + } + } + + if (yAxis.reversed) { + posAttr = below; + negAttr = above; + } else { + posAttr = above; + negAttr = below; + } + + if (posClip) { // update + posClip.animate(posAttr); + negClip.animate(negAttr); + } else { + + this.posClip = posClip = renderer.clipRect(posAttr); + this.negClip = negClip = renderer.clipRect(negAttr); + + if (graph && this.graphNeg) { + graph.clip(posClip); + this.graphNeg.clip(negClip); + } + + if (area) { + area.clip(posClip); + this.areaNeg.clip(negClip); + } + } + } + }, + + /** + * Initialize and perform group inversion on series.group and series.markerGroup + */ + invertGroups: function () { + var series = this, + chart = series.chart; + + // Pie, go away (#1736) + if (!series.xAxis) { + return; + } + + // A fixed size is needed for inversion to work + function setInvert() { + var size = { + width: series.yAxis.len, + height: series.xAxis.len + }; + + each(['group', 'markerGroup'], function (groupName) { + if (series[groupName]) { + series[groupName].attr(size).invert(); + } + }); + } + + addEvent(chart, 'resize', setInvert); // do it on resize + addEvent(series, 'destroy', function () { + removeEvent(chart, 'resize', setInvert); + }); + + // Do it now + setInvert(); // do it now + + // On subsequent render and redraw, just do setInvert without setting up events again + series.invertGroups = setInvert; + }, + + /** + * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and + * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size. + */ + plotGroup: function (prop, name, visibility, zIndex, parent) { + var group = this[prop], + isNew = !group; + + // Generate it on first call + if (isNew) { + this[prop] = group = this.chart.renderer.g(name) + .attr({ + visibility: visibility, + zIndex: zIndex || 0.1 // IE8 needs this + }) + .add(parent); + } + // Place it on first and subsequent (redraw) calls + group[isNew ? 'attr' : 'animate'](this.getPlotBox()); + return group; + }, + + /** + * Get the translation and scale for the plot area of this series + */ + getPlotBox: function () { + var chart = this.chart, + xAxis = this.xAxis, + yAxis = this.yAxis; + + // Swap axes for inverted (#2339) + if (chart.inverted) { + xAxis = yAxis; + yAxis = this.xAxis; + } + return { + translateX: xAxis ? xAxis.left : chart.plotLeft, + translateY: yAxis ? yAxis.top : chart.plotTop, + scaleX: 1, // #1623 + scaleY: 1 + }; + }, + + /** + * Render the graph and markers + */ + render: function () { + var series = this, + chart = series.chart, + group, + options = series.options, + animation = options.animation, + // Animation doesn't work in IE8 quirks when the group div is hidden, + // and looks bad in other oldIE + animDuration = (animation && !!series.animate && chart.renderer.isSVG && pick(animation.duration, 500)) || 0, + visibility = series.visible ? VISIBLE : HIDDEN, + zIndex = options.zIndex, + hasRendered = series.hasRendered, + chartSeriesGroup = chart.seriesGroup; + + // the group + group = series.plotGroup( + 'group', + 'series', + visibility, + zIndex, + chartSeriesGroup + ); + + series.markerGroup = series.plotGroup( + 'markerGroup', + 'markers', + visibility, + zIndex, + chartSeriesGroup + ); + + // initiate the animation + if (animDuration) { + series.animate(true); + } + + // cache attributes for shapes + series.getAttribs(); + + // SVGRenderer needs to know this before drawing elements (#1089, #1795) + group.inverted = series.isCartesian ? chart.inverted : false; + + // draw the graph if any + if (series.drawGraph) { + series.drawGraph(); + series.clipNeg(); + } + + // draw the data labels (inn pies they go before the points) + if (series.drawDataLabels) { + series.drawDataLabels(); + } + + // draw the points + if (series.visible) { + series.drawPoints(); + } + + + // draw the mouse tracking area + if (series.drawTracker && series.options.enableMouseTracking !== false) { + series.drawTracker(); + } + + // Handle inverted series and tracker groups + if (chart.inverted) { + series.invertGroups(); + } + + // Initial clipping, must be defined after inverting groups for VML + if (options.clip !== false && !series.sharedClipKey && !hasRendered) { + group.clip(chart.clipRect); + } + + // Run the animation + if (animDuration) { + series.animate(); + } + + // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option + // which should be available to the user). + if (!hasRendered) { + if (animDuration) { + series.animationTimeout = setTimeout(function () { + series.afterAnimate(); + }, animDuration); + } else { + series.afterAnimate(); + } + } + + series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see + // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see + series.hasRendered = true; + }, + + /** + * Redraw the series after an update in the axes. + */ + redraw: function () { + var series = this, + chart = series.chart, + wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after + group = series.group, + xAxis = series.xAxis, + yAxis = series.yAxis; + + // reposition on resize + if (group) { + if (chart.inverted) { + group.attr({ + width: chart.plotWidth, + height: chart.plotHeight + }); + } + + group.animate({ + translateX: pick(xAxis && xAxis.left, chart.plotLeft), + translateY: pick(yAxis && yAxis.top, chart.plotTop) + }); + } + + series.translate(); + if (series.setTooltipPoints) { + series.setTooltipPoints(true); + } + series.render(); + + if (wasDirtyData) { + fireEvent(series, 'updatedData'); + } + } +}; // end Series prototype + +/** + * The class for stack items + */ +function StackItem(axis, options, isNegative, x, stackOption) { + + var inverted = axis.chart.inverted; + + this.axis = axis; + + // Tells if the stack is negative + this.isNegative = isNegative; + + // Save the options to be able to style the label + this.options = options; + + // Save the x value to be able to position the label later + this.x = x; + + // Initialize total value + this.total = null; + + // This will keep each points' extremes stored by series.index and point index + this.points = {}; + + // Save the stack option on the series configuration object, and whether to treat it as percent + this.stack = stackOption; + + // The align options and text align varies on whether the stack is negative and + // if the chart is inverted or not. + // First test the user supplied value, then use the dynamic. + this.alignOptions = { + align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'), + verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')), + y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)), + x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0) + }; + + this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center'); +} + +StackItem.prototype = { + destroy: function () { + destroyObjectProperties(this, this.axis); + }, + + /** + * Renders the stack total label and adds it to the stack label group. + */ + render: function (group) { + var options = this.options, + formatOption = options.format, + str = formatOption ? + format(formatOption, this) : + options.formatter.call(this); // format the text in the label + + // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden + if (this.label) { + this.label.attr({text: str, visibility: HIDDEN}); + // Create new label + } else { + this.label = + this.axis.chart.renderer.text(str, null, null, options.useHTML) // dummy positions, actual position updated with setOffset method in columnseries + .css(options.style) // apply style + .attr({ + align: this.textAlign, // fix the text-anchor + rotation: options.rotation, // rotation + visibility: HIDDEN // hidden until setOffset is called + }) + .add(group); // add to the labels-group + } + }, + + /** + * Sets the offset that the stack has from the x value and repositions the label. + */ + setOffset: function (xOffset, xWidth) { + var stackItem = this, + axis = stackItem.axis, + chart = axis.chart, + inverted = chart.inverted, + neg = this.isNegative, // special treatment is needed for negative stacks + y = axis.translate(axis.usePercentage ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates + yZero = axis.translate(0), // stack origin + h = mathAbs(y - yZero), // stack height + x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position + plotHeight = chart.plotHeight, + stackBox = { // this is the box for the complete stack + x: inverted ? (neg ? y : y - h) : x, + y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y), + width: inverted ? h : xWidth, + height: inverted ? xWidth : h + }, + label = this.label, + alignAttr; + + if (label) { + label.align(this.alignOptions, null, stackBox); // align the label to the box + + // Set visibility (#678) + alignAttr = label.alignAttr; + label[this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ? 'show' : 'hide'](true); + } + } +}; + + +// Stacking methods defined on the Axis prototype + +/** + * Build the stacks from top down + */ +Axis.prototype.buildStacks = function () { + var series = this.series, + reversedStacks = pick(this.options.reversedStacks, true), + i = series.length; + if (!this.isXAxis) { + this.usePercentage = false; + while (i--) { + series[reversedStacks ? i : series.length - i - 1].setStackedPoints(); + } + // Loop up again to compute percent stack + if (this.usePercentage) { + for (i = 0; i < series.length; i++) { + series[i].setPercentStacks(); + } + } + } +}; + +Axis.prototype.renderStackTotals = function () { + var axis = this, + chart = axis.chart, + renderer = chart.renderer, + stacks = axis.stacks, + stackKey, + oneStack, + stackCategory, + stackTotalGroup = axis.stackTotalGroup; + + // Create a separate group for the stack total labels + if (!stackTotalGroup) { + axis.stackTotalGroup = stackTotalGroup = + renderer.g('stack-labels') + .attr({ + visibility: VISIBLE, + zIndex: 6 + }) + .add(); + } + + // plotLeft/Top will change when y axis gets wider so we need to translate the + // stackTotalGroup at every render call. See bug #506 and #516 + stackTotalGroup.translate(chart.plotLeft, chart.plotTop); + + // Render each stack total + for (stackKey in stacks) { + oneStack = stacks[stackKey]; + for (stackCategory in oneStack) { + oneStack[stackCategory].render(stackTotalGroup); + } + } +}; + + +// Stacking methods defnied for Series prototype + +/** + * Adds series' points value to corresponding stack + */ +Series.prototype.setStackedPoints = function () { + if (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) { + return; + } + + var series = this, + xData = series.processedXData, + yData = series.processedYData, + stackedYData = [], + yDataLength = yData.length, + seriesOptions = series.options, + threshold = seriesOptions.threshold, + stackOption = seriesOptions.stack, + stacking = seriesOptions.stacking, + stackKey = series.stackKey, + negKey = '-' + stackKey, + negStacks = series.negStacks, + yAxis = series.yAxis, + stacks = yAxis.stacks, + oldStacks = yAxis.oldStacks, + isNegative, + stack, + other, + key, + pointKey, + i, + x, + y; + + // loop over the non-null y values and read them into a local array + for (i = 0; i < yDataLength; i++) { + x = xData[i]; + y = yData[i]; + pointKey = series.index + ',' + i; + + // Read stacked values into a stack based on the x value, + // the sign of y and the stack key. Stacking is also handled for null values (#739) + isNegative = negStacks && y < threshold; + key = isNegative ? negKey : stackKey; + + // Create empty object for this stack if it doesn't exist yet + if (!stacks[key]) { + stacks[key] = {}; + } + + // Initialize StackItem for this x + if (!stacks[key][x]) { + if (oldStacks[key] && oldStacks[key][x]) { + stacks[key][x] = oldStacks[key][x]; + stacks[key][x].total = null; + } else { + stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption); + } + } + + // If the StackItem doesn't exist, create it first + stack = stacks[key][x]; + stack.points[pointKey] = [stack.cum || 0]; + + // Add value to the stack total + if (stacking === 'percent') { + + // Percent stacked column, totals are the same for the positive and negative stacks + other = isNegative ? stackKey : negKey; + if (negStacks && stacks[other] && stacks[other][x]) { + other = stacks[other][x]; + stack.total = other.total = mathMax(other.total, stack.total) + mathAbs(y) || 0; + + // Percent stacked areas + } else { + stack.total = correctFloat(stack.total + (mathAbs(y) || 0)); + } + } else { + stack.total = correctFloat(stack.total + (y || 0)); + } + + stack.cum = (stack.cum || 0) + (y || 0); + + stack.points[pointKey].push(stack.cum); + stackedYData[i] = stack.cum; + + } + + if (stacking === 'percent') { + yAxis.usePercentage = true; + } + + this.stackedYData = stackedYData; // To be used in getExtremes + + // Reset old stacks + yAxis.oldStacks = {}; +}; + +/** + * Iterate over all stacks and compute the absolute values to percent + */ +Series.prototype.setPercentStacks = function () { + var series = this, + stackKey = series.stackKey, + stacks = series.yAxis.stacks, + processedXData = series.processedXData; + + each([stackKey, '-' + stackKey], function (key) { + var i = processedXData.length, + x, + stack, + pointExtremes, + totalFactor; + + while (i--) { + x = processedXData[i]; + stack = stacks[key] && stacks[key][x]; + pointExtremes = stack && stack.points[series.index + ',' + i]; + if (pointExtremes) { + totalFactor = stack.total ? 100 / stack.total : 0; + pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y bottom value + pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor); // Y value + series.stackedYData[i] = pointExtremes[1]; + } + } + }); +}; + +// Extend the Chart prototype for dynamic methods +extend(Chart.prototype, { + + /** + * Add a series dynamically after time + * + * @param {Object} options The config options + * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true. + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + * + * @return {Object} series The newly created series object + */ + addSeries: function (options, redraw, animation) { + var series, + chart = this; + + if (options) { + redraw = pick(redraw, true); // defaults to true + + fireEvent(chart, 'addSeries', { options: options }, function () { + series = chart.initSeries(options); + + chart.isDirtyLegend = true; // the series array is out of sync with the display + chart.linkSeries(); + if (redraw) { + chart.redraw(animation); + } + }); + } + + return series; + }, + + /** + * Add an axis to the chart + * @param {Object} options The axis option + * @param {Boolean} isX Whether it is an X axis or a value axis + */ + addAxis: function (options, isX, redraw, animation) { + var key = isX ? 'xAxis' : 'yAxis', + chartOptions = this.options, + axis; + + /*jslint unused: false*/ + axis = new Axis(this, merge(options, { + index: this[key].length, + isX: isX + })); + /*jslint unused: true*/ + + // Push the new axis options to the chart options + chartOptions[key] = splat(chartOptions[key] || {}); + chartOptions[key].push(options); + + if (pick(redraw, true)) { + this.redraw(animation); + } + }, + + /** + * Dim the chart and show a loading text or symbol + * @param {String} str An optional text to show in the loading label instead of the default one + */ + showLoading: function (str) { + var chart = this, + options = chart.options, + loadingDiv = chart.loadingDiv; + + var loadingOptions = options.loading; + + // create the layer at the first call + if (!loadingDiv) { + chart.loadingDiv = loadingDiv = createElement(DIV, { + className: PREFIX + 'loading' + }, extend(loadingOptions.style, { + zIndex: 10, + display: NONE + }), chart.container); + + chart.loadingSpan = createElement( + 'span', + null, + loadingOptions.labelStyle, + loadingDiv + ); + + } + + // update text + chart.loadingSpan.innerHTML = str || options.lang.loading; + + // show it + if (!chart.loadingShown) { + css(loadingDiv, { + opacity: 0, + display: '', + left: chart.plotLeft + PX, + top: chart.plotTop + PX, + width: chart.plotWidth + PX, + height: chart.plotHeight + PX + }); + animate(loadingDiv, { + opacity: loadingOptions.style.opacity + }, { + duration: loadingOptions.showDuration || 0 + }); + chart.loadingShown = true; + } + }, + + /** + * Hide the loading layer + */ + hideLoading: function () { + var options = this.options, + loadingDiv = this.loadingDiv; + + if (loadingDiv) { + animate(loadingDiv, { + opacity: 0 + }, { + duration: options.loading.hideDuration || 100, + complete: function () { + css(loadingDiv, { display: NONE }); + } + }); + } + this.loadingShown = false; + } +}); + +// extend the Point prototype for dynamic methods +extend(Point.prototype, { + /** + * Update the point with new options (typically x/y data) and optionally redraw the series. + * + * @param {Object} options Point options as defined in the series.data array + * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + * + */ + update: function (options, redraw, animation) { + var point = this, + series = point.series, + graphic = point.graphic, + i, + data = series.data, + chart = series.chart, + seriesOptions = series.options; + + redraw = pick(redraw, true); + + // fire the event with a default handler of doing the update + point.firePointEvent('update', { options: options }, function () { + + point.applyOptions(options); + + // update visuals + if (isObject(options)) { + series.getAttribs(); + if (graphic) { + if (options && options.marker && options.marker.symbol) { + point.graphic = graphic.destroy(); + } else { + graphic.attr(point.pointAttr[point.state || '']); + } + } + if (options && options.dataLabels && point.dataLabel) { // #2468 + point.dataLabel = point.dataLabel.destroy(); + } + } + + // record changes in the parallel arrays + i = inArray(point, data); + series.updateParallelArrays(point, i); + + seriesOptions.data[i] = point.options; + + // redraw + series.isDirty = series.isDirtyData = true; + if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320 + chart.isDirtyBox = true; + } + + if (seriesOptions.legendType === 'point') { // #1831, #1885 + chart.legend.destroyItem(point); + } + if (redraw) { + chart.redraw(animation); + } + }); + }, + + /** + * Remove a point and optionally redraw the series and if necessary the axes + * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + */ + remove: function (redraw, animation) { + var point = this, + series = point.series, + points = series.points, + chart = series.chart, + i, + data = series.data; + + setAnimation(animation, chart); + redraw = pick(redraw, true); + + // fire the event with a default handler of removing the point + point.firePointEvent('remove', null, function () { + + // splice all the parallel arrays + i = inArray(point, data); + if (data.length === points.length) { + points.splice(i, 1); + } + data.splice(i, 1); + series.options.data.splice(i, 1); + series.updateParallelArrays(point, 'splice', i, 1); + + point.destroy(); + + // redraw + series.isDirty = true; + series.isDirtyData = true; + if (redraw) { + chart.redraw(); + } + }); + } +}); + +// Extend the series prototype for dynamic methods +extend(Series.prototype, { + /** + * Add a point dynamically after chart load time + * @param {Object} options Point options as given in series.data + * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call + * @param {Boolean} shift If shift is true, a point is shifted off the start + * of the series as one is appended to the end. + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + */ + addPoint: function (options, redraw, shift, animation) { + var series = this, + seriesOptions = series.options, + data = series.data, + graph = series.graph, + area = series.area, + chart = series.chart, + names = series.xAxis && series.xAxis.names, + currentShift = (graph && graph.shift) || 0, + dataOptions = seriesOptions.data, + point, + isInTheMiddle, + xData = series.xData, + x, + i; + + setAnimation(animation, chart); + + // Make graph animate sideways + if (shift) { + each([graph, area, series.graphNeg, series.areaNeg], function (shape) { + if (shape) { + shape.shift = currentShift + 1; + } + }); + } + if (area) { + area.isArea = true; // needed in animation, both with and without shift + } + + // Optional redraw, defaults to true + redraw = pick(redraw, true); + + // Get options and push the point to xData, yData and series.options. In series.generatePoints + // the Point instance will be created on demand and pushed to the series.data array. + point = { series: series }; + series.pointClass.prototype.applyOptions.apply(point, [options]); + x = point.x; + + // Get the insertion point + i = xData.length; + if (series.requireSorting && x < xData[i - 1]) { + isInTheMiddle = true; + while (i && xData[i - 1] > x) { + i--; + } + } + + series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item + series.updateParallelArrays(point, i); // update it + + if (names) { + names[x] = point.name; + } + dataOptions.splice(i, 0, options); + + if (isInTheMiddle) { + series.data.splice(i, 0, null); + series.processData(); + } + + // Generate points to be added to the legend (#1329) + if (seriesOptions.legendType === 'point') { + series.generatePoints(); + } + + // Shift the first point off the parallel arrays + // todo: consider series.removePoint(i) method + if (shift) { + if (data[0] && data[0].remove) { + data[0].remove(false); + } else { + data.shift(); + series.updateParallelArrays(point, 'shift'); + + dataOptions.shift(); + } + } + + // redraw + series.isDirty = true; + series.isDirtyData = true; + if (redraw) { + series.getAttribs(); // #1937 + chart.redraw(); + } + }, + + /** + * Remove a series and optionally redraw the chart + * + * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + */ + + remove: function (redraw, animation) { + var series = this, + chart = series.chart; + redraw = pick(redraw, true); + + if (!series.isRemoving) { /* prevent triggering native event in jQuery + (calling the remove function from the remove event) */ + series.isRemoving = true; + + // fire the event with a default handler of removing the point + fireEvent(series, 'remove', null, function () { + + + // destroy elements + series.destroy(); + + + // redraw + chart.isDirtyLegend = chart.isDirtyBox = true; + chart.linkSeries(); + + if (redraw) { + chart.redraw(animation); + } + }); + + } + series.isRemoving = false; + }, + + /** + * Update the series with a new set of options + */ + update: function (newOptions, redraw) { + var chart = this.chart, + // must use user options when changing type because this.options is merged + // in with type specific plotOptions + oldOptions = this.userOptions, + oldType = this.type, + proto = seriesTypes[oldType].prototype, + n; + + // Do the merge, with some forced options + newOptions = merge(oldOptions, { + animation: false, + index: this.index, + pointStart: this.xData[0] // when updating after addPoint + }, { data: this.options.data }, newOptions); + + // Destroy the series and reinsert methods from the type prototype + this.remove(false); + for (n in proto) { // Overwrite series-type specific methods (#2270) + if (proto.hasOwnProperty(n)) { + this[n] = UNDEFINED; + } + } + extend(this, seriesTypes[newOptions.type || oldType].prototype); + + + this.init(chart, newOptions); + if (pick(redraw, true)) { + chart.redraw(false); + } + } +}); + +// Extend the Axis.prototype for dynamic methods +extend(Axis.prototype, { + + /** + * Update the axis with a new options structure + */ + update: function (newOptions, redraw) { + var chart = this.chart; + + newOptions = chart.options[this.coll][this.options.index] = merge(this.userOptions, newOptions); + + this.destroy(true); + this._addedPlotLB = UNDEFINED; // #1611, #2887 + + this.init(chart, extend(newOptions, { events: UNDEFINED })); + + chart.isDirtyBox = true; + if (pick(redraw, true)) { + chart.redraw(); + } + }, + + /** + * Remove the axis from the chart + */ + remove: function (redraw) { + var chart = this.chart, + key = this.coll, // xAxis or yAxis + axisSeries = this.series, + i = axisSeries.length; + + // Remove associated series (#2687) + while (i--) { + if (axisSeries[i]) { + axisSeries[i].remove(false); + } + } + + // Remove the axis + erase(chart.axes, this); + erase(chart[key], this); + chart.options[key].splice(this.options.index, 1); + each(chart[key], function (axis, i) { // Re-index, #1706 + axis.options.index = i; + }); + this.destroy(); + chart.isDirtyBox = true; + + if (pick(redraw, true)) { + chart.redraw(); + } + }, + + /** + * Update the axis title by options + */ + setTitle: function (newTitleOptions, redraw) { + this.update({ title: newTitleOptions }, redraw); + }, + + /** + * Set new axis categories and optionally redraw + * @param {Array} categories + * @param {Boolean} redraw + */ + setCategories: function (categories, redraw) { + this.update({ categories: categories }, redraw); + } + +}); + + +/** + * LineSeries object + */ +var LineSeries = extendClass(Series); +seriesTypes.line = LineSeries; + +/** + * Set the default options for area + */ +defaultPlotOptions.area = merge(defaultSeriesOptions, { + threshold: 0 + // trackByArea: false, + // lineColor: null, // overrides color, but lets fillColor be unaltered + // fillOpacity: 0.75, + // fillColor: null +}); + +/** + * AreaSeries object + */ +var AreaSeries = extendClass(Series, { + type: 'area', + /** + * For stacks, don't split segments on null values. Instead, draw null values with + * no marker. Also insert dummy points for any X position that exists in other series + * in the stack. + */ + getSegments: function () { + var segments = [], + segment = [], + keys = [], + xAxis = this.xAxis, + yAxis = this.yAxis, + stack = yAxis.stacks[this.stackKey], + pointMap = {}, + plotX, + plotY, + points = this.points, + connectNulls = this.options.connectNulls, + val, + i, + x; + + if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue + // Create a map where we can quickly look up the points by their X value. + for (i = 0; i < points.length; i++) { + pointMap[points[i].x] = points[i]; + } + + // Sort the keys (#1651) + for (x in stack) { + if (stack[x].total !== null) { // nulled after switching between grouping and not (#1651, #2336) + keys.push(+x); + } + } + keys.sort(function (a, b) { + return a - b; + }); + + each(keys, function (x) { + if (connectNulls && (!pointMap[x] || pointMap[x].y === null)) { // #1836 + return; + + // The point exists, push it to the segment + } else if (pointMap[x]) { + segment.push(pointMap[x]); + + // There is no point for this X value in this series, so we + // insert a dummy point in order for the areas to be drawn + // correctly. + } else { + plotX = xAxis.translate(x); + val = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991 + plotY = yAxis.toPixels(val, true); + segment.push({ + y: null, + plotX: plotX, + clientX: plotX, + plotY: plotY, + yBottom: plotY, + onMouseOver: noop + }); + } + }); + + if (segment.length) { + segments.push(segment); + } + + } else { + Series.prototype.getSegments.call(this); + segments = this.segments; + } + + this.segments = segments; + }, + + /** + * Extend the base Series getSegmentPath method by adding the path for the area. + * This path is pushed to the series.areaPath property. + */ + getSegmentPath: function (segment) { + + var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method + areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path + i, + options = this.options, + segLength = segmentPath.length, + translatedThreshold = this.yAxis.getThreshold(options.threshold), // #2181 + yBottom; + + if (segLength === 3) { // for animation from 1 to two points + areaSegmentPath.push(L, segmentPath[1], segmentPath[2]); + } + if (options.stacking && !this.closedStacks) { + + // Follow stack back. Todo: implement areaspline. A general solution could be to + // reverse the entire graphPath of the previous series, though may be hard with + // splines and with series with different extremes + for (i = segment.length - 1; i >= 0; i--) { + + yBottom = pick(segment[i].yBottom, translatedThreshold); + + // step line? + if (i < segment.length - 1 && options.step) { + areaSegmentPath.push(segment[i + 1].plotX, yBottom); + } + + areaSegmentPath.push(segment[i].plotX, yBottom); + } + + } else { // follow zero line back + this.closeSegment(areaSegmentPath, segment, translatedThreshold); + } + this.areaPath = this.areaPath.concat(areaSegmentPath); + return segmentPath; + }, + + /** + * Extendable method to close the segment path of an area. This is overridden in polar + * charts. + */ + closeSegment: function (path, segment, translatedThreshold) { + path.push( + L, + segment[segment.length - 1].plotX, + translatedThreshold, + L, + segment[0].plotX, + translatedThreshold + ); + }, + + /** + * Draw the graph and the underlying area. This method calls the Series base + * function and adds the area. The areaPath is calculated in the getSegmentPath + * method called from Series.prototype.drawGraph. + */ + drawGraph: function () { + + // Define or reset areaPath + this.areaPath = []; + + // Call the base method + Series.prototype.drawGraph.apply(this); + + // Define local variables + var series = this, + areaPath = this.areaPath, + options = this.options, + negativeColor = options.negativeColor, + negativeFillColor = options.negativeFillColor, + props = [['area', this.color, options.fillColor]]; // area name, main color, fill color + + if (negativeColor || negativeFillColor) { + props.push(['areaNeg', negativeColor, negativeFillColor]); + } + + each(props, function (prop) { + var areaKey = prop[0], + area = series[areaKey]; + + // Create or update the area + if (area) { // update + area.animate({ d: areaPath }); + + } else { // create + series[areaKey] = series.chart.renderer.path(areaPath) + .attr({ + fill: pick( + prop[2], + Color(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get() + ), + zIndex: 0 // #1069 + }).add(series.group); + } + }); + }, + + drawLegendSymbol: LegendSymbolMixin.drawRectangle +}); + +seriesTypes.area = AreaSeries; +/** + * Set the default options for spline + */ +defaultPlotOptions.spline = merge(defaultSeriesOptions); + +/** + * SplineSeries object + */ +var SplineSeries = extendClass(Series, { + type: 'spline', + + /** + * Get the spline segment from a given point's previous neighbour to the given point + */ + getPointSpline: function (segment, point, i) { + var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc + denom = smoothing + 1, + plotX = point.plotX, + plotY = point.plotY, + lastPoint = segment[i - 1], + nextPoint = segment[i + 1], + leftContX, + leftContY, + rightContX, + rightContY, + ret; + + // find control points + if (lastPoint && nextPoint) { + + var lastX = lastPoint.plotX, + lastY = lastPoint.plotY, + nextX = nextPoint.plotX, + nextY = nextPoint.plotY, + correction; + + leftContX = (smoothing * plotX + lastX) / denom; + leftContY = (smoothing * plotY + lastY) / denom; + rightContX = (smoothing * plotX + nextX) / denom; + rightContY = (smoothing * plotY + nextY) / denom; + + // have the two control points make a straight line through main point + correction = ((rightContY - leftContY) * (rightContX - plotX)) / + (rightContX - leftContX) + plotY - rightContY; + + leftContY += correction; + rightContY += correction; + + // to prevent false extremes, check that control points are between + // neighbouring points' y values + if (leftContY > lastY && leftContY > plotY) { + leftContY = mathMax(lastY, plotY); + rightContY = 2 * plotY - leftContY; // mirror of left control point + } else if (leftContY < lastY && leftContY < plotY) { + leftContY = mathMin(lastY, plotY); + rightContY = 2 * plotY - leftContY; + } + if (rightContY > nextY && rightContY > plotY) { + rightContY = mathMax(nextY, plotY); + leftContY = 2 * plotY - rightContY; + } else if (rightContY < nextY && rightContY < plotY) { + rightContY = mathMin(nextY, plotY); + leftContY = 2 * plotY - rightContY; + } + + // record for drawing in next point + point.rightContX = rightContX; + point.rightContY = rightContY; + + } + + // Visualize control points for debugging + /* + if (leftContX) { + this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2) + .attr({ + stroke: 'red', + 'stroke-width': 1, + fill: 'none' + }) + .add(); + this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, + 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) + .attr({ + stroke: 'red', + 'stroke-width': 1 + }) + .add(); + this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2) + .attr({ + stroke: 'green', + 'stroke-width': 1, + fill: 'none' + }) + .add(); + this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, + 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) + .attr({ + stroke: 'green', + 'stroke-width': 1 + }) + .add(); + } + */ + + // moveTo or lineTo + if (!i) { + ret = [M, plotX, plotY]; + } else { // curve from last point to this + ret = [ + 'C', + lastPoint.rightContX || lastPoint.plotX, + lastPoint.rightContY || lastPoint.plotY, + leftContX || plotX, + leftContY || plotY, + plotX, + plotY + ]; + lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later + } + return ret; + } +}); +seriesTypes.spline = SplineSeries; + +/** + * Set the default options for areaspline + */ +defaultPlotOptions.areaspline = merge(defaultPlotOptions.area); + +/** + * AreaSplineSeries object + */ +var areaProto = AreaSeries.prototype, + AreaSplineSeries = extendClass(SplineSeries, { + type: 'areaspline', + closedStacks: true, // instead of following the previous graph back, follow the threshold back + + // Mix in methods from the area series + getSegmentPath: areaProto.getSegmentPath, + closeSegment: areaProto.closeSegment, + drawGraph: areaProto.drawGraph, + drawLegendSymbol: LegendSymbolMixin.drawRectangle + }); + +seriesTypes.areaspline = AreaSplineSeries; + +/** + * Set the default options for column + */ +defaultPlotOptions.column = merge(defaultSeriesOptions, { + borderColor: '#FFFFFF', + //borderWidth: 1, + borderRadius: 0, + //colorByPoint: undefined, + groupPadding: 0.2, + //grouping: true, + marker: null, // point options are specified in the base options + pointPadding: 0.1, + //pointWidth: null, + minPointLength: 0, + cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes + pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories + states: { + hover: { + brightness: 0.1, + shadow: false, + halo: false + }, + select: { + color: '#C0C0C0', + borderColor: '#000000', + shadow: false + } + }, + dataLabels: { + align: null, // auto + verticalAlign: null, // auto + y: null + }, + stickyTracking: false, + tooltip: { + distance: 6 + }, + threshold: 0 +}); + +/** + * ColumnSeries object + */ +var ColumnSeries = extendClass(Series, { + type: 'column', + pointAttrToOptions: { // mapping between SVG attributes and the corresponding options + stroke: 'borderColor', + fill: 'color', + r: 'borderRadius' + }, + cropShoulder: 0, + trackerGroups: ['group', 'dataLabelsGroup'], + negStacks: true, // use separate negative stacks, unlike area stacks where a negative + // point is substracted from previous (#1910) + + /** + * Initialize the series + */ + init: function () { + Series.prototype.init.apply(this, arguments); + + var series = this, + chart = series.chart; + + // if the series is added dynamically, force redraw of other + // series affected by a new column + if (chart.hasRendered) { + each(chart.series, function (otherSeries) { + if (otherSeries.type === series.type) { + otherSeries.isDirty = true; + } + }); + } + }, + + /** + * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding, + * pointWidth etc. + */ + getColumnMetrics: function () { + + var series = this, + options = series.options, + xAxis = series.xAxis, + yAxis = series.yAxis, + reversedXAxis = xAxis.reversed, + stackKey, + stackGroups = {}, + columnIndex, + columnCount = 0; + + // Get the total number of column type series. + // This is called on every series. Consider moving this logic to a + // chart.orderStacks() function and call it on init, addSeries and removeSeries + if (options.grouping === false) { + columnCount = 1; + } else { + each(series.chart.series, function (otherSeries) { + var otherOptions = otherSeries.options, + otherYAxis = otherSeries.yAxis; + if (otherSeries.type === series.type && otherSeries.visible && + yAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) { // #642, #2086 + if (otherOptions.stacking) { + stackKey = otherSeries.stackKey; + if (stackGroups[stackKey] === UNDEFINED) { + stackGroups[stackKey] = columnCount++; + } + columnIndex = stackGroups[stackKey]; + } else if (otherOptions.grouping !== false) { // #1162 + columnIndex = columnCount++; + } + otherSeries.columnIndex = columnIndex; + } + }); + } + + var categoryWidth = mathMin( + mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || xAxis.tickInterval || 1), // #2610 + xAxis.len // #1535 + ), + groupPadding = categoryWidth * options.groupPadding, + groupWidth = categoryWidth - 2 * groupPadding, + pointOffsetWidth = groupWidth / columnCount, + optionPointWidth = options.pointWidth, + pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 : + pointOffsetWidth * options.pointPadding, + pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts + colIndex = (reversedXAxis ? + columnCount - (series.columnIndex || 0) : // #1251 + series.columnIndex) || 0, + pointXOffset = pointPadding + (groupPadding + colIndex * + pointOffsetWidth - (categoryWidth / 2)) * + (reversedXAxis ? -1 : 1); + + // Save it for reading in linked series (Error bars particularly) + return (series.columnMetrics = { + width: pointWidth, + offset: pointXOffset + }); + + }, + + /** + * Translate each point to the plot area coordinate system and find shape positions + */ + translate: function () { + var series = this, + chart = series.chart, + options = series.options, + borderWidth = series.borderWidth = pick( + options.borderWidth, + series.activePointCount > 0.5 * series.xAxis.len ? 0 : 1 + ), + yAxis = series.yAxis, + threshold = options.threshold, + translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold), + minPointLength = pick(options.minPointLength, 5), + metrics = series.getColumnMetrics(), + pointWidth = metrics.width, + seriesBarW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width + pointXOffset = series.pointXOffset = metrics.offset, + xCrisp = -(borderWidth % 2 ? 0.5 : 0), + yCrisp = borderWidth % 2 ? 0.5 : 1; + + if (chart.renderer.isVML && chart.inverted) { + yCrisp += 1; + } + + Series.prototype.translate.apply(series); + + // record the new values + each(series.points, function (point) { + var yBottom = pick(point.yBottom, translatedThreshold), + plotY = mathMin(mathMax(-999 - yBottom, point.plotY), yAxis.len + 999 + yBottom), // Don't draw too far outside plot area (#1303, #2241) + barX = point.plotX + pointXOffset, + barW = seriesBarW, + barY = mathMin(plotY, yBottom), + right, + bottom, + fromTop, + fromLeft, + barH = mathMax(plotY, yBottom) - barY; + + // Handle options.minPointLength + if (mathAbs(barH) < minPointLength) { + if (minPointLength) { + barH = minPointLength; + barY = + mathRound(mathAbs(barY - translatedThreshold) > minPointLength ? // stacked + yBottom - minPointLength : // keep position + translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0)); // use exact yAxis.translation (#1485) + } + } + + // Cache for access in polar + point.barX = barX; + point.pointWidth = pointWidth; + + // Fix the tooltip on center of grouped columns (#1216) + point.tooltipPos = chart.inverted ? [yAxis.len - plotY, series.xAxis.len - barX - barW / 2] : [barX + barW / 2, plotY]; + + // Round off to obtain crisp edges + fromLeft = mathAbs(barX) < 0.5; + right = mathRound(barX + barW) + xCrisp; + barX = mathRound(barX) + xCrisp; + barW = right - barX; + + fromTop = mathAbs(barY) < 0.5; + bottom = mathRound(barY + barH) + yCrisp; + barY = mathRound(barY) + yCrisp; + barH = bottom - barY; + + // Top and left edges are exceptions + if (fromLeft) { + barX += 1; + barW -= 1; + } + if (fromTop) { + barY -= 1; + barH += 1; + } + + // Register shape type and arguments to be used in drawPoints + point.shapeType = 'rect'; + point.shapeArgs = { + x: barX, + y: barY, + width: barW, + height: barH + }; + }); + + }, + + getSymbol: noop, + + /** + * Use a solid rectangle like the area series types + */ + drawLegendSymbol: LegendSymbolMixin.drawRectangle, + + + /** + * Columns have no graph + */ + drawGraph: noop, + + /** + * Draw the columns. For bars, the series.group is rotated, so the same coordinates + * apply for columns and bars. This method is inherited by scatter series. + * + */ + drawPoints: function () { + var series = this, + chart = this.chart, + options = series.options, + renderer = chart.renderer, + animationLimit = options.animationLimit || 250, + shapeArgs, + pointAttr, + borderAttr; + + // draw the columns + each(series.points, function (point) { + var plotY = point.plotY, + graphic = point.graphic; + + if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { + shapeArgs = point.shapeArgs; + borderAttr = defined(series.borderWidth) ? { + 'stroke-width': series.borderWidth + } : {}; + pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || series.pointAttr[NORMAL_STATE]; + if (graphic) { // update + stop(graphic); + graphic.attr(borderAttr)[chart.pointCount < animationLimit ? 'animate' : 'attr'](merge(shapeArgs)); + + } else { + point.graphic = graphic = renderer[point.shapeType](shapeArgs) + .attr(pointAttr) + .attr(borderAttr) + .add(series.group) + .shadow(options.shadow, null, options.stacking && !options.borderRadius); + } + + } else if (graphic) { + point.graphic = graphic.destroy(); // #1269 + } + }); + }, + + /** + * Animate the column heights one by one from zero + * @param {Boolean} init Whether to initialize the animation or run it + */ + animate: function (init) { + var series = this, + yAxis = this.yAxis, + options = series.options, + inverted = this.chart.inverted, + attr = {}, + translatedThreshold; + + if (hasSVG) { // VML is too slow anyway + if (init) { + attr.scaleY = 0.001; + translatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold))); + if (inverted) { + attr.translateX = translatedThreshold - yAxis.len; + } else { + attr.translateY = translatedThreshold; + } + series.group.attr(attr); + + } else { // run the animation + + attr.scaleY = 1; + attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos; + series.group.animate(attr, series.options.animation); + + // delete this function to allow it only once + series.animate = null; + } + } + }, + + /** + * Remove this series from the chart + */ + remove: function () { + var series = this, + chart = series.chart; + + // column and bar series affects other series of the same type + // as they are either stacked or grouped + if (chart.hasRendered) { + each(chart.series, function (otherSeries) { + if (otherSeries.type === series.type) { + otherSeries.isDirty = true; + } + }); + } + + Series.prototype.remove.apply(series, arguments); + } +}); +seriesTypes.column = ColumnSeries; +/** + * Set the default options for bar + */ +defaultPlotOptions.bar = merge(defaultPlotOptions.column); +/** + * The Bar series class + */ +var BarSeries = extendClass(ColumnSeries, { + type: 'bar', + inverted: true +}); +seriesTypes.bar = BarSeries; + +/** + * Set the default options for scatter + */ +defaultPlotOptions.scatter = merge(defaultSeriesOptions, { + lineWidth: 0, + tooltip: { + headerFormat: '\u25CF {series.name}
', // docs + pointFormat: 'x: {point.x}
y: {point.y}
' + }, + stickyTracking: false +}); + +/** + * The scatter series class + */ +var ScatterSeries = extendClass(Series, { + type: 'scatter', + sorted: false, + requireSorting: false, + noSharedTooltip: true, + trackerGroups: ['markerGroup'], + takeOrdinalPosition: false, // #2342 + singularTooltips: true, + drawGraph: function () { + if (this.options.lineWidth) { + Series.prototype.drawGraph.call(this); + } + } +}); + +seriesTypes.scatter = ScatterSeries; + +/** + * Set the default options for pie + */ +defaultPlotOptions.pie = merge(defaultSeriesOptions, { + borderColor: '#FFFFFF', + borderWidth: 1, + center: [null, null], + clip: false, + colorByPoint: true, // always true for pies + dataLabels: { + // align: null, + // connectorWidth: 1, + // connectorColor: point.color, + // connectorPadding: 5, + distance: 30, + enabled: true, + formatter: function () { + return this.point.name; + } + // softConnector: true, + //y: 0 + }, + ignoreHiddenPoint: true, + //innerSize: 0, + legendType: 'point', + marker: null, // point options are specified in the base options + size: null, + showInLegend: false, + slicedOffset: 10, + states: { + hover: { + brightness: 0.1, + shadow: false + } + }, + stickyTracking: false, + tooltip: { + followPointer: true + } +}); + +/** + * Extended point object for pies + */ +var PiePoint = extendClass(Point, { + /** + * Initiate the pie slice + */ + init: function () { + + Point.prototype.init.apply(this, arguments); + + var point = this, + toggleSlice; + + // Disallow negative values (#1530) + if (point.y < 0) { + point.y = null; + } + + //visible: options.visible !== false, + extend(point, { + visible: point.visible !== false, + name: pick(point.name, 'Slice') + }); + + // add event listener for select + toggleSlice = function (e) { + point.slice(e.type === 'select'); + }; + addEvent(point, 'select', toggleSlice); + addEvent(point, 'unselect', toggleSlice); + + return point; + }, + + /** + * Toggle the visibility of the pie slice + * @param {Boolean} vis Whether to show the slice or not. If undefined, the + * visibility is toggled + */ + setVisible: function (vis) { + var point = this, + series = point.series, + chart = series.chart; + + // if called without an argument, toggle visibility + point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis; + series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data + + // Show and hide associated elements + each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) { + if (point[key]) { + point[key][vis ? 'show' : 'hide'](true); + } + }); + + if (point.legendItem) { + chart.legend.colorizeItem(point, vis); + } + + // Handle ignore hidden slices + if (!series.isDirty && series.options.ignoreHiddenPoint) { + series.isDirty = true; + chart.redraw(); + } + }, + + /** + * Set or toggle whether the slice is cut out from the pie + * @param {Boolean} sliced When undefined, the slice state is toggled + * @param {Boolean} redraw Whether to redraw the chart. True by default. + */ + slice: function (sliced, redraw, animation) { + var point = this, + series = point.series, + chart = series.chart, + translation; + + setAnimation(animation, chart); + + // redraw is true by default + redraw = pick(redraw, true); + + // if called without an argument, toggle + point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced; + series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data + + translation = sliced ? point.slicedTranslation : { + translateX: 0, + translateY: 0 + }; + + point.graphic.animate(translation); + + if (point.shadowGroup) { + point.shadowGroup.animate(translation); + } + + }, + + haloPath: function (size) { + var shapeArgs = this.shapeArgs, + chart = this.series.chart; + + return this.series.chart.renderer.symbols.arc(chart.plotLeft + shapeArgs.x, chart.plotTop + shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, { + innerR: this.shapeArgs.r, + start: shapeArgs.start, + end: shapeArgs.end + }); + } +}); + +/** + * The Pie series class + */ +var PieSeries = { + type: 'pie', + isCartesian: false, + pointClass: PiePoint, + requireSorting: false, + noSharedTooltip: true, + trackerGroups: ['group', 'dataLabelsGroup'], + axisTypes: [], + pointAttrToOptions: { // mapping between SVG attributes and the corresponding options + stroke: 'borderColor', + 'stroke-width': 'borderWidth', + fill: 'color' + }, + singularTooltips: true, + + /** + * Pies have one color each point + */ + getColor: noop, + + /** + * Animate the pies in + */ + animate: function (init) { + var series = this, + points = series.points, + startAngleRad = series.startAngleRad; + + if (!init) { + each(points, function (point) { + var graphic = point.graphic, + args = point.shapeArgs; + + if (graphic) { + // start values + graphic.attr({ + r: series.center[3] / 2, // animate from inner radius (#779) + start: startAngleRad, + end: startAngleRad + }); + + // animate + graphic.animate({ + r: args.r, + start: args.start, + end: args.end + }, series.options.animation); + } + }); + + // delete this function to allow it only once + series.animate = null; + } + }, + + /** + * Extend the basic setData method by running processData and generatePoints immediately, + * in order to access the points from the legend. + */ + setData: function (data, redraw, animation, updatePoints) { + Series.prototype.setData.call(this, data, false, animation, updatePoints); + this.processData(); + this.generatePoints(); + if (pick(redraw, true)) { + this.chart.redraw(animation); + } + }, + + /** + * Extend the generatePoints method by adding total and percentage properties to each point + */ + generatePoints: function () { + var i, + total = 0, + points, + len, + point, + ignoreHiddenPoint = this.options.ignoreHiddenPoint; + + Series.prototype.generatePoints.call(this); + + // Populate local vars + points = this.points; + len = points.length; + + // Get the total sum + for (i = 0; i < len; i++) { + point = points[i]; + total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y; + } + this.total = total; + + // Set each point's properties + for (i = 0; i < len; i++) { + point = points[i]; + point.percentage = total > 0 ? (point.y / total) * 100 : 0; + point.total = total; + } + + }, + + /** + * Do translation for pie slices + */ + translate: function (positions) { + this.generatePoints(); + + var series = this, + cumulative = 0, + precision = 1000, // issue #172 + options = series.options, + slicedOffset = options.slicedOffset, + connectorOffset = slicedOffset + options.borderWidth, + start, + end, + angle, + startAngle = options.startAngle || 0, + startAngleRad = series.startAngleRad = mathPI / 180 * (startAngle - 90), + endAngleRad = series.endAngleRad = mathPI / 180 * ((pick(options.endAngle, startAngle + 360)) - 90), + circ = endAngleRad - startAngleRad, //2 * mathPI, + points = series.points, + radiusX, // the x component of the radius vector for a given point + radiusY, + labelDistance = options.dataLabels.distance, + ignoreHiddenPoint = options.ignoreHiddenPoint, + i, + len = points.length, + point; + + // Get positions - either an integer or a percentage string must be given. + // If positions are passed as a parameter, we're in a recursive loop for adjusting + // space for data labels. + if (!positions) { + series.center = positions = series.getCenter(); + } + + // utility for getting the x value from a given y, used for anticollision logic in data labels + series.getX = function (y, left) { + + angle = math.asin(mathMin((y - positions[1]) / (positions[2] / 2 + labelDistance), 1)); + + return positions[0] + + (left ? -1 : 1) * + (mathCos(angle) * (positions[2] / 2 + labelDistance)); + }; + + // Calculate the geometry for each point + for (i = 0; i < len; i++) { + + point = points[i]; + + // set start and end angle + start = startAngleRad + (cumulative * circ); + if (!ignoreHiddenPoint || point.visible) { + cumulative += point.percentage / 100; + } + end = startAngleRad + (cumulative * circ); + + // set the shape + point.shapeType = 'arc'; + point.shapeArgs = { + x: positions[0], + y: positions[1], + r: positions[2] / 2, + innerR: positions[3] / 2, + start: mathRound(start * precision) / precision, + end: mathRound(end * precision) / precision + }; + + // The angle must stay within -90 and 270 (#2645) + angle = (end + start) / 2; + if (angle > 1.5 * mathPI) { + angle -= 2 * mathPI; + } else if (angle < -mathPI / 2) { + angle += 2 * mathPI; + } + + // Center for the sliced out slice + point.slicedTranslation = { + translateX: mathRound(mathCos(angle) * slicedOffset), + translateY: mathRound(mathSin(angle) * slicedOffset) + }; + + // set the anchor point for tooltips + radiusX = mathCos(angle) * positions[2] / 2; + radiusY = mathSin(angle) * positions[2] / 2; + point.tooltipPos = [ + positions[0] + radiusX * 0.7, + positions[1] + radiusY * 0.7 + ]; + + point.half = angle < -mathPI / 2 || angle > mathPI / 2 ? 1 : 0; + point.angle = angle; + + // set the anchor point for data labels + connectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678 + point.labelPos = [ + positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector + positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a + positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie + positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a + positions[0] + radiusX, // landing point for connector + positions[1] + radiusY, // a/a + labelDistance < 0 ? // alignment + 'center' : + point.half ? 'right' : 'left', // alignment + angle // center angle + ]; + + } + }, + + drawGraph: null, + + /** + * Draw the data points + */ + drawPoints: function () { + var series = this, + chart = series.chart, + renderer = chart.renderer, + groupTranslation, + //center, + graphic, + //group, + shadow = series.options.shadow, + shadowGroup, + shapeArgs; + + if (shadow && !series.shadowGroup) { + series.shadowGroup = renderer.g('shadow') + .add(series.group); + } + + // draw the slices + each(series.points, function (point) { + graphic = point.graphic; + shapeArgs = point.shapeArgs; + shadowGroup = point.shadowGroup; + + // put the shadow behind all points + if (shadow && !shadowGroup) { + shadowGroup = point.shadowGroup = renderer.g('shadow') + .add(series.shadowGroup); + } + + // if the point is sliced, use special translation, else use plot area traslation + groupTranslation = point.sliced ? point.slicedTranslation : { + translateX: 0, + translateY: 0 + }; + + //group.translate(groupTranslation[0], groupTranslation[1]); + if (shadowGroup) { + shadowGroup.attr(groupTranslation); + } + + // draw the slice + if (graphic) { + graphic.animate(extend(shapeArgs, groupTranslation)); + } else { + point.graphic = graphic = renderer[point.shapeType](shapeArgs) + .setRadialReference(series.center) + .attr( + point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] + ) + .attr({ + 'stroke-linejoin': 'round' + //zIndex: 1 // #2722 (reversed) + }) + .attr(groupTranslation) + .add(series.group) + .shadow(shadow, shadowGroup); + } + + // detect point specific visibility (#2430) + if (point.visible !== undefined) { + point.setVisible(point.visible); + } + + }); + + }, + + /** + * Utility for sorting data labels + */ + sortByAngle: function (points, sign) { + points.sort(function (a, b) { + return a.angle !== undefined && (b.angle - a.angle) * sign; + }); + }, + + /** + * Use a simple symbol from LegendSymbolMixin + */ + drawLegendSymbol: LegendSymbolMixin.drawRectangle, + + /** + * Use the getCenter method from drawLegendSymbol + */ + getCenter: CenteredSeriesMixin.getCenter, + + /** + * Pies don't have point marker symbols + */ + getSymbol: noop + +}; +PieSeries = extendClass(Series, PieSeries); +seriesTypes.pie = PieSeries; + +/** + * Draw the data labels + */ +Series.prototype.drawDataLabels = function () { + + var series = this, + seriesOptions = series.options, + cursor = seriesOptions.cursor, + options = seriesOptions.dataLabels, + points = series.points, + pointOptions, + generalOptions, + str, + dataLabelsGroup; + + if (options.enabled || series._hasPointLabels) { + + // Process default alignment of data labels for columns + if (series.dlProcessOptions) { + series.dlProcessOptions(options); + } + + // Create a separate group for the data labels to avoid rotation + dataLabelsGroup = series.plotGroup( + 'dataLabelsGroup', + 'data-labels', + HIDDEN, + options.zIndex || 6 + ); + + if (!series.hasRendered && pick(options.defer, true)) { + dataLabelsGroup.attr({ opacity: 0 }); + addEvent(series, 'afterAnimate', function () { + series.dataLabelsGroup.show()[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, { duration: 200 }); + }); + } + + // Make the labels for each point + generalOptions = options; + each(points, function (point) { + + var enabled, + dataLabel = point.dataLabel, + labelConfig, + attr, + name, + rotation, + connector = point.connector, + isNew = true; + + // Determine if each data label is enabled + pointOptions = point.options && point.options.dataLabels; + enabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled); // #2282 + + + // If the point is outside the plot area, destroy it. #678, #820 + if (dataLabel && !enabled) { + point.dataLabel = dataLabel.destroy(); + + // Individual labels are disabled if the are explicitly disabled + // in the point options, or if they fall outside the plot area. + } else if (enabled) { + + // Create individual options structure that can be extended without + // affecting others + options = merge(generalOptions, pointOptions); + + rotation = options.rotation; + + // Get the string + labelConfig = point.getLabelConfig(); + str = options.format ? + format(options.format, labelConfig) : + options.formatter.call(labelConfig, options); + + // Determine the color + options.style.color = pick(options.color, options.style.color, series.color, 'black'); + + + // update existing label + if (dataLabel) { + + if (defined(str)) { + dataLabel + .attr({ + text: str + }); + isNew = false; + + } else { // #1437 - the label is shown conditionally + point.dataLabel = dataLabel = dataLabel.destroy(); + if (connector) { + point.connector = connector.destroy(); + } + } + + // create new label + } else if (defined(str)) { + attr = { + //align: align, + fill: options.backgroundColor, + stroke: options.borderColor, + 'stroke-width': options.borderWidth, + r: options.borderRadius || 0, + rotation: rotation, + padding: options.padding, + zIndex: 1 + }; + // Remove unused attributes (#947) + for (name in attr) { + if (attr[name] === UNDEFINED) { + delete attr[name]; + } + } + + dataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation + str, + 0, + -999, + null, + null, + null, + options.useHTML + ) + .attr(attr) + .css(extend(options.style, cursor && { cursor: cursor })) + .add(dataLabelsGroup) + .shadow(options.shadow); + + } + + if (dataLabel) { + // Now the data label is created and placed at 0,0, so we need to align it + series.alignDataLabel(point, dataLabel, options, null, isNew); + } + } + }); + } +}; + +/** + * Align each individual data label + */ +Series.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { + var chart = this.chart, + inverted = chart.inverted, + plotX = pick(point.plotX, -999), + plotY = pick(point.plotY, -999), + bBox = dataLabel.getBBox(), + // Math.round for rounding errors (#2683), alignTo to allow column labels (#2700) + visible = this.visible && (point.series.forceDL || chart.isInsidePlot(plotX, mathRound(plotY), inverted) || + (alignTo && chart.isInsidePlot(plotX, inverted ? alignTo.x + 1 : alignTo.y + alignTo.height - 1, inverted))), + alignAttr; // the final position; + + if (visible) { + + // The alignment box is a singular point + alignTo = extend({ + x: inverted ? chart.plotWidth - plotY : plotX, + y: mathRound(inverted ? chart.plotHeight - plotX : plotY), + width: 0, + height: 0 + }, alignTo); + + // Add the text size for alignment calculation + extend(options, { + width: bBox.width, + height: bBox.height + }); + + // Allow a hook for changing alignment in the last moment, then do the alignment + if (options.rotation) { // Fancy box alignment isn't supported for rotated text + alignAttr = { + align: options.align, + x: alignTo.x + options.x + alignTo.width / 2, + y: alignTo.y + options.y + alignTo.height / 2 + }; + dataLabel[isNew ? 'attr' : 'animate'](alignAttr); + } else { + dataLabel.align(options, null, alignTo); + alignAttr = dataLabel.alignAttr; + + // Handle justify or crop + if (pick(options.overflow, 'justify') === 'justify') { + this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew); + + } else if (pick(options.crop, true)) { + // Now check that the data label is within the plot area + visible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height); + + } + } + } + + // Show or hide based on the final aligned position + if (!visible) { + dataLabel.attr({ y: -999 }); + dataLabel.placed = false; // don't animate back in + } + +}; + +/** + * If data labels fall partly outside the plot area, align them back in, in a way that + * doesn't hide the point. + */ +Series.prototype.justifyDataLabel = function (dataLabel, options, alignAttr, bBox, alignTo, isNew) { + var chart = this.chart, + align = options.align, + verticalAlign = options.verticalAlign, + off, + justified; + + // Off left + off = alignAttr.x; + if (off < 0) { + if (align === 'right') { + options.align = 'left'; + } else { + options.x = -off; + } + justified = true; + } + + // Off right + off = alignAttr.x + bBox.width; + if (off > chart.plotWidth) { + if (align === 'left') { + options.align = 'right'; + } else { + options.x = chart.plotWidth - off; + } + justified = true; + } + + // Off top + off = alignAttr.y; + if (off < 0) { + if (verticalAlign === 'bottom') { + options.verticalAlign = 'top'; + } else { + options.y = -off; + } + justified = true; + } + + // Off bottom + off = alignAttr.y + bBox.height; + if (off > chart.plotHeight) { + if (verticalAlign === 'top') { + options.verticalAlign = 'bottom'; + } else { + options.y = chart.plotHeight - off; + } + justified = true; + } + + if (justified) { + dataLabel.placed = !isNew; + dataLabel.align(options, null, alignTo); + } +}; + +/** + * Override the base drawDataLabels method by pie specific functionality + */ +if (seriesTypes.pie) { + seriesTypes.pie.prototype.drawDataLabels = function () { + var series = this, + data = series.data, + point, + chart = series.chart, + options = series.options.dataLabels, + connectorPadding = pick(options.connectorPadding, 10), + connectorWidth = pick(options.connectorWidth, 1), + plotWidth = chart.plotWidth, + plotHeight = chart.plotHeight, + connector, + connectorPath, + softConnector = pick(options.softConnector, true), + distanceOption = options.distance, + seriesCenter = series.center, + radius = seriesCenter[2] / 2, + centerY = seriesCenter[1], + outside = distanceOption > 0, + dataLabel, + dataLabelWidth, + labelPos, + labelHeight, + halves = [// divide the points into right and left halves for anti collision + [], // right + [] // left + ], + x, + y, + visibility, + rankArr, + i, + j, + overflow = [0, 0, 0, 0], // top, right, bottom, left + sort = function (a, b) { + return b.y - a.y; + }; + + // get out if not enabled + if (!series.visible || (!options.enabled && !series._hasPointLabels)) { + return; + } + + // run parent method + Series.prototype.drawDataLabels.apply(series); + + // arrange points for detection collision + each(data, function (point) { + if (point.dataLabel && point.visible) { // #407, #2510 + halves[point.half].push(point); + } + }); + + // assume equal label heights + i = 0; + while (!labelHeight && data[i]) { // #1569 + labelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968 + i++; + } + + /* Loop over the points in each half, starting from the top and bottom + * of the pie to detect overlapping labels. + */ + i = 2; + while (i--) { + + var slots = [], + slotsLength, + usedSlots = [], + points = halves[i], + pos, + length = points.length, + slotIndex; + + // Sort by angle + series.sortByAngle(points, i - 0.5); + + // Only do anti-collision when we are outside the pie and have connectors (#856) + if (distanceOption > 0) { + + // build the slots + for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) { + slots.push(pos); + + // visualize the slot + /* + var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0), + slotY = pos + chart.plotTop; + if (!isNaN(slotX)) { + chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1) + .attr({ + 'stroke-width': 1, + stroke: 'silver' + }) + .add(); + chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4) + .attr({ + fill: 'silver' + }).add(); + } + */ + } + slotsLength = slots.length; + + // if there are more values than available slots, remove lowest values + if (length > slotsLength) { + // create an array for sorting and ranking the points within each quarter + rankArr = [].concat(points); + rankArr.sort(sort); + j = length; + while (j--) { + rankArr[j].rank = j; + } + j = length; + while (j--) { + if (points[j].rank >= slotsLength) { + points.splice(j, 1); + } + } + length = points.length; + } + + // The label goes to the nearest open slot, but not closer to the edge than + // the label's index. + for (j = 0; j < length; j++) { + + point = points[j]; + labelPos = point.labelPos; + + var closest = 9999, + distance, + slotI; + + // find the closest slot index + for (slotI = 0; slotI < slotsLength; slotI++) { + distance = mathAbs(slots[slotI] - labelPos[1]); + if (distance < closest) { + closest = distance; + slotIndex = slotI; + } + } + + // if that slot index is closer to the edges of the slots, move it + // to the closest appropriate slot + if (slotIndex < j && slots[j] !== null) { // cluster at the top + slotIndex = j; + } else if (slotsLength < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom + slotIndex = slotsLength - length + j; + while (slots[slotIndex] === null) { // make sure it is not taken + slotIndex++; + } + } else { + // Slot is taken, find next free slot below. In the next run, the next slice will find the + // slot above these, because it is the closest one + while (slots[slotIndex] === null) { // make sure it is not taken + slotIndex++; + } + } + + usedSlots.push({ i: slotIndex, y: slots[slotIndex] }); + slots[slotIndex] = null; // mark as taken + } + // sort them in order to fill in from the top + usedSlots.sort(sort); + } + + // now the used slots are sorted, fill them up sequentially + for (j = 0; j < length; j++) { + + var slot, naturalY; + + point = points[j]; + labelPos = point.labelPos; + dataLabel = point.dataLabel; + visibility = point.visible === false ? HIDDEN : VISIBLE; + naturalY = labelPos[1]; + + if (distanceOption > 0) { + slot = usedSlots.pop(); + slotIndex = slot.i; + + // if the slot next to currrent slot is free, the y value is allowed + // to fall back to the natural position + y = slot.y; + if ((naturalY > y && slots[slotIndex + 1] !== null) || + (naturalY < y && slots[slotIndex - 1] !== null)) { + y = naturalY; + } + + } else { + y = naturalY; + } + + // get the x - use the natural x position for first and last slot, to prevent the top + // and botton slice connectors from touching each other on either side + x = options.justify ? + seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) : + series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i); + + + // Record the placement and visibility + dataLabel._attr = { + visibility: visibility, + align: labelPos[6] + }; + dataLabel._pos = { + x: x + options.x + + ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0), + y: y + options.y - 10 // 10 is for the baseline (label vs text) + }; + dataLabel.connX = x; + dataLabel.connY = y; + + + // Detect overflowing data labels + if (this.options.size === null) { + dataLabelWidth = dataLabel.width; + // Overflow left + if (x - dataLabelWidth < connectorPadding) { + overflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]); + + // Overflow right + } else if (x + dataLabelWidth > plotWidth - connectorPadding) { + overflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]); + } + + // Overflow top + if (y - labelHeight / 2 < 0) { + overflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]); + + // Overflow left + } else if (y + labelHeight / 2 > plotHeight) { + overflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]); + } + } + } // for each point + } // for each half + + // Do not apply the final placement and draw the connectors until we have verified + // that labels are not spilling over. + if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) { + + // Place the labels in the final position + this.placeDataLabels(); + + // Draw the connectors + if (outside && connectorWidth) { + each(this.points, function (point) { + connector = point.connector; + labelPos = point.labelPos; + dataLabel = point.dataLabel; + + if (dataLabel && dataLabel._pos) { + visibility = dataLabel._attr.visibility; + x = dataLabel.connX; + y = dataLabel.connY; + connectorPath = softConnector ? [ + M, + x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label + 'C', + x, y, // first break, next to the label + 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5], + labelPos[2], labelPos[3], // second break + L, + labelPos[4], labelPos[5] // base + ] : [ + M, + x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label + L, + labelPos[2], labelPos[3], // second break + L, + labelPos[4], labelPos[5] // base + ]; + + if (connector) { + connector.animate({ d: connectorPath }); + connector.attr('visibility', visibility); + + } else { + point.connector = connector = series.chart.renderer.path(connectorPath).attr({ + 'stroke-width': connectorWidth, + stroke: options.connectorColor || point.color || '#606060', + visibility: visibility + //zIndex: 0 // #2722 (reversed) + }) + .add(series.dataLabelsGroup); + } + } else if (connector) { + point.connector = connector.destroy(); + } + }); + } + } + }; + /** + * Perform the final placement of the data labels after we have verified that they + * fall within the plot area. + */ + seriesTypes.pie.prototype.placeDataLabels = function () { + each(this.points, function (point) { + var dataLabel = point.dataLabel, + _pos; + + if (dataLabel) { + _pos = dataLabel._pos; + if (_pos) { + dataLabel.attr(dataLabel._attr); + dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos); + dataLabel.moved = true; + } else if (dataLabel) { + dataLabel.attr({ y: -999 }); + } + } + }); + }; + + seriesTypes.pie.prototype.alignDataLabel = noop; + + /** + * Verify whether the data labels are allowed to draw, or we should run more translation and data + * label positioning to keep them inside the plot area. Returns true when data labels are ready + * to draw. + */ + seriesTypes.pie.prototype.verifyDataLabelOverflow = function (overflow) { + + var center = this.center, + options = this.options, + centerOption = options.center, + minSize = options.minSize || 80, + newSize = minSize, + ret; + + // Handle horizontal size and center + if (centerOption[0] !== null) { // Fixed center + newSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize); + + } else { // Auto center + newSize = mathMax( + center[2] - overflow[1] - overflow[3], // horizontal overflow + minSize + ); + center[0] += (overflow[3] - overflow[1]) / 2; // horizontal center + } + + // Handle vertical size and center + if (centerOption[1] !== null) { // Fixed center + newSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize); + + } else { // Auto center + newSize = mathMax( + mathMin( + newSize, + center[2] - overflow[0] - overflow[2] // vertical overflow + ), + minSize + ); + center[1] += (overflow[0] - overflow[2]) / 2; // vertical center + } + + // If the size must be decreased, we need to run translate and drawDataLabels again + if (newSize < center[2]) { + center[2] = newSize; + this.translate(center); + each(this.points, function (point) { + if (point.dataLabel) { + point.dataLabel._pos = null; // reset + } + }); + + if (this.drawDataLabels) { + this.drawDataLabels(); + } + // Else, return true to indicate that the pie and its labels is within the plot area + } else { + ret = true; + } + return ret; + }; +} + +if (seriesTypes.column) { + + /** + * Override the basic data label alignment by adjusting for the position of the column + */ + seriesTypes.column.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { + var chart = this.chart, + inverted = chart.inverted, + dlBox = point.dlBox || point.shapeArgs, // data label box for alignment + below = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)), + inside = pick(options.inside, !!this.options.stacking); // draw it inside the box? + + // Align to the column itself, or the top of it + if (dlBox) { // Area range uses this method but not alignTo + alignTo = merge(dlBox); + + if (inverted) { + alignTo = { + x: chart.plotWidth - alignTo.y - alignTo.height, + y: chart.plotHeight - alignTo.x - alignTo.width, + width: alignTo.height, + height: alignTo.width + }; + } + + // Compute the alignment box + if (!inside) { + if (inverted) { + alignTo.x += below ? 0 : alignTo.width; + alignTo.width = 0; + } else { + alignTo.y += below ? alignTo.height : 0; + alignTo.height = 0; + } + } + } + + + // When alignment is undefined (typically columns and bars), display the individual + // point below or above the point depending on the threshold + options.align = pick( + options.align, + !inverted || inside ? 'center' : below ? 'right' : 'left' + ); + options.verticalAlign = pick( + options.verticalAlign, + inverted || inside ? 'middle' : below ? 'top' : 'bottom' + ); + + // Call the parent method + Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); + }; +} + + + +/** + * TrackerMixin for points and graphs + */ + +var TrackerMixin = Highcharts.TrackerMixin = { + + drawTrackerPoint: function () { + var series = this, + chart = series.chart, + pointer = chart.pointer, + cursor = series.options.cursor, + css = cursor && { cursor: cursor }, + onMouseOver = function (e) { + var target = e.target, + point; + + if (chart.hoverSeries !== series) { + series.onMouseOver(); + } + + while (target && !point) { + point = target.point; + target = target.parentNode; + } + + if (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart + point.onMouseOver(e); + } + }; + + // Add reference to the point + each(series.points, function (point) { + if (point.graphic) { + point.graphic.element.point = point; + } + if (point.dataLabel) { + point.dataLabel.element.point = point; + } + }); + + // Add the event listeners, we need to do this only once + if (!series._hasTracking) { + each(series.trackerGroups, function (key) { + if (series[key]) { // we don't always have dataLabelsGroup + series[key] + .addClass(PREFIX + 'tracker') + .on('mouseover', onMouseOver) + .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }) + .css(css); + if (hasTouch) { + series[key].on('touchstart', onMouseOver); + } + } + }); + series._hasTracking = true; + } + }, + + /** + * Draw the tracker object that sits above all data labels and markers to + * track mouse events on the graph or points. For the line type charts + * the tracker uses the same graphPath, but with a greater stroke width + * for better control. + */ + drawTrackerGraph: function () { + var series = this, + options = series.options, + trackByArea = options.trackByArea, + trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath), + trackerPathLength = trackerPath.length, + chart = series.chart, + pointer = chart.pointer, + renderer = chart.renderer, + snap = chart.options.tooltip.snap, + tracker = series.tracker, + cursor = options.cursor, + css = cursor && { cursor: cursor }, + singlePoints = series.singlePoints, + singlePoint, + i, + onMouseOver = function () { + if (chart.hoverSeries !== series) { + series.onMouseOver(); + } + }, + /* + * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable + * IE6: 0.002 + * IE7: 0.002 + * IE8: 0.002 + * IE9: 0.00000000001 (unlimited) + * IE10: 0.0001 (exporting only) + * FF: 0.00000000001 (unlimited) + * Chrome: 0.000001 + * Safari: 0.000001 + * Opera: 0.00000000001 (unlimited) + */ + TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')'; + + // Extend end points. A better way would be to use round linecaps, + // but those are not clickable in VML. + if (trackerPathLength && !trackByArea) { + i = trackerPathLength + 1; + while (i--) { + if (trackerPath[i] === M) { // extend left side + trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L); + } + if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side + trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]); + } + } + } + + // handle single points + for (i = 0; i < singlePoints.length; i++) { + singlePoint = singlePoints[i]; + trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY, + L, singlePoint.plotX + snap, singlePoint.plotY); + } + + // draw the tracker + if (tracker) { + tracker.attr({ d: trackerPath }); + } else { // create + + series.tracker = renderer.path(trackerPath) + .attr({ + 'stroke-linejoin': 'round', // #1225 + visibility: series.visible ? VISIBLE : HIDDEN, + stroke: TRACKER_FILL, + fill: trackByArea ? TRACKER_FILL : NONE, + 'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap), + zIndex: 2 + }) + .add(series.group); + + // The tracker is added to the series group, which is clipped, but is covered + // by the marker group. So the marker group also needs to capture events. + each([series.tracker, series.markerGroup], function (tracker) { + tracker.addClass(PREFIX + 'tracker') + .on('mouseover', onMouseOver) + .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }) + .css(css); + + if (hasTouch) { + tracker.on('touchstart', onMouseOver); + } + }); + } + } +}; +/* End TrackerMixin */ + + +/** + * Add tracking event listener to the series group, so the point graphics + * themselves act as trackers + */ + +if (seriesTypes.column) { + ColumnSeries.prototype.drawTracker = TrackerMixin.drawTrackerPoint; +} + +if (seriesTypes.pie) { + seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint; +} + +if (seriesTypes.scatter) { + ScatterSeries.prototype.drawTracker = TrackerMixin.drawTrackerPoint; +} + +/* + * Extend Legend for item events + */ +extend(Legend.prototype, { + + setItemEvents: function (item, legendItem, useHTML, itemStyle, itemHiddenStyle) { + var legend = this; + // Set the events on the item group, or in case of useHTML, the item itself (#1249) + (useHTML ? legendItem : item.legendGroup).on('mouseover', function () { + item.setState(HOVER_STATE); + legendItem.css(legend.options.itemHoverStyle); + }) + .on('mouseout', function () { + legendItem.css(item.visible ? itemStyle : itemHiddenStyle); + item.setState(); + }) + .on('click', function (event) { + var strLegendItemClick = 'legendItemClick', + fnLegendItemClick = function () { + item.setVisible(); + }; + + // Pass over the click/touch event. #4. + event = { + browserEvent: event + }; + + // click the name or symbol + if (item.firePointEvent) { // point + item.firePointEvent(strLegendItemClick, event, fnLegendItemClick); + } else { + fireEvent(item, strLegendItemClick, event, fnLegendItemClick); + } + }); + }, + + createCheckboxForItem: function (item) { + var legend = this; + + item.checkbox = createElement('input', { + type: 'checkbox', + checked: item.selected, + defaultChecked: item.selected // required by IE7 + }, legend.options.itemCheckboxStyle, legend.chart.container); + + addEvent(item.checkbox, 'click', function (event) { + var target = event.target; + fireEvent(item, 'checkboxClick', { + checked: target.checked + }, + function () { + item.select(); + } + ); + }); + } +}); + +/* + * Add pointer cursor to legend itemstyle in defaultOptions + */ +defaultOptions.legend.itemStyle.cursor = 'pointer'; + + +/* + * Extend the Chart object with interaction + */ + +extend(Chart.prototype, { + /** + * Display the zoom button + */ + showResetZoom: function () { + var chart = this, + lang = defaultOptions.lang, + btnOptions = chart.options.chart.resetZoomButton, + theme = btnOptions.theme, + states = theme.states, + alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox'; + + this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover) + .attr({ + align: btnOptions.position.align, + title: lang.resetZoomTitle + }) + .add() + .align(btnOptions.position, false, alignTo); + + }, + + /** + * Zoom out to 1:1 + */ + zoomOut: function () { + var chart = this; + fireEvent(chart, 'selection', { resetSelection: true }, function () { + chart.zoom(); + }); + }, + + /** + * Zoom into a given portion of the chart given by axis coordinates + * @param {Object} event + */ + zoom: function (event) { + var chart = this, + hasZoomed, + pointer = chart.pointer, + displayButton = false, + resetZoomButton; + + // If zoom is called with no arguments, reset the axes + if (!event || event.resetSelection) { + each(chart.axes, function (axis) { + hasZoomed = axis.zoom(); + }); + } else { // else, zoom in on all axes + each(event.xAxis.concat(event.yAxis), function (axisData) { + var axis = axisData.axis, + isXAxis = axis.isXAxis; + + // don't zoom more than minRange + if (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) { + hasZoomed = axis.zoom(axisData.min, axisData.max); + if (axis.displayBtn) { + displayButton = true; + } + } + }); + } + + // Show or hide the Reset zoom button + resetZoomButton = chart.resetZoomButton; + if (displayButton && !resetZoomButton) { + chart.showResetZoom(); + } else if (!displayButton && isObject(resetZoomButton)) { + chart.resetZoomButton = resetZoomButton.destroy(); + } + + + // Redraw + if (hasZoomed) { + chart.redraw( + pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation + ); + } + }, + + /** + * Pan the chart by dragging the mouse across the pane. This function is called + * on mouse move, and the distance to pan is computed from chartX compared to + * the first chartX position in the dragging operation. + */ + pan: function (e, panning) { + + var chart = this, + hoverPoints = chart.hoverPoints, + doRedraw; + + // remove active points for shared tooltip + if (hoverPoints) { + each(hoverPoints, function (point) { + point.setState(); + }); + } + + each(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps + var mousePos = e[isX ? 'chartX' : 'chartY'], + axis = chart[isX ? 'xAxis' : 'yAxis'][0], + startPos = chart[isX ? 'mouseDownX' : 'mouseDownY'], + halfPointRange = (axis.pointRange || 0) / 2, + extremes = axis.getExtremes(), + newMin = axis.toValue(startPos - mousePos, true) + halfPointRange, + newMax = axis.toValue(startPos + chart[isX ? 'plotWidth' : 'plotHeight'] - mousePos, true) - halfPointRange; + + if (axis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) { + axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' }); + doRedraw = true; + } + + chart[isX ? 'mouseDownX' : 'mouseDownY'] = mousePos; // set new reference for next run + }); + + if (doRedraw) { + chart.redraw(false); + } + css(chart.container, { cursor: 'move' }); + } +}); + +/* + * Extend the Point object with interaction + */ +extend(Point.prototype, { + /** + * Toggle the selection status of a point + * @param {Boolean} selected Whether to select or unselect the point. + * @param {Boolean} accumulate Whether to add to the previous selection. By default, + * this happens if the control key (Cmd on Mac) was pressed during clicking. + */ + select: function (selected, accumulate) { + var point = this, + series = point.series, + chart = series.chart; + + selected = pick(selected, !point.selected); + + // fire the event with the defalut handler + point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { + point.selected = point.options.selected = selected; + series.options.data[inArray(point, series.data)] = point.options; + + point.setState(selected && SELECT_STATE); + + // unselect all other points unless Ctrl or Cmd + click + if (!accumulate) { + each(chart.getSelectedPoints(), function (loopPoint) { + if (loopPoint.selected && loopPoint !== point) { + loopPoint.selected = loopPoint.options.selected = false; + series.options.data[inArray(loopPoint, series.data)] = loopPoint.options; + loopPoint.setState(NORMAL_STATE); + loopPoint.firePointEvent('unselect'); + } + }); + } + }); + }, + + /** + * Runs on mouse over the point + */ + onMouseOver: function (e) { + var point = this, + series = point.series, + chart = series.chart, + tooltip = chart.tooltip, + hoverPoint = chart.hoverPoint; + + // set normal state to previous series + if (hoverPoint && hoverPoint !== point) { + hoverPoint.onMouseOut(); + } + + // trigger the event + point.firePointEvent('mouseOver'); + + // update the tooltip + if (tooltip && (!tooltip.shared || series.noSharedTooltip)) { + tooltip.refresh(point, e); + } + + // hover this + point.setState(HOVER_STATE); + chart.hoverPoint = point; + }, + + /** + * Runs on mouse out from the point + */ + onMouseOut: function () { + var chart = this.series.chart, + hoverPoints = chart.hoverPoints; + + if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887 + this.firePointEvent('mouseOut'); + + this.setState(); + chart.hoverPoint = null; + } + }, + + /** + * Import events from the series' and point's options. Only do it on + * demand, to save processing time on hovering. + */ + importEvents: function () { + if (!this.hasImportedEvents) { + var point = this, + options = merge(point.series.options.point, point.options), + events = options.events, + eventType; + + point.events = events; + + for (eventType in events) { + addEvent(point, eventType, events[eventType]); + } + this.hasImportedEvents = true; + + } + }, + + /** + * Set the point's state + * @param {String} state + */ + setState: function (state, move) { + var point = this, + plotX = point.plotX, + plotY = point.plotY, + series = point.series, + stateOptions = series.options.states, + markerOptions = defaultPlotOptions[series.type].marker && series.options.marker, + normalDisabled = markerOptions && !markerOptions.enabled, + markerStateOptions = markerOptions && markerOptions.states[state], + stateDisabled = markerStateOptions && markerStateOptions.enabled === false, + stateMarkerGraphic = series.stateMarkerGraphic, + pointMarker = point.marker || {}, + chart = series.chart, + radius, + halo = series.halo, + haloOptions, + newSymbol, + pointAttr; + + state = state || NORMAL_STATE; // empty string + pointAttr = point.pointAttr[state] || series.pointAttr[state]; + + if ( + // already has this state + (state === point.state && !move) || + // selected points don't respond to hover + (point.selected && state !== SELECT_STATE) || + // series' state options is disabled + (stateOptions[state] && stateOptions[state].enabled === false) || + // general point marker's state options is disabled + (state && (stateDisabled || (normalDisabled && markerStateOptions.enabled === false))) || + // individual point marker's state options is disabled + (state && pointMarker.states && pointMarker.states[state] && pointMarker.states[state].enabled === false) // #1610 + + ) { + return; + } + + // apply hover styles to the existing point + if (point.graphic) { + radius = markerOptions && point.graphic.symbolName && pointAttr.r; + point.graphic.attr(merge( + pointAttr, + radius ? { // new symbol attributes (#507, #612) + x: plotX - radius, + y: plotY - radius, + width: 2 * radius, + height: 2 * radius + } : {} + )); + + // Zooming in from a range with no markers to a range with markers + if (stateMarkerGraphic) { + stateMarkerGraphic.hide(); + } + } else { + // if a graphic is not applied to each point in the normal state, create a shared + // graphic for the hover state + if (state && markerStateOptions) { + radius = markerStateOptions.radius; + newSymbol = pointMarker.symbol || series.symbol; + + // If the point has another symbol than the previous one, throw away the + // state marker graphic and force a new one (#1459) + if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) { + stateMarkerGraphic = stateMarkerGraphic.destroy(); + } + + // Add a new state marker graphic + if (!stateMarkerGraphic) { + if (newSymbol) { + series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol( + newSymbol, + plotX - radius, + plotY - radius, + 2 * radius, + 2 * radius + ) + .attr(pointAttr) + .add(series.markerGroup); + stateMarkerGraphic.currentSymbol = newSymbol; + } + + // Move the existing graphic + } else { + stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054 + x: plotX - radius, + y: plotY - radius + }); + } + } + + if (stateMarkerGraphic) { + stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450 + } + } + + // Show me your halo + haloOptions = stateOptions[state] && stateOptions[state].halo; + if (haloOptions && haloOptions.size) { + if (!halo) { + series.halo = halo = chart.renderer.path() + .add(series.seriesGroup); + } + halo.attr(extend({ + fill: Color(point.color || series.color).setOpacity(haloOptions.opacity).get() + }, haloOptions.attributes))[move ? 'animate' : 'attr']({ + d: point.haloPath(haloOptions.size) + }); + } else if (halo) { + halo.attr({ d: [] }); + } + + point.state = state; + }, + + haloPath: function (size) { + var series = this.series, + chart = series.chart, + plotBox = series.getPlotBox(), + inverted = chart.inverted; + + return chart.renderer.symbols.circle( + plotBox.translateX + (inverted ? series.yAxis.len - this.plotY : this.plotX) - size, + plotBox.translateY + (inverted ? series.xAxis.len - this.plotX : this.plotY) - size, + size * 2, + size * 2 + ); + } +}); + +/* + * Extend the Series object with interaction + */ + +extend(Series.prototype, { + /** + * Series mouse over handler + */ + onMouseOver: function () { + var series = this, + chart = series.chart, + hoverSeries = chart.hoverSeries; + + // set normal state to previous series + if (hoverSeries && hoverSeries !== series) { + hoverSeries.onMouseOut(); + } + + // trigger the event, but to save processing time, + // only if defined + if (series.options.events.mouseOver) { + fireEvent(series, 'mouseOver'); + } + + // hover this + series.setState(HOVER_STATE); + chart.hoverSeries = series; + }, + + /** + * Series mouse out handler + */ + onMouseOut: function () { + // trigger the event only if listeners exist + var series = this, + options = series.options, + chart = series.chart, + tooltip = chart.tooltip, + hoverPoint = chart.hoverPoint; + + // trigger mouse out on the point, which must be in this series + if (hoverPoint) { + hoverPoint.onMouseOut(); + } + + // fire the mouse out event + if (series && options.events.mouseOut) { + fireEvent(series, 'mouseOut'); + } + + + // hide the tooltip + if (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) { + tooltip.hide(); + } + + // set normal state + series.setState(); + chart.hoverSeries = null; + }, + + /** + * Set the state of the graph + */ + setState: function (state) { + var series = this, + options = series.options, + graph = series.graph, + graphNeg = series.graphNeg, + stateOptions = options.states, + lineWidth = options.lineWidth, + attribs; + + state = state || NORMAL_STATE; + + if (series.state !== state) { + series.state = state; + + if (stateOptions[state] && stateOptions[state].enabled === false) { + return; + } + + if (state) { + lineWidth = stateOptions[state].lineWidth || lineWidth + 1; + } + + if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML + attribs = { + 'stroke-width': lineWidth + }; + // use attr because animate will cause any other animation on the graph to stop + graph.attr(attribs); + if (graphNeg) { + graphNeg.attr(attribs); + } + } + } + }, + + /** + * Set the visibility of the graph + * + * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED, + * the visibility is toggled. + */ + setVisible: function (vis, redraw) { + var series = this, + chart = series.chart, + legendItem = series.legendItem, + showOrHide, + ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, + oldVisibility = series.visible; + + // if called without an argument, toggle visibility + series.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis; + showOrHide = vis ? 'show' : 'hide'; + + // show or hide elements + each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) { + if (series[key]) { + series[key][showOrHide](); + } + }); + + + // hide tooltip (#1361) + if (chart.hoverSeries === series) { + series.onMouseOut(); + } + + + if (legendItem) { + chart.legend.colorizeItem(series, vis); + } + + + // rescale or adapt to resized chart + series.isDirty = true; + // in a stack, all other series are affected + if (series.options.stacking) { + each(chart.series, function (otherSeries) { + if (otherSeries.options.stacking && otherSeries.visible) { + otherSeries.isDirty = true; + } + }); + } + + // show or hide linked series + each(series.linkedSeries, function (otherSeries) { + otherSeries.setVisible(vis, false); + }); + + if (ignoreHiddenSeries) { + chart.isDirtyBox = true; + } + if (redraw !== false) { + chart.redraw(); + } + + fireEvent(series, showOrHide); + }, + + /** + * Memorize tooltip texts and positions + */ + setTooltipPoints: function (renew) { + var series = this, + points = [], + pointsLength, + low, + high, + xAxis = series.xAxis, + xExtremes = xAxis && xAxis.getExtremes(), + axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar + point, + pointX, + nextPoint, + i, + tooltipPoints = []; // a lookup array for each pixel in the x dimension + + // don't waste resources if tracker is disabled + if (series.options.enableMouseTracking === false || series.singularTooltips) { + return; + } + + // renew + if (renew) { + series.tooltipPoints = null; + } + + // concat segments to overcome null values + each(series.segments || series.points, function (segment) { + points = points.concat(segment); + }); + + // Reverse the points in case the X axis is reversed + if (xAxis && xAxis.reversed) { + points = points.reverse(); + } + + // Polar needs additional shaping + if (series.orderTooltipPoints) { + series.orderTooltipPoints(points); + } + + // Assign each pixel position to the nearest point + pointsLength = points.length; + for (i = 0; i < pointsLength; i++) { + point = points[i]; + pointX = point.x; + if (pointX >= xExtremes.min && pointX <= xExtremes.max) { // #1149 + nextPoint = points[i + 1]; + + // Set this range's low to the last range's high plus one + low = high === UNDEFINED ? 0 : high + 1; + // Now find the new high + high = points[i + 1] ? + mathMin(mathMax(0, mathFloor( // #2070 + (point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2 + )), axisLength) : + axisLength; + + while (low >= 0 && low <= high) { + tooltipPoints[low++] = point; + } + } + } + series.tooltipPoints = tooltipPoints; + }, + + /** + * Show the graph + */ + show: function () { + this.setVisible(true); + }, + + /** + * Hide the graph + */ + hide: function () { + this.setVisible(false); + }, + + + /** + * Set the selected state of the graph + * + * @param selected {Boolean} True to select the series, false to unselect. If + * UNDEFINED, the selection state is toggled. + */ + select: function (selected) { + var series = this; + // if called without an argument, toggle + series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected; + + if (series.checkbox) { + series.checkbox.checked = selected; + } + + fireEvent(series, selected ? 'select' : 'unselect'); + }, + + drawTracker: TrackerMixin.drawTrackerGraph +}); +// global variables +extend(Highcharts, { + + // Constructors + Axis: Axis, + Chart: Chart, + Color: Color, + Point: Point, + Tick: Tick, + Renderer: Renderer, + Series: Series, + SVGElement: SVGElement, + SVGRenderer: SVGRenderer, + + // Various + arrayMin: arrayMin, + arrayMax: arrayMax, + charts: charts, + dateFormat: dateFormat, + format: format, + pathAnim: pathAnim, + getOptions: getOptions, + hasBidiBug: hasBidiBug, + isTouchDevice: isTouchDevice, + numberFormat: numberFormat, + seriesTypes: seriesTypes, + setOptions: setOptions, + addEvent: addEvent, + removeEvent: removeEvent, + createElement: createElement, + discardElement: discardElement, + css: css, + each: each, + extend: extend, + map: map, + merge: merge, + pick: pick, + splat: splat, + extendClass: extendClass, + pInt: pInt, + wrap: wrap, + svg: hasSVG, + canvas: useCanVG, + vml: !hasSVG && !useCanVG, + product: PRODUCT, + version: VERSION +}); + +}()); diff --git a/java/com.sap.sailing.gwt.ui/highcharts/js/modules/canvas-tools.js b/java/com.sap.sailing.gwt.ui/highcharts/js/modules/canvas-tools.js old mode 100644 new mode 100755 index dcac51ef02a..d9229716dba --- a/java/com.sap.sailing.gwt.ui/highcharts/js/modules/canvas-tools.js +++ b/java/com.sap.sailing.gwt.ui/highcharts/js/modules/canvas-tools.js @@ -1,133 +1,133 @@ -/* - A class to parse color values - @author Stoyan Stefanov - @link http://www.phpied.com/rgb-color-parser-in-javascript/ - Use it if you like it - - canvg.js - Javascript SVG parser and renderer on Canvas - MIT Licensed - Gabe Lerner (gabelerner@gmail.com) - http://code.google.com/p/canvg/ - - Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/ - - Highcharts JS v2.3.3 (2012-10-04) - CanVGRenderer Extension module - - (c) 2011-2012 Torstein Hønsi, Erik Olsson - - License: www.highcharts.com/license -*/ -function RGBColor(m){this.ok=!1;m.charAt(0)=="#"&&(m=m.substr(1,6));var m=m.replace(/ /g,""),m=m.toLowerCase(),a={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b", -darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff", -gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa", -lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080", -oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd", -slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"},c;for(c in a)m==c&&(m=a[c]);var d=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(b){return[parseInt(b[1]),parseInt(b[2]),parseInt(b[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/, -example:["#00ff00","336699"],process:function(b){return[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(b){return[parseInt(b[1]+b[1],16),parseInt(b[2]+b[2],16),parseInt(b[3]+b[3],16)]}}];for(c=0;c255?255:this.r;this.g=this.g<0||isNaN(this.g)?0: -this.g>255?255:this.g;this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b;this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"};this.toHex=function(){var b=this.r.toString(16),a=this.g.toString(16),d=this.b.toString(16);b.length==1&&(b="0"+b);a.length==1&&(a="0"+a);d.length==1&&(d="0"+d);return"#"+b+a+d};this.getHelpXML=function(){for(var b=[],k=0;k "+o.toRGB()+" -> "+o.toHex());l.appendChild(n);l.appendChild(q);c.appendChild(l)}catch(p){}return c}} -if(!window.console)window.console={},window.console.log=function(){},window.console.dir=function(){};if(!Array.prototype.indexOf)Array.prototype.indexOf=function(m){for(var a=0;a]*>/,""),d=new ActiveXObject("Microsoft.XMLDOM");d.async="false";d.loadXML(a);return d}};a.Property=function(c,d){this.name=c;this.value=d;this.hasValue=function(){return this.value!=null&&this.value!==""};this.numValue=function(){if(!this.hasValue())return 0;var b=parseFloat(this.value);(this.value+"").match(/%$/)&& -(b/=100);return b};this.valueOrDefault=function(b){return this.hasValue()?this.value:b};this.numValueOrDefault=function(b){return this.hasValue()?this.numValue():b};var b=this;this.Color={addOpacity:function(d){var c=b.value;if(d!=null&&d!=""){var f=new RGBColor(b.value);f.ok&&(c="rgba("+f.r+", "+f.g+", "+f.b+", "+d+")")}return new a.Property(b.name,c)}};this.Definition={getDefinition:function(){var d=b.value.replace(/^(url\()?#([^\)]+)\)?$/,"$2");return a.Definitions[d]},isUrl:function(){return b.value.indexOf("url(")== -0},getFillStyle:function(b){var d=this.getDefinition();return d!=null&&d.createGradient?d.createGradient(a.ctx,b):d!=null&&d.createPattern?d.createPattern(a.ctx,b):null}};this.Length={DPI:function(){return 96},EM:function(b){var d=12,c=new a.Property("fontSize",a.Font.Parse(a.ctx.font).fontSize);c.hasValue()&&(d=c.Length.toPixels(b));return d},toPixels:function(d){if(!b.hasValue())return 0;var c=b.value+"";return c.match(/em$/)?b.numValue()*this.EM(d):c.match(/ex$/)?b.numValue()*this.EM(d)/2:c.match(/px$/)? -b.numValue():c.match(/pt$/)?b.numValue()*1.25:c.match(/pc$/)?b.numValue()*15:c.match(/cm$/)?b.numValue()*this.DPI(d)/2.54:c.match(/mm$/)?b.numValue()*this.DPI(d)/25.4:c.match(/in$/)?b.numValue()*this.DPI(d):c.match(/%$/)?b.numValue()*a.ViewPort.ComputeSize(d):b.numValue()}};this.Time={toMilliseconds:function(){if(!b.hasValue())return 0;var a=b.value+"";if(a.match(/s$/))return b.numValue()*1E3;a.match(/ms$/);return b.numValue()}};this.Angle={toRadians:function(){if(!b.hasValue())return 0;var a=b.value+ -"";return a.match(/deg$/)?b.numValue()*(Math.PI/180):a.match(/grad$/)?b.numValue()*(Math.PI/200):a.match(/rad$/)?b.numValue():b.numValue()*(Math.PI/180)}}};a.Font=new function(){this.Styles=["normal","italic","oblique","inherit"];this.Variants=["normal","small-caps","inherit"];this.Weights="normal,bold,bolder,lighter,100,200,300,400,500,600,700,800,900,inherit".split(",");this.CreateFont=function(d,b,c,e,f,g){g=g!=null?this.Parse(g):this.CreateFont("","","","","",a.ctx.font);return{fontFamily:f|| -g.fontFamily,fontSize:e||g.fontSize,fontStyle:d||g.fontStyle,fontWeight:c||g.fontWeight,fontVariant:b||g.fontVariant,toString:function(){return[this.fontStyle,this.fontVariant,this.fontWeight,this.fontSize,this.fontFamily].join(" ")}}};var c=this;this.Parse=function(d){for(var b={},d=a.trim(a.compressSpaces(d||"")).split(" "),k=!1,e=!1,f=!1,g=!1,j="",h=0;hthis.x2)this.x2=b}if(a!=null){if(isNaN(this.y1)||isNaN(this.y2))this.y2=this.y1=a;if(athis.y2)this.y2=a}};this.addX=function(b){this.addPoint(b,null)};this.addY=function(b){this.addPoint(null,b)};this.addBoundingBox=function(b){this.addPoint(b.x1,b.y1);this.addPoint(b.x2,b.y2)};this.addQuadraticCurve=function(b,a,d,c,k,l){d=b+2/3*(d-b);c=a+2/3*(c- -a);this.addBezierCurve(b,a,d,d+1/3*(k-b),c,c+1/3*(l-a),k,l)};this.addBezierCurve=function(b,a,d,c,k,l,o,n){var q=[b,a],p=[d,c],t=[k,l],m=[o,n];this.addPoint(q[0],q[1]);this.addPoint(m[0],m[1]);for(i=0;i<=1;i++)b=function(b){return Math.pow(1-b,3)*q[i]+3*Math.pow(1-b,2)*b*p[i]+3*(1-b)*Math.pow(b,2)*t[i]+Math.pow(b,3)*m[i]},a=6*q[i]-12*p[i]+6*t[i],d=-3*q[i]+9*p[i]-9*t[i]+3*m[i],c=3*p[i]-3*q[i],d==0?a!=0&&(a=-c/a,0=this.tokens.length-1};this.isCommandOrEnd=function(){return this.isEnd()? -!0:this.tokens[this.i+1].match(/^[A-Za-z]$/)!=null};this.isRelativeCommand=function(){return this.command==this.command.toLowerCase()};this.getToken=function(){this.i+=1;return this.tokens[this.i]};this.getScalar=function(){return parseFloat(this.getToken())};this.nextCommand=function(){this.previousCommand=this.command;this.command=this.getToken()};this.getPoint=function(){return this.makeAbsolute(new a.Point(this.getScalar(),this.getScalar()))};this.getAsControlPoint=function(){var b=this.getPoint(); -return this.control=b};this.getAsCurrentPoint=function(){var b=this.getPoint();return this.current=b};this.getReflectedControlPoint=function(){return this.previousCommand.toLowerCase()!="c"&&this.previousCommand.toLowerCase()!="s"?this.current:new a.Point(2*this.current.x-this.control.x,2*this.current.y-this.control.y)};this.makeAbsolute=function(b){if(this.isRelativeCommand())b.x=this.current.x+b.x,b.y=this.current.y+b.y;return b};this.addMarker=function(b,a,d){d!=null&&this.angles.length>0&&this.angles[this.angles.length- -1]==null&&(this.angles[this.angles.length-1]=this.points[this.points.length-1].angleTo(d));this.addMarkerAngle(b,a==null?null:a.angleTo(b))};this.addMarkerAngle=function(b,a){this.points.push(b);this.angles.push(a)};this.getMarkerPoints=function(){return this.points};this.getMarkerAngles=function(){for(var b=0;b1&&(h*=Math.sqrt(q),l*=Math.sqrt(q));o=(o==j?-1:1)*Math.sqrt((Math.pow(h,2)*Math.pow(l,2)-Math.pow(h,2)*Math.pow(n.y,2)-Math.pow(l,2)*Math.pow(n.x,2))/(Math.pow(h,2)*Math.pow(n.y,2)+Math.pow(l,2)*Math.pow(n.x,2)));isNaN(o)&&(o=0);var p=new a.Point(o*h*n.y/l,o*-l*n.x/h),g=new a.Point((g.x+e.x)/2+Math.cos(f)* -p.x-Math.sin(f)*p.y,(g.y+e.y)/2+Math.sin(f)*p.x+Math.cos(f)*p.y),m=function(b,a){return(b[0]*a[0]+b[1]*a[1])/(Math.sqrt(Math.pow(b[0],2)+Math.pow(b[1],2))*Math.sqrt(Math.pow(a[0],2)+Math.pow(a[1],2)))},s=function(b,a){return(b[0]*a[1]=1&&(n=0);j==0&&n>0&&(n-=2*Math.PI);j==1&&n<0&&(n+=2*Math.PI);q=new a.Point(g.x-h*Math.cos((o+n)/ -2),g.y-l*Math.sin((o+n)/2));b.addMarkerAngle(q,(o+n)/2+(j==0?1:-1)*Math.PI/2);b.addMarkerAngle(e,n+(j==0?1:-1)*Math.PI/2);c.addPoint(e.x,e.y);d!=null&&(m=h>l?h:l,e=h>l?1:h/l,h=h>l?l/h:1,d.translate(g.x,g.y),d.rotate(f),d.scale(e,h),d.arc(0,0,m,o,o+n,1-j),d.scale(1/e,1/h),d.rotate(-f),d.translate(-g.x,-g.y))}break;case "Z":d!=null&&d.closePath(),b.current=b.start}return c};this.getMarkers=function(){for(var a=this.PathParser.getMarkerPoints(),b=this.PathParser.getMarkerAngles(),c=[],e=0;ethis.maxDuration)if(this.attribute("repeatCount").value=="indefinite")this.duration=0;else return this.attribute("fill").valueOrDefault("remove")=="remove"&&!this.removed?(this.removed=!0,this.getProperty().value=this.initialValue,!0):!1;this.duration+=a;a=!1;if(this.begin0&&b[c-1]!=" "&&c0&&b[c-1]!=" "&&(c==b.length-1||b[c+1]==" "))g="initial";typeof a.glyphs[e]!="undefined"&&(f=a.glyphs[e][g],f==null&&a.glyphs[e].type=="glyph"&&(f=a.glyphs[e]))}else f=a.glyphs[e];if(f==null)f=a.missingGlyph;return f};this.renderChildren=function(c){var b=this.parent.style("font-family").Definition.getDefinition();if(b!=null){var k=this.parent.style("font-size").numValueOrDefault(a.Font.Parse(a.ctx.font).fontSize), -e=this.parent.style("font-style").valueOrDefault(a.Font.Parse(a.ctx.font).fontStyle),f=this.getText();b.isRTL&&(f=f.split("").reverse().join(""));for(var g=a.ToNumberArray(this.parent.attribute("dx").value),j=0;j0?c.childNodes[0].nodeValue:c.text;this.getText=function(){return this.text}};a.Element.tspan.prototype=new a.Element.TextElementBase;a.Element.tref=function(c){this.base=a.Element.TextElementBase;this.base(c);this.getText=function(){var a=this.attribute("xlink:href").Definition.getDefinition();if(a!=null)return a.children[0].getText()}};a.Element.tref.prototype=new a.Element.TextElementBase; -a.Element.a=function(c){this.base=a.Element.TextElementBase;this.base(c);this.hasText=!0;for(var d=0;d1?c.childNodes[1].nodeValue: -""),c=c.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm,""),c=a.compressSpaces(c),c=c.split("}"),d=0;d0){l=g[j].indexOf("url");h=g[j].indexOf(")",l);l=g[j].substr(l+5,h-l-6);l=a.parseXml(a.ajax(l)).getElementsByTagName("font");for(h=0;h + @link http://www.phpied.com/rgb-color-parser-in-javascript/ + Use it if you like it + + canvg.js - Javascript SVG parser and renderer on Canvas + MIT Licensed + Gabe Lerner (gabelerner@gmail.com) + http://code.google.com/p/canvg/ + + Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/ + + Highcharts JS v4.0.1 (2014-04-24) + CanVGRenderer Extension module + + (c) 2011-2012 Torstein Honsi, Erik Olsson + + License: www.highcharts.com/license +*/ +function RGBColor(m){this.ok=!1;m.charAt(0)=="#"&&(m=m.substr(1,6));var m=m.replace(/ /g,""),m=m.toLowerCase(),a={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b", +darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff", +gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa", +lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080", +oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd", +slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"},c;for(c in a)m==c&&(m=a[c]);var d=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(b){return[parseInt(b[1]),parseInt(b[2]),parseInt(b[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/, +example:["#00ff00","336699"],process:function(b){return[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(b){return[parseInt(b[1]+b[1],16),parseInt(b[2]+b[2],16),parseInt(b[3]+b[3],16)]}}];for(c=0;c255?255:this.r;this.g=this.g<0||isNaN(this.g)?0: +this.g>255?255:this.g;this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b;this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"};this.toHex=function(){var b=this.r.toString(16),a=this.g.toString(16),d=this.b.toString(16);b.length==1&&(b="0"+b);a.length==1&&(a="0"+a);d.length==1&&(d="0"+d);return"#"+b+a+d};this.getHelpXML=function(){for(var b=[],k=0;k "+o.toRGB()+" -> "+o.toHex());l.appendChild(n);l.appendChild(q);c.appendChild(l)}catch(p){}return c}} +if(!window.console)window.console={},window.console.log=function(){},window.console.dir=function(){};if(!Array.prototype.indexOf)Array.prototype.indexOf=function(m){for(var a=0;a]*>/,""),d=new ActiveXObject("Microsoft.XMLDOM");d.async="false";d.loadXML(a);return d}};a.Property=function(c,d){this.name=c;this.value=d;this.hasValue=function(){return this.value!=null&&this.value!==""};this.numValue=function(){if(!this.hasValue())return 0;var b=parseFloat(this.value);(this.value+"").match(/%$/)&& +(b/=100);return b};this.valueOrDefault=function(b){return this.hasValue()?this.value:b};this.numValueOrDefault=function(b){return this.hasValue()?this.numValue():b};var b=this;this.Color={addOpacity:function(d){var c=b.value;if(d!=null&&d!=""){var f=new RGBColor(b.value);f.ok&&(c="rgba("+f.r+", "+f.g+", "+f.b+", "+d+")")}return new a.Property(b.name,c)}};this.Definition={getDefinition:function(){var d=b.value.replace(/^(url\()?#([^\)]+)\)?$/,"$2");return a.Definitions[d]},isUrl:function(){return b.value.indexOf("url(")== +0},getFillStyle:function(b){var d=this.getDefinition();return d!=null&&d.createGradient?d.createGradient(a.ctx,b):d!=null&&d.createPattern?d.createPattern(a.ctx,b):null}};this.Length={DPI:function(){return 96},EM:function(b){var d=12,c=new a.Property("fontSize",a.Font.Parse(a.ctx.font).fontSize);c.hasValue()&&(d=c.Length.toPixels(b));return d},toPixels:function(d){if(!b.hasValue())return 0;var c=b.value+"";return c.match(/em$/)?b.numValue()*this.EM(d):c.match(/ex$/)?b.numValue()*this.EM(d)/2:c.match(/px$/)? +b.numValue():c.match(/pt$/)?b.numValue()*1.25:c.match(/pc$/)?b.numValue()*15:c.match(/cm$/)?b.numValue()*this.DPI(d)/2.54:c.match(/mm$/)?b.numValue()*this.DPI(d)/25.4:c.match(/in$/)?b.numValue()*this.DPI(d):c.match(/%$/)?b.numValue()*a.ViewPort.ComputeSize(d):b.numValue()}};this.Time={toMilliseconds:function(){if(!b.hasValue())return 0;var a=b.value+"";if(a.match(/s$/))return b.numValue()*1E3;a.match(/ms$/);return b.numValue()}};this.Angle={toRadians:function(){if(!b.hasValue())return 0;var a=b.value+ +"";return a.match(/deg$/)?b.numValue()*(Math.PI/180):a.match(/grad$/)?b.numValue()*(Math.PI/200):a.match(/rad$/)?b.numValue():b.numValue()*(Math.PI/180)}}};a.Font=new function(){this.Styles=["normal","italic","oblique","inherit"];this.Variants=["normal","small-caps","inherit"];this.Weights="normal,bold,bolder,lighter,100,200,300,400,500,600,700,800,900,inherit".split(",");this.CreateFont=function(d,b,c,e,f,g){g=g!=null?this.Parse(g):this.CreateFont("","","","","",a.ctx.font);return{fontFamily:f|| +g.fontFamily,fontSize:e||g.fontSize,fontStyle:d||g.fontStyle,fontWeight:c||g.fontWeight,fontVariant:b||g.fontVariant,toString:function(){return[this.fontStyle,this.fontVariant,this.fontWeight,this.fontSize,this.fontFamily].join(" ")}}};var c=this;this.Parse=function(d){for(var b={},d=a.trim(a.compressSpaces(d||"")).split(" "),k=!1,e=!1,f=!1,g=!1,j="",h=0;hthis.x2)this.x2=b}if(a!=null){if(isNaN(this.y1)||isNaN(this.y2))this.y2=this.y1=a;if(athis.y2)this.y2=a}};this.addX=function(b){this.addPoint(b,null)};this.addY=function(b){this.addPoint(null,b)};this.addBoundingBox=function(b){this.addPoint(b.x1,b.y1);this.addPoint(b.x2,b.y2)};this.addQuadraticCurve=function(b,a,d,c,k,l){d=b+2/3*(d-b);c=a+2/3*(c- +a);this.addBezierCurve(b,a,d,d+1/3*(k-b),c,c+1/3*(l-a),k,l)};this.addBezierCurve=function(b,a,d,c,k,l,o,n){var q=[b,a],p=[d,c],t=[k,l],m=[o,n];this.addPoint(q[0],q[1]);this.addPoint(m[0],m[1]);for(i=0;i<=1;i++)b=function(b){return Math.pow(1-b,3)*q[i]+3*Math.pow(1-b,2)*b*p[i]+3*(1-b)*Math.pow(b,2)*t[i]+Math.pow(b,3)*m[i]},a=6*q[i]-12*p[i]+6*t[i],d=-3*q[i]+9*p[i]-9*t[i]+3*m[i],c=3*p[i]-3*q[i],d==0?a!=0&&(a=-c/a,0=this.tokens.length-1};this.isCommandOrEnd=function(){return this.isEnd()? +!0:this.tokens[this.i+1].match(/^[A-Za-z]$/)!=null};this.isRelativeCommand=function(){return this.command==this.command.toLowerCase()};this.getToken=function(){this.i+=1;return this.tokens[this.i]};this.getScalar=function(){return parseFloat(this.getToken())};this.nextCommand=function(){this.previousCommand=this.command;this.command=this.getToken()};this.getPoint=function(){return this.makeAbsolute(new a.Point(this.getScalar(),this.getScalar()))};this.getAsControlPoint=function(){var b=this.getPoint(); +return this.control=b};this.getAsCurrentPoint=function(){var b=this.getPoint();return this.current=b};this.getReflectedControlPoint=function(){return this.previousCommand.toLowerCase()!="c"&&this.previousCommand.toLowerCase()!="s"?this.current:new a.Point(2*this.current.x-this.control.x,2*this.current.y-this.control.y)};this.makeAbsolute=function(b){if(this.isRelativeCommand())b.x=this.current.x+b.x,b.y=this.current.y+b.y;return b};this.addMarker=function(b,a,d){d!=null&&this.angles.length>0&&this.angles[this.angles.length- +1]==null&&(this.angles[this.angles.length-1]=this.points[this.points.length-1].angleTo(d));this.addMarkerAngle(b,a==null?null:a.angleTo(b))};this.addMarkerAngle=function(b,a){this.points.push(b);this.angles.push(a)};this.getMarkerPoints=function(){return this.points};this.getMarkerAngles=function(){for(var b=0;b1&&(h*=Math.sqrt(q),l*=Math.sqrt(q));o=(o==j?-1:1)*Math.sqrt((Math.pow(h,2)*Math.pow(l,2)-Math.pow(h,2)*Math.pow(n.y,2)-Math.pow(l,2)*Math.pow(n.x,2))/(Math.pow(h,2)*Math.pow(n.y,2)+Math.pow(l,2)*Math.pow(n.x,2)));isNaN(o)&&(o=0);var p=new a.Point(o*h*n.y/l,o*-l*n.x/h),g=new a.Point((g.x+e.x)/2+Math.cos(f)* +p.x-Math.sin(f)*p.y,(g.y+e.y)/2+Math.sin(f)*p.x+Math.cos(f)*p.y),m=function(b,a){return(b[0]*a[0]+b[1]*a[1])/(Math.sqrt(Math.pow(b[0],2)+Math.pow(b[1],2))*Math.sqrt(Math.pow(a[0],2)+Math.pow(a[1],2)))},s=function(b,a){return(b[0]*a[1]=1&&(n=0);j==0&&n>0&&(n-=2*Math.PI);j==1&&n<0&&(n+=2*Math.PI);q=new a.Point(g.x-h*Math.cos((o+n)/ +2),g.y-l*Math.sin((o+n)/2));b.addMarkerAngle(q,(o+n)/2+(j==0?1:-1)*Math.PI/2);b.addMarkerAngle(e,n+(j==0?1:-1)*Math.PI/2);c.addPoint(e.x,e.y);d!=null&&(m=h>l?h:l,e=h>l?1:h/l,h=h>l?l/h:1,d.translate(g.x,g.y),d.rotate(f),d.scale(e,h),d.arc(0,0,m,o,o+n,1-j),d.scale(1/e,1/h),d.rotate(-f),d.translate(-g.x,-g.y))}break;case "Z":d!=null&&d.closePath(),b.current=b.start}return c};this.getMarkers=function(){for(var a=this.PathParser.getMarkerPoints(),b=this.PathParser.getMarkerAngles(),c=[],e=0;ethis.maxDuration)if(this.attribute("repeatCount").value=="indefinite")this.duration=0;else return this.attribute("fill").valueOrDefault("remove")=="remove"&&!this.removed?(this.removed=!0,this.getProperty().value=this.initialValue,!0):!1;this.duration+=a;a=!1;if(this.begin0&&b[c-1]!=" "&&c0&&b[c-1]!=" "&&(c==b.length-1||b[c+1]==" "))g="initial";typeof a.glyphs[e]!="undefined"&&(f=a.glyphs[e][g],f==null&&a.glyphs[e].type=="glyph"&&(f=a.glyphs[e]))}else f=a.glyphs[e];if(f==null)f=a.missingGlyph;return f};this.renderChildren=function(c){var b=this.parent.style("font-family").Definition.getDefinition();if(b!=null){var k=this.parent.style("font-size").numValueOrDefault(a.Font.Parse(a.ctx.font).fontSize), +e=this.parent.style("font-style").valueOrDefault(a.Font.Parse(a.ctx.font).fontStyle),f=this.getText();b.isRTL&&(f=f.split("").reverse().join(""));for(var g=a.ToNumberArray(this.parent.attribute("dx").value),j=0;j0?c.childNodes[0].nodeValue:c.text;this.getText=function(){return this.text}};a.Element.tspan.prototype=new a.Element.TextElementBase;a.Element.tref=function(c){this.base=a.Element.TextElementBase;this.base(c);this.getText=function(){var a=this.attribute("xlink:href").Definition.getDefinition();if(a!=null)return a.children[0].getText()}};a.Element.tref.prototype=new a.Element.TextElementBase; +a.Element.a=function(c){this.base=a.Element.TextElementBase;this.base(c);this.hasText=!0;for(var d=0;d1?c.childNodes[1].nodeValue: +""),c=c.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm,""),c=a.compressSpaces(c),c=c.split("}"),d=0;d0){l=g[j].indexOf("url");h=g[j].indexOf(")",l);l=g[j].substr(l+5,h-l-6);l=a.parseXml(a.ajax(l)).getElementsByTagName("font");for(h=0;h - * @link http://www.phpied.com/rgb-color-parser-in-javascript/ - * Use it if you like it - * - */ -function RGBColor(color_string) -{ - this.ok = false; - - // strip any leading # - if (color_string.charAt(0) == '#') { // remove # if any - color_string = color_string.substr(1,6); - } - - color_string = color_string.replace(/ /g,''); - color_string = color_string.toLowerCase(); - - // before getting into regexps, try simple matches - // and overwrite the input - var simple_colors = { - aliceblue: 'f0f8ff', - antiquewhite: 'faebd7', - aqua: '00ffff', - aquamarine: '7fffd4', - azure: 'f0ffff', - beige: 'f5f5dc', - bisque: 'ffe4c4', - black: '000000', - blanchedalmond: 'ffebcd', - blue: '0000ff', - blueviolet: '8a2be2', - brown: 'a52a2a', - burlywood: 'deb887', - cadetblue: '5f9ea0', - chartreuse: '7fff00', - chocolate: 'd2691e', - coral: 'ff7f50', - cornflowerblue: '6495ed', - cornsilk: 'fff8dc', - crimson: 'dc143c', - cyan: '00ffff', - darkblue: '00008b', - darkcyan: '008b8b', - darkgoldenrod: 'b8860b', - darkgray: 'a9a9a9', - darkgreen: '006400', - darkkhaki: 'bdb76b', - darkmagenta: '8b008b', - darkolivegreen: '556b2f', - darkorange: 'ff8c00', - darkorchid: '9932cc', - darkred: '8b0000', - darksalmon: 'e9967a', - darkseagreen: '8fbc8f', - darkslateblue: '483d8b', - darkslategray: '2f4f4f', - darkturquoise: '00ced1', - darkviolet: '9400d3', - deeppink: 'ff1493', - deepskyblue: '00bfff', - dimgray: '696969', - dodgerblue: '1e90ff', - feldspar: 'd19275', - firebrick: 'b22222', - floralwhite: 'fffaf0', - forestgreen: '228b22', - fuchsia: 'ff00ff', - gainsboro: 'dcdcdc', - ghostwhite: 'f8f8ff', - gold: 'ffd700', - goldenrod: 'daa520', - gray: '808080', - green: '008000', - greenyellow: 'adff2f', - honeydew: 'f0fff0', - hotpink: 'ff69b4', - indianred : 'cd5c5c', - indigo : '4b0082', - ivory: 'fffff0', - khaki: 'f0e68c', - lavender: 'e6e6fa', - lavenderblush: 'fff0f5', - lawngreen: '7cfc00', - lemonchiffon: 'fffacd', - lightblue: 'add8e6', - lightcoral: 'f08080', - lightcyan: 'e0ffff', - lightgoldenrodyellow: 'fafad2', - lightgrey: 'd3d3d3', - lightgreen: '90ee90', - lightpink: 'ffb6c1', - lightsalmon: 'ffa07a', - lightseagreen: '20b2aa', - lightskyblue: '87cefa', - lightslateblue: '8470ff', - lightslategray: '778899', - lightsteelblue: 'b0c4de', - lightyellow: 'ffffe0', - lime: '00ff00', - limegreen: '32cd32', - linen: 'faf0e6', - magenta: 'ff00ff', - maroon: '800000', - mediumaquamarine: '66cdaa', - mediumblue: '0000cd', - mediumorchid: 'ba55d3', - mediumpurple: '9370d8', - mediumseagreen: '3cb371', - mediumslateblue: '7b68ee', - mediumspringgreen: '00fa9a', - mediumturquoise: '48d1cc', - mediumvioletred: 'c71585', - midnightblue: '191970', - mintcream: 'f5fffa', - mistyrose: 'ffe4e1', - moccasin: 'ffe4b5', - navajowhite: 'ffdead', - navy: '000080', - oldlace: 'fdf5e6', - olive: '808000', - olivedrab: '6b8e23', - orange: 'ffa500', - orangered: 'ff4500', - orchid: 'da70d6', - palegoldenrod: 'eee8aa', - palegreen: '98fb98', - paleturquoise: 'afeeee', - palevioletred: 'd87093', - papayawhip: 'ffefd5', - peachpuff: 'ffdab9', - peru: 'cd853f', - pink: 'ffc0cb', - plum: 'dda0dd', - powderblue: 'b0e0e6', - purple: '800080', - red: 'ff0000', - rosybrown: 'bc8f8f', - royalblue: '4169e1', - saddlebrown: '8b4513', - salmon: 'fa8072', - sandybrown: 'f4a460', - seagreen: '2e8b57', - seashell: 'fff5ee', - sienna: 'a0522d', - silver: 'c0c0c0', - skyblue: '87ceeb', - slateblue: '6a5acd', - slategray: '708090', - snow: 'fffafa', - springgreen: '00ff7f', - steelblue: '4682b4', - tan: 'd2b48c', - teal: '008080', - thistle: 'd8bfd8', - tomato: 'ff6347', - turquoise: '40e0d0', - violet: 'ee82ee', - violetred: 'd02090', - wheat: 'f5deb3', - white: 'ffffff', - whitesmoke: 'f5f5f5', - yellow: 'ffff00', - yellowgreen: '9acd32' - }; - for (var key in simple_colors) { - if (color_string == key) { - color_string = simple_colors[key]; - } - } - // emd of simple type-in colors - - // array of color definition objects - var color_defs = [ - { - re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, - example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'], - process: function (bits){ - return [ - parseInt(bits[1]), - parseInt(bits[2]), - parseInt(bits[3]) - ]; - } - }, - { - re: /^(\w{2})(\w{2})(\w{2})$/, - example: ['#00ff00', '336699'], - process: function (bits){ - return [ - parseInt(bits[1], 16), - parseInt(bits[2], 16), - parseInt(bits[3], 16) - ]; - } - }, - { - re: /^(\w{1})(\w{1})(\w{1})$/, - example: ['#fb0', 'f0f'], - process: function (bits){ - return [ - parseInt(bits[1] + bits[1], 16), - parseInt(bits[2] + bits[2], 16), - parseInt(bits[3] + bits[3], 16) - ]; - } - } - ]; - - // search through the definitions to find a match - for (var i = 0; i < color_defs.length; i++) { - var re = color_defs[i].re; - var processor = color_defs[i].process; - var bits = re.exec(color_string); - if (bits) { - channels = processor(bits); - this.r = channels[0]; - this.g = channels[1]; - this.b = channels[2]; - this.ok = true; - } - - } - - // validate/cleanup values - this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r); - this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g); - this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b); - - // some getters - this.toRGB = function () { - return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')'; - } - this.toHex = function () { - var r = this.r.toString(16); - var g = this.g.toString(16); - var b = this.b.toString(16); - if (r.length == 1) r = '0' + r; - if (g.length == 1) g = '0' + g; - if (b.length == 1) b = '0' + b; - return '#' + r + g + b; - } - - // help - this.getHelpXML = function () { - - var examples = new Array(); - // add regexps - for (var i = 0; i < color_defs.length; i++) { - var example = color_defs[i].example; - for (var j = 0; j < example.length; j++) { - examples[examples.length] = example[j]; - } - } - // add type-in colors - for (var sc in simple_colors) { - examples[examples.length] = sc; - } - - var xml = document.createElement('ul'); - xml.setAttribute('id', 'rgbcolor-examples'); - for (var i = 0; i < examples.length; i++) { - try { - var list_item = document.createElement('li'); - var list_color = new RGBColor(examples[i]); - var example_div = document.createElement('div'); - example_div.style.cssText = - 'margin: 3px; ' - + 'border: 1px solid black; ' - + 'background:' + list_color.toHex() + '; ' - + 'color:' + list_color.toHex() - ; - example_div.appendChild(document.createTextNode('test')); - var list_item_value = document.createTextNode( - ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex() - ); - list_item.appendChild(example_div); - list_item.appendChild(list_item_value); - xml.appendChild(list_item); - - } catch(e){} - } - return xml; - - } - -} - -/** - * @license canvg.js - Javascript SVG parser and renderer on Canvas - * MIT Licensed - * Gabe Lerner (gabelerner@gmail.com) - * http://code.google.com/p/canvg/ - * - * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/ - * - */ -if(!window.console) { - window.console = {}; - window.console.log = function(str) {}; - window.console.dir = function(str) {}; -} - -if(!Array.prototype.indexOf){ - Array.prototype.indexOf = function(obj){ - for(var i=0; i ignore mouse events - // ignoreAnimation: true => ignore animations - // ignoreDimensions: true => does not try to resize canvas - // ignoreClear: true => does not clear canvas - // offsetX: int => draws at a x offset - // offsetY: int => draws at a y offset - // scaleWidth: int => scales horizontally to width - // scaleHeight: int => scales vertically to height - // renderCallback: function => will call the function after the first render is completed - // forceRedraw: function => will call the function on every frame, if it returns true, will redraw - this.canvg = function (target, s, opts) { - // no parameters - if (target == null && s == null && opts == null) { - var svgTags = document.getElementsByTagName('svg'); - for (var i=0; i]*>/, ''); - var xmlDoc = new ActiveXObject('Microsoft.XMLDOM'); - xmlDoc.async = 'false'; - xmlDoc.loadXML(xml); - return xmlDoc; - } - } - - svg.Property = function(name, value) { - this.name = name; - this.value = value; - - this.hasValue = function() { - return (this.value != null && this.value !== ''); - } - - // return the numerical value of the property - this.numValue = function() { - if (!this.hasValue()) return 0; - - var n = parseFloat(this.value); - if ((this.value + '').match(/%$/)) { - n = n / 100.0; - } - return n; - } - - this.valueOrDefault = function(def) { - if (this.hasValue()) return this.value; - return def; - } - - this.numValueOrDefault = function(def) { - if (this.hasValue()) return this.numValue(); - return def; - } - - /* EXTENSIONS */ - var that = this; - - // color extensions - this.Color = { - // augment the current color value with the opacity - addOpacity: function(opacity) { - var newValue = that.value; - if (opacity != null && opacity != '') { - var color = new RGBColor(that.value); - if (color.ok) { - newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')'; - } - } - return new svg.Property(that.name, newValue); - } - } - - // definition extensions - this.Definition = { - // get the definition from the definitions table - getDefinition: function() { - var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2'); - return svg.Definitions[name]; - }, - - isUrl: function() { - return that.value.indexOf('url(') == 0 - }, - - getFillStyle: function(e) { - var def = this.getDefinition(); - - // gradient - if (def != null && def.createGradient) { - return def.createGradient(svg.ctx, e); - } - - // pattern - if (def != null && def.createPattern) { - return def.createPattern(svg.ctx, e); - } - - return null; - } - } - - // length extensions - this.Length = { - DPI: function(viewPort) { - return 96.0; // TODO: compute? - }, - - EM: function(viewPort) { - var em = 12; - - var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize); - if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort); - - return em; - }, - - // get the length as pixels - toPixels: function(viewPort) { - if (!that.hasValue()) return 0; - var s = that.value+''; - if (s.match(/em$/)) return that.numValue() * this.EM(viewPort); - if (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0; - if (s.match(/px$/)) return that.numValue(); - if (s.match(/pt$/)) return that.numValue() * 1.25; - if (s.match(/pc$/)) return that.numValue() * 15; - if (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54; - if (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4; - if (s.match(/in$/)) return that.numValue() * this.DPI(viewPort); - if (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort); - return that.numValue(); - } - } - - // time extensions - this.Time = { - // get the time as milliseconds - toMilliseconds: function() { - if (!that.hasValue()) return 0; - var s = that.value+''; - if (s.match(/s$/)) return that.numValue() * 1000; - if (s.match(/ms$/)) return that.numValue(); - return that.numValue(); - } - } - - // angle extensions - this.Angle = { - // get the angle as radians - toRadians: function() { - if (!that.hasValue()) return 0; - var s = that.value+''; - if (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0); - if (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0); - if (s.match(/rad$/)) return that.numValue(); - return that.numValue() * (Math.PI / 180.0); - } - } - } - - // fonts - svg.Font = new (function() { - this.Styles = ['normal','italic','oblique','inherit']; - this.Variants = ['normal','small-caps','inherit']; - this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit']; - - this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) { - var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font); - return { - fontFamily: fontFamily || f.fontFamily, - fontSize: fontSize || f.fontSize, - fontStyle: fontStyle || f.fontStyle, - fontWeight: fontWeight || f.fontWeight, - fontVariant: fontVariant || f.fontVariant, - toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') } - } - } - - var that = this; - this.Parse = function(s) { - var f = {}; - var d = svg.trim(svg.compressSpaces(s || '')).split(' '); - var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false } - var ff = ''; - for (var i=0; i this.x2) this.x2 = x; - } - - if (y != null) { - if (isNaN(this.y1) || isNaN(this.y2)) { - this.y1 = y; - this.y2 = y; - } - if (y < this.y1) this.y1 = y; - if (y > this.y2) this.y2 = y; - } - } - this.addX = function(x) { this.addPoint(x, null); } - this.addY = function(y) { this.addPoint(null, y); } - - this.addBoundingBox = function(bb) { - this.addPoint(bb.x1, bb.y1); - this.addPoint(bb.x2, bb.y2); - } - - this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) { - var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0) - var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0) - var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0) - var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0) - this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y); - } - - this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) { - // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html - var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y]; - this.addPoint(p0[0], p0[1]); - this.addPoint(p3[0], p3[1]); - - for (i=0; i<=1; i++) { - var f = function(t) { - return Math.pow(1-t, 3) * p0[i] - + 3 * Math.pow(1-t, 2) * t * p1[i] - + 3 * (1-t) * Math.pow(t, 2) * p2[i] - + Math.pow(t, 3) * p3[i]; - } - - var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; - var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; - var c = 3 * p1[i] - 3 * p0[i]; - - if (a == 0) { - if (b == 0) continue; - var t = -c / b; - if (0 < t && t < 1) { - if (i == 0) this.addX(f(t)); - if (i == 1) this.addY(f(t)); - } - continue; - } - - var b2ac = Math.pow(b, 2) - 4 * c * a; - if (b2ac < 0) continue; - var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); - if (0 < t1 && t1 < 1) { - if (i == 0) this.addX(f(t1)); - if (i == 1) this.addY(f(t1)); - } - var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); - if (0 < t2 && t2 < 1) { - if (i == 0) this.addX(f(t2)); - if (i == 1) this.addY(f(t2)); - } - } - } - - this.isPointInBox = function(x, y) { - return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2); - } - - this.addPoint(x1, y1); - this.addPoint(x2, y2); - } - - // transforms - svg.Transform = function(v) { - var that = this; - this.Type = {} - - // translate - this.Type.translate = function(s) { - this.p = svg.CreatePoint(s); - this.apply = function(ctx) { - ctx.translate(this.p.x || 0.0, this.p.y || 0.0); - } - this.applyToPoint = function(p) { - p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]); - } - } - - // rotate - this.Type.rotate = function(s) { - var a = svg.ToNumberArray(s); - this.angle = new svg.Property('angle', a[0]); - this.cx = a[1] || 0; - this.cy = a[2] || 0; - this.apply = function(ctx) { - ctx.translate(this.cx, this.cy); - ctx.rotate(this.angle.Angle.toRadians()); - ctx.translate(-this.cx, -this.cy); - } - this.applyToPoint = function(p) { - var a = this.angle.Angle.toRadians(); - p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]); - p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]); - p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]); - } - } - - this.Type.scale = function(s) { - this.p = svg.CreatePoint(s); - this.apply = function(ctx) { - ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0); - } - this.applyToPoint = function(p) { - p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]); - } - } - - this.Type.matrix = function(s) { - this.m = svg.ToNumberArray(s); - this.apply = function(ctx) { - ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]); - } - this.applyToPoint = function(p) { - p.applyTransform(this.m); - } - } - - this.Type.SkewBase = function(s) { - this.base = that.Type.matrix; - this.base(s); - this.angle = new svg.Property('angle', s); - } - this.Type.SkewBase.prototype = new this.Type.matrix; - - this.Type.skewX = function(s) { - this.base = that.Type.SkewBase; - this.base(s); - this.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0]; - } - this.Type.skewX.prototype = new this.Type.SkewBase; - - this.Type.skewY = function(s) { - this.base = that.Type.SkewBase; - this.base(s); - this.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0]; - } - this.Type.skewY.prototype = new this.Type.SkewBase; - - this.transforms = []; - - this.apply = function(ctx) { - for (var i=0; i= this.tokens.length - 1; - } - - this.isCommandOrEnd = function() { - if (this.isEnd()) return true; - return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null; - } - - this.isRelativeCommand = function() { - return this.command == this.command.toLowerCase(); - } - - this.getToken = function() { - this.i = this.i + 1; - return this.tokens[this.i]; - } - - this.getScalar = function() { - return parseFloat(this.getToken()); - } - - this.nextCommand = function() { - this.previousCommand = this.command; - this.command = this.getToken(); - } - - this.getPoint = function() { - var p = new svg.Point(this.getScalar(), this.getScalar()); - return this.makeAbsolute(p); - } - - this.getAsControlPoint = function() { - var p = this.getPoint(); - this.control = p; - return p; - } - - this.getAsCurrentPoint = function() { - var p = this.getPoint(); - this.current = p; - return p; - } - - this.getReflectedControlPoint = function() { - if (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') { - return this.current; - } - - // reflect point - var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y); - return p; - } - - this.makeAbsolute = function(p) { - if (this.isRelativeCommand()) { - p.x = this.current.x + p.x; - p.y = this.current.y + p.y; - } - return p; - } - - this.addMarker = function(p, from, priorTo) { - // if the last angle isn't filled in because we didn't have this point yet ... - if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) { - this.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo); - } - this.addMarkerAngle(p, from == null ? null : from.angleTo(p)); - } - - this.addMarkerAngle = function(p, a) { - this.points.push(p); - this.angles.push(a); - } - - this.getMarkerPoints = function() { return this.points; } - this.getMarkerAngles = function() { - for (var i=0; i 1) { - rx *= Math.sqrt(l); - ry *= Math.sqrt(l); - } - // cx', cy' - var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt( - ((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) / - (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2)) - ); - if (isNaN(s)) s = 0; - var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx); - // cx, cy - var centp = new svg.Point( - (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y, - (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y - ); - // vector magnitude - var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); } - // ratio between two vectors - var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) } - // angle between two vectors - var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); } - // initial angle - var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]); - // angle delta - var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]; - var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry]; - var ad = a(u, v); - if (r(u,v) <= -1) ad = Math.PI; - if (r(u,v) >= 1) ad = 0; - - if (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI; - if (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI; - - // for markers - var halfWay = new svg.Point( - centp.x - rx * Math.cos((a1 + ad) / 2), - centp.y - ry * Math.sin((a1 + ad) / 2) - ); - pp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2); - pp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2); - - bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better - if (ctx != null) { - var r = rx > ry ? rx : ry; - var sx = rx > ry ? 1 : rx / ry; - var sy = rx > ry ? ry / rx : 1; - - ctx.translate(centp.x, centp.y); - ctx.rotate(xAxisRotation); - ctx.scale(sx, sy); - ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag); - ctx.scale(1/sx, 1/sy); - ctx.rotate(-xAxisRotation); - ctx.translate(-centp.x, -centp.y); - } - } - break; - case 'Z': - if (ctx != null) ctx.closePath(); - pp.current = pp.start; - } - } - - return bb; - } - - this.getMarkers = function() { - var points = this.PathParser.getMarkerPoints(); - var angles = this.PathParser.getMarkerAngles(); - - var markers = []; - for (var i=0; i this.maxDuration) { - // loop for indefinitely repeating animations - if (this.attribute('repeatCount').value == 'indefinite') { - this.duration = 0.0 - } - else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) { - this.removed = true; - this.getProperty().value = this.initialValue; - return true; - } - else { - return false; // no updates made - } - } - this.duration = this.duration + delta; - - // if we're past the begin time - var updated = false; - if (this.begin < this.duration) { - var newValue = this.calcValue(); // tween - - if (this.attribute('type').hasValue()) { - // for transform, etc. - var type = this.attribute('type').value; - newValue = type + '(' + newValue + ')'; - } - - this.getProperty().value = newValue; - updated = true; - } - - return updated; - } - - // fraction of duration we've covered - this.progress = function() { - return ((this.duration - this.begin) / (this.maxDuration - this.begin)); - } - } - svg.Element.AnimateBase.prototype = new svg.Element.ElementBase; - - // animate element - svg.Element.animate = function(node) { - this.base = svg.Element.AnimateBase; - this.base(node); - - this.calcValue = function() { - var from = this.attribute('from').numValue(); - var to = this.attribute('to').numValue(); - - // tween value linearly - return from + (to - from) * this.progress(); - }; - } - svg.Element.animate.prototype = new svg.Element.AnimateBase; - - // animate color element - svg.Element.animateColor = function(node) { - this.base = svg.Element.AnimateBase; - this.base(node); - - this.calcValue = function() { - var from = new RGBColor(this.attribute('from').value); - var to = new RGBColor(this.attribute('to').value); - - if (from.ok && to.ok) { - // tween color linearly - var r = from.r + (to.r - from.r) * this.progress(); - var g = from.g + (to.g - from.g) * this.progress(); - var b = from.b + (to.b - from.b) * this.progress(); - return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')'; - } - return this.attribute('from').value; - }; - } - svg.Element.animateColor.prototype = new svg.Element.AnimateBase; - - // animate transform element - svg.Element.animateTransform = function(node) { - this.base = svg.Element.animate; - this.base(node); - } - svg.Element.animateTransform.prototype = new svg.Element.animate; - - // font element - svg.Element.font = function(node) { - this.base = svg.Element.ElementBase; - this.base(node); - - this.horizAdvX = this.attribute('horiz-adv-x').numValue(); - - this.isRTL = false; - this.isArabic = false; - this.fontFace = null; - this.missingGlyph = null; - this.glyphs = []; - for (var i=0; i0 && text[i-1]!=' ' && i0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial'; - if (typeof(font.glyphs[c]) != 'undefined') { - glyph = font.glyphs[c][arabicForm]; - if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c]; - } - } - else { - glyph = font.glyphs[c]; - } - if (glyph == null) glyph = font.missingGlyph; - return glyph; - } - - this.renderChildren = function(ctx) { - var customFont = this.parent.style('font-family').Definition.getDefinition(); - if (customFont != null) { - var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize); - var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle); - var text = this.getText(); - if (customFont.isRTL) text = text.split("").reverse().join(""); - - var dx = svg.ToNumberArray(this.parent.attribute('dx').value); - for (var i=0; i 0 ? node.childNodes[0].nodeValue : // element - node.text; - this.getText = function() { - return this.text; - } - } - svg.Element.tspan.prototype = new svg.Element.TextElementBase; - - // tref - svg.Element.tref = function(node) { - this.base = svg.Element.TextElementBase; - this.base(node); - - this.getText = function() { - var element = this.attribute('xlink:href').Definition.getDefinition(); - if (element != null) return element.children[0].getText(); - } - } - svg.Element.tref.prototype = new svg.Element.TextElementBase; - - // a element - svg.Element.a = function(node) { - this.base = svg.Element.TextElementBase; - this.base(node); - - this.hasText = true; - for (var i=0; i 1 ? node.childNodes[1].nodeValue : ''); - css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, ''); // remove comments - css = svg.compressSpaces(css); // replace whitespace - var cssDefs = css.split('}'); - for (var i=0; i 0) { - var urlStart = srcs[s].indexOf('url'); - var urlEnd = srcs[s].indexOf(')', urlStart); - var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6); - var doc = svg.parseXml(svg.ajax(url)); - var fonts = doc.getElementsByTagName('font'); - for (var f=0; f

+ + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivity.java new file mode 100755 index 00000000000..cf5dd2d8dd4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivity.java @@ -0,0 +1,41 @@ +package com.sap.sailing.gwt.home.client.place.events; + +import java.util.List; + +import com.google.gwt.activity.shared.AbstractActivity; +import com.google.gwt.event.shared.EventBus; +import com.google.gwt.place.shared.Place; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class EventsActivity extends AbstractActivity { + + private final EventsClientFactory clientFactory; + + public EventsActivity(EventsPlace place, EventsClientFactory clientFactory) { + this.clientFactory = clientFactory; + } + + @Override + public void start(final AcceptsOneWidget panel, EventBus eventBus) { + final EventsView eventsView = clientFactory.createEventsView(EventsActivity.this); + panel.setWidget(eventsView.asWidget()); + clientFactory.getSailingService().getEvents(new AsyncCallback>() { + @Override + public void onSuccess(List events) { + eventsView.setEvents(events); + } + + @Override + public void onFailure(Throwable caught) { + Window.alert("Shit happens"); + } + }); + } + + void goTo(Place place) { + clientFactory.getPlaceController().goTo(place); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivityProxy.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivityProxy.java new file mode 100755 index 00000000000..4c461a8fd4c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivityProxy.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.home.client.place.events; + +import com.google.gwt.core.client.GWT; +import com.sap.sse.gwt.client.mvp.AbstractActivityProxy; + +public class EventsActivityProxy extends AbstractActivityProxy { + + private final EventsClientFactory clientFactory; + private final EventsPlace place; + + public EventsActivityProxy(EventsPlace place, EventsClientFactory clientFactory) { + this.place = place; + this.clientFactory = clientFactory; + } + + @Override + protected void startAsync() { + GWT.runAsync(new AbstractRunAsyncCallback() { + @Override + public void onSuccess() { + super.onSuccess(new EventsActivity(place, clientFactory)); + } + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsClientFactory.java new file mode 100755 index 00000000000..83bb3827b25 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsClientFactory.java @@ -0,0 +1,7 @@ +package com.sap.sailing.gwt.home.client.place.events; + +import com.sap.sailing.gwt.ui.client.SailingClientFactory; + +public interface EventsClientFactory extends SailingClientFactory { + EventsView createEventsView(EventsActivity activity); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsPlace.java new file mode 100755 index 00000000000..009d97886f6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsPlace.java @@ -0,0 +1,18 @@ +package com.sap.sailing.gwt.home.client.place.events; + +import com.google.gwt.place.shared.Place; +import com.google.gwt.place.shared.PlaceTokenizer; + +public class EventsPlace extends Place { + public static class Tokenizer implements PlaceTokenizer { + @Override + public String getToken(EventsPlace place) { + return null; + } + + @Override + public EventsPlace getPlace(String token) { + return new EventsPlace(); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsTable.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsTable.java new file mode 100644 index 00000000000..468e715c9cd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsTable.java @@ -0,0 +1,139 @@ +package com.sap.sailing.gwt.home.client.place.events; + +import java.util.Comparator; +import java.util.UUID; + +import com.google.gwt.cell.client.CheckboxCell; +import com.google.gwt.cell.client.FieldUpdater; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +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.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +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.SimplePager; +import com.google.gwt.user.cellview.client.SimplePager.TextLocation; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.view.client.DefaultSelectionEventManager; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.ProvidesKey; +import com.google.gwt.view.client.SelectionModel; +import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sailing.gwt.home.client.place.event.EventPlace; +import com.sap.sailing.gwt.ui.shared.ClickableSafeHtmlCell; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.common.Util; + +public class EventsTable extends Composite { + private static EventsTableUiBinder uiBinder = GWT.create(EventsTableUiBinder.class); + + interface EventsTableUiBinder extends UiBinder { + } + + private ListDataProvider dataProvider = new ListDataProvider(); + + @UiField(provided = true) + CellTable cellTable; + + /** + * The pager used to change the range of data. + */ + @UiField(provided = true) + SimplePager pager; + + private final EventsActivity activity; + + /** + * Constructor. + * + * @param constants + * the constants + */ + public EventsTable(EventsActivity activity) { + this.activity = activity; + initTable(); + initWidget(uiBinder.createAndBindUi(this)); + } + + public static final ProvidesKey KEY_PROVIDER = new ProvidesKey() { + public UUID getKey(EventDTO item) { + return item == null ? null : item.id; + } + }; + + public void initTable() { + cellTable = new CellTable(KEY_PROVIDER); + cellTable.setWidth("100%", true); + + ListHandler sortHandler = new ListHandler(dataProvider.getList()); + cellTable.addColumnSortHandler(sortHandler); + + // Create a Pager to control the table. + SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class); + pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 0, true); + pager.setDisplay(cellTable); + + // Add a selection model so we can select cells. + final SelectionModel selectionModel = new SingleSelectionModel(KEY_PROVIDER); + cellTable.setSelectionModel(selectionModel, DefaultSelectionEventManager. createCheckboxManager()); + + // Initialize the columns. + initTableColumns(selectionModel, sortHandler); + + // Add the CellList to the adapter in the database. + dataProvider.addDataDisplay(cellTable); + } + + public void setEvents(Iterable events) { + dataProvider.getList().clear(); + Util.addAll(events, dataProvider.getList()); + } + + /** + * Add the columns to the table. + */ + private void initTableColumns(final SelectionModel selectionModel, ListHandler sortHandler) { + // Checkbox column. This table will uses a checkbox column for selection. + // Alternatively, you can call cellTable.setSelectionEnabled(true) to enable mouse selection. + Column checkColumn = new Column(new CheckboxCell(true, false)) { + @Override + public Boolean getValue(EventDTO object) { + // Get the value from the selection model. + return selectionModel.isSelected(object); + } + }; + cellTable.addColumn(checkColumn, SafeHtmlUtils.fromSafeConstant("
")); + cellTable.setColumnWidth(checkColumn, 40, Unit.PX); + + // name. + Column nameColumn = new Column(new ClickableSafeHtmlCell()) { + @Override + public SafeHtml getValue(EventDTO event) { + final SafeHtmlBuilder safeHtmlBuilder = new SafeHtmlBuilder(); + safeHtmlBuilder.appendHtmlConstant("
"); + safeHtmlBuilder.appendEscaped(event.getName()); + safeHtmlBuilder.appendHtmlConstant(""); + return safeHtmlBuilder.toSafeHtml(); + } + }; + nameColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, EventDTO object, SafeHtml value) { + activity.goTo(new EventPlace(object.id.toString())); + } + }); + nameColumn.setSortable(true); + sortHandler.setComparator(nameColumn, new Comparator() { + public int compare(EventDTO o1, EventDTO o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + cellTable.addColumn(nameColumn, "name"); + cellTable.setColumnWidth(nameColumn, 20, Unit.PCT); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsTable.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsTable.ui.xml new file mode 100644 index 00000000000..9c7d9557968 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsTable.ui.xml @@ -0,0 +1,29 @@ + + + + + .cellTable { + border-bottom: 1px solid #ccc; + text-align: left; + margin-bottom: 4px; + } + + + + + + + + + + +
+ +
+ +
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsView.java new file mode 100755 index 00000000000..4ca97b1c79e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsView.java @@ -0,0 +1,10 @@ +package com.sap.sailing.gwt.home.client.place.events; + +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public interface EventsView { + Widget asWidget(); + + void setEvents(Iterable events); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.java new file mode 100644 index 00000000000..3f172c40f72 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.java @@ -0,0 +1,61 @@ +package com.sap.sailing.gwt.home.client.place.events; + +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.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.place.event.EventPlace; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class TabletAndDesktopEventsView extends Composite implements EventsView { + private static EventsPageViewUiBinder uiBinder = GWT.create(EventsPageViewUiBinder.class); + + interface EventsPageViewUiBinder extends UiBinder { + } + + private final EventsActivity activity; + + @UiField + TextBox queryInput; + @UiField + Button searchButton; + @UiField + FlowPanel eventListPanel; + @UiField(provided=true) + EventsTable eventsTable; + + public TabletAndDesktopEventsView(EventsActivity activity) { + super(); + this.activity = activity; + eventsTable = new EventsTable(activity); + initWidget(uiBinder.createAndBindUi(this)); + queryInput.getElement().setId("queryInput"); + } + + public void setEvents(Iterable events) { + eventListPanel.clear(); + for (final EventDTO event : events) { + Anchor request = new Anchor(event.getName()); + request.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent e) { + TabletAndDesktopEventsView.this.activity.goTo(new EventPlace(event.id.toString())); + } + }); + eventListPanel.add(request); + } + eventsTable.setEvents(events); + } + + @UiHandler("searchButton") + void buttonClick(ClickEvent event) { + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.ui.xml new file mode 100644 index 00000000000..4fd3f4ebab5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.ui.xml @@ -0,0 +1,30 @@ + + + + .panel { + width: 100%; + background-color: rgb(255,255,255); + border: solid 1px black; + } + + + + + Events page +
+

Search event:

+ + + +
+
+

Events:

+ +
+ +
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.java new file mode 100644 index 00000000000..c0ce2f90355 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.java @@ -0,0 +1,45 @@ +package com.sap.sailing.gwt.home.client.place.start; + +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.place.shared.PlaceController; +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.Widget; +import com.sap.sailing.gwt.home.client.shared.mainevents.MainEvents; +import com.sap.sailing.gwt.home.client.shared.mainmedia.MainMedia; +import com.sap.sailing.gwt.home.client.shared.mainsponsors.MainSponsors; +import com.sap.sailing.gwt.home.client.shared.socialfooter.SocialFooter; +import com.sap.sailing.gwt.home.client.shared.stage.Stage; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class SmartphoneStartView extends Composite implements StartView { + private static StartPageMobileViewUiBinder uiBinder = GWT.create(StartPageMobileViewUiBinder.class); + + interface StartPageMobileViewUiBinder extends UiBinder { + } + + @UiField Stage stage; + @UiField MainSponsors mainSponsors; + @UiField(provided=true) MainEvents mainEvents; + @UiField MainMedia mainMedia; + @UiField SocialFooter socialFooter; + + public SmartphoneStartView(PlaceController placeController) { + super(); + mainEvents = new MainEvents(placeController); + initWidget(uiBinder.createAndBindUi(this)); + } + + @Override + public void setFeaturedEvent(EventDTO featuredEvent) { + stage.setFeaturedEvent(featuredEvent); + } + + @Override + public void setRecentEvents(List recentEvents) { + mainEvents.setRecentEvents(recentEvents); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.ui.xml new file mode 100644 index 00000000000..750f7d032ac --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.ui.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java new file mode 100755 index 00000000000..e3641f43a9c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java @@ -0,0 +1,57 @@ +package com.sap.sailing.gwt.home.client.place.start; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.google.gwt.activity.shared.AbstractActivity; +import com.google.gwt.event.shared.EventBus; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class StartActivity extends AbstractActivity { + private final StartClientFactory clientFactory; + + public StartActivity(StartPlace place, StartClientFactory clientFactory) { + this.clientFactory = clientFactory; + } + + @Override + public void start(AcceptsOneWidget panel, EventBus eventBus) { + final StartView view = clientFactory.createStartView(); + panel.setWidget(view.asWidget()); + + clientFactory.getSailingService().getEvents(new AsyncCallback>() { + @Override + public void onSuccess(List result) { + fillStartPageEvents(view, result); + } + + @Override + public void onFailure(Throwable caught) { + } + }); + } + + @SuppressWarnings("deprecation") + protected void fillStartPageEvents(StartView view, List events) { + EventDTO featuredEvent = null; + List recentEvents = new ArrayList(); + Date now = new Date(); + int currentYear = now.getYear(); + + for(EventDTO event: events) { + if(event.startDate != null && event.endDate != null) { + if(featuredEvent == null && event.startDate.after(now)) { + featuredEvent = event; + view.setFeaturedEvent(event); + } + if(event.endDate.before(now) && event.endDate.getYear() == currentYear) { + recentEvents.add(event); + } + } + } + view.setRecentEvents(recentEvents); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivityProxy.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivityProxy.java new file mode 100755 index 00000000000..38c9a2dfa03 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivityProxy.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.home.client.place.start; + +import com.google.gwt.core.client.GWT; +import com.sap.sse.gwt.client.mvp.AbstractActivityProxy; + +public class StartActivityProxy extends AbstractActivityProxy { + + private final StartClientFactory clientFactory; + private final StartPlace place; + + public StartActivityProxy(StartPlace place, StartClientFactory clientFactory) { + this.place = place; + this.clientFactory = clientFactory; + } + + @Override + protected void startAsync() { + GWT.runAsync(new AbstractRunAsyncCallback() { + @Override + public void onSuccess() { + super.onSuccess(new StartActivity(place, clientFactory)); + } + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartClientFactory.java new file mode 100755 index 00000000000..182c1f48715 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartClientFactory.java @@ -0,0 +1,7 @@ +package com.sap.sailing.gwt.home.client.place.start; + +import com.sap.sailing.gwt.ui.client.SailingClientFactory; + +public interface StartClientFactory extends SailingClientFactory { + StartView createStartView(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartPlace.java new file mode 100755 index 00000000000..69e494d1a78 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartPlace.java @@ -0,0 +1,18 @@ +package com.sap.sailing.gwt.home.client.place.start; + +import com.google.gwt.place.shared.Place; +import com.google.gwt.place.shared.PlaceTokenizer; + +public class StartPlace extends Place { + public static class Tokenizer implements PlaceTokenizer { + @Override + public String getToken(StartPlace place) { + return null; + } + + @Override + public StartPlace getPlace(String token) { + return new StartPlace(); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartView.java new file mode 100755 index 00000000000..e14799f0990 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartView.java @@ -0,0 +1,14 @@ +package com.sap.sailing.gwt.home.client.place.start; + +import java.util.List; + +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public interface StartView { + Widget asWidget(); + + void setFeaturedEvent(EventDTO featuredEvent); + + void setRecentEvents(List recentEvents); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.java new file mode 100644 index 00000000000..9d090cc997d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.java @@ -0,0 +1,45 @@ +package com.sap.sailing.gwt.home.client.place.start; + +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.place.shared.PlaceController; +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.Widget; +import com.sap.sailing.gwt.home.client.shared.mainevents.MainEvents; +import com.sap.sailing.gwt.home.client.shared.mainmedia.MainMedia; +import com.sap.sailing.gwt.home.client.shared.mainsponsors.MainSponsors; +import com.sap.sailing.gwt.home.client.shared.socialfooter.SocialFooter; +import com.sap.sailing.gwt.home.client.shared.stage.Stage; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class TabletAndDesktopStartView extends Composite implements StartView { + private static StartPageViewUiBinder uiBinder = GWT.create(StartPageViewUiBinder.class); + + interface StartPageViewUiBinder extends UiBinder { + } + + @UiField Stage stage; + @UiField MainSponsors mainSponsors; + + @UiField(provided=true) MainEvents mainEvents; + @UiField MainMedia mainMedia; + @UiField SocialFooter socialFooter; + + public TabletAndDesktopStartView(PlaceController placeController) { + mainEvents = new MainEvents(placeController); + initWidget(uiBinder.createAndBindUi(this)); + } + + @Override + public void setFeaturedEvent(EventDTO featuredEvent) { + stage.setFeaturedEvent(featuredEvent); + } + + @Override + public void setRecentEvents(List recentEvents) { + mainEvents.setRecentEvents(recentEvents); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.ui.xml new file mode 100644 index 00000000000..750f7d032ac --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.ui.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/EventDatesFormatterUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/EventDatesFormatterUtil.java new file mode 100644 index 00000000000..8fb8118c819 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/EventDatesFormatterUtil.java @@ -0,0 +1,42 @@ +package com.sap.sailing.gwt.home.client.shared; + +import java.util.Date; + +import com.google.gwt.i18n.client.DateTimeFormat; + +/** + * Formatter for event dates and date ranges + * @author Frank + * + */ +public class EventDatesFormatterUtil { + private final static DateTimeFormat dayFormat = DateTimeFormat.getFormat("dd"); + private final static DateTimeFormat dayAndMonthFormat = DateTimeFormat.getFormat("MMMM dd"); + private final static DateTimeFormat monthFormat = DateTimeFormat.getFormat("MMMM"); + private final static DateTimeFormat yearFormat = DateTimeFormat.getFormat("yyyy"); + + @SuppressWarnings("deprecation") + public static String formatDateRangeWithYear(Date from, Date to) { + String result = ""; + if(from.getMonth() == to.getMonth()) { + result = monthFormat.format(from) + " " + dayFormat.format(from) + " - " + dayFormat.format(to) + " " + yearFormat.format(from); + } else { + result = dayAndMonthFormat.format(from) + " - " + dayAndMonthFormat.format(to) + " " + yearFormat.format(from); + } + + return result; + } + + @SuppressWarnings("deprecation") + public static String formatDateRangeWithoutYear(Date from, Date to) { + String result = ""; + if(from.getMonth() == to.getMonth()) { + result = monthFormat.format(from) + " " + dayFormat.format(from) + " - " + dayFormat.format(to); + } else { + result = dayAndMonthFormat.format(from) + " - " + dayAndMonthFormat.format(to); + } + + return result; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/LocaleSelection.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/LocaleSelection.java new file mode 100644 index 00000000000..4cec9b4bb99 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/LocaleSelection.java @@ -0,0 +1,33 @@ +package com.sap.sailing.gwt.home.client.shared; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.Widget; + +public class LocaleSelection extends Composite { + private static LocaleSelectionCompositeUiBinder uiBinder = GWT.create(LocaleSelectionCompositeUiBinder.class); + + interface LocaleSelectionCompositeUiBinder extends UiBinder { + } + + @UiField(provided = true) + ListBox localeSelection; + + public LocaleSelection() { + localeSelection = new ListBox(false); + localeSelection.addItem("Deutsch"); + localeSelection.addItem("English"); + initWidget(uiBinder.createAndBindUi(this)); + } + + @UiHandler("localeSelection") + void onLocaleSelectionChange(ChangeEvent event) { + Window.alert(localeSelection.getItemText(localeSelection.getSelectedIndex())); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/LocaleSelection.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/LocaleSelection.ui.xml new file mode 100644 index 00000000000..9632596b1e1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/LocaleSelection.ui.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.css new file mode 100644 index 00000000000..9e61ac590db --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.css @@ -0,0 +1,115 @@ +/** + * The overview over events + */ +.eventteaser { + font-size: 16px; + font-size: 1rem; + display: inline-block; + margin-bottom: 2.8125em; +} +.eventteaser:hover { + text-decoration: none; +} +.eventteaser:hover .eventteaser_name { + text-decoration: underline; +} +.eventteaser:hover .eventteaser_image { + opacity: 0.9; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; + filter: alpha(opacity=90); +} +.eventteaser_image { + position: relative; +} +.eventteaser_series { + position: absolute; + bottom: 0; + right: 0; +} +.eventteaser_name { + font-size: 1.375em; + font-weight: bold; + color: #008fcc; + margin-top: 0.3125em; +} +.eventteaser_location { + color: #222; +} +.eventsoverview { + font-size: 16px; + font-size: 1rem; +} +.eventsoverview_navigation { + margin: 1.875em 0; +} +.eventsoverview_navigation_button { + text-transform: uppercase; + margin-right: 0.625em; +} +.eventsoverview_contenthidden { + display: none; +} +.eventsoverview_contentrecent { + padding-bottom: 1.875em; +} +.eventsoverview_contentrecent_year { + border-top: 1px solid #ddd; + margin-top: 0.625em; +} +.eventsoverview_contentupcoming { + padding-bottom: 4.6875em; +} +.eventsoverview_contentupcoming_teaser { + font-size: 1.125em; + font-weight: bold; + color: #008fcc; + margin-top: 1.6875em; +} +.eventslist { + font-size: 16px; + font-size: 1rem; +} +.eventslist_header { + font-size: 1em; + font-weight: 600; + text-transform: uppercase; + color: #008fcc; + background: #f2f2f2; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} +.eventslist_header_item { + padding-top: 1.875em; + padding-bottom: 1.75em; +} +.eventslist_header_itemseries { + text-align: right; +} +.eventslist_event { + background: #fff; + border-bottom: 1px solid #ddd; + -webkit-transition: 0.2s background; + -moz-transition: 0.2s background; + -o-transition: 0.2s background; + -ms-transition: 0.2s background; + transition: 0.2s background; +} +.eventslist_event:hover, +.eventslist_event:focus { + background: #fafafa; +} +.eventslist_event:hover .eventslist_event_itemname, +.eventslist_event:focus .eventslist_event_itemname { + text-decoration: underline; +} +.eventslist_event_link { + color: #333; + padding-top: 1.875em; + padding-bottom: 1.75em; +} +.eventslist_event_item { + font-size: 1.125em; +} +.eventslist_event_itemseries { + text-align: right; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.java new file mode 100644 index 00000000000..24e82d87e89 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.home.client.shared.eventsoverview; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class EventsOverview extends Composite { + + interface EventsOverviewUiBinder extends UiBinder { + } + + private static EventsOverviewUiBinder uiBinder = GWT.create(EventsOverviewUiBinder.class); + + public EventsOverview() { + EventsOverviewResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.ui.xml new file mode 100644 index 00000000000..fe0a87de16b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.ui.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverviewResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverviewResources.java new file mode 100644 index 00000000000..0d60f5056fc --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverviewResources.java @@ -0,0 +1,38 @@ +package com.sap.sailing.gwt.home.client.shared.eventsoverview; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface EventsOverviewResources extends ClientBundle { + public static final EventsOverviewResources INSTANCE = GWT.create(EventsOverviewResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/eventsoverview/EventsOverview.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String eventteaser(); + String eventteaser_name(); + String eventteaser_image(); + String Microsoft(); + String eventteaser_series(); + String eventteaser_location(); + String eventsoverview(); + String eventsoverview_navigation(); + String eventsoverview_navigation_button(); + String eventsoverview_contenthidden(); + String eventsoverview_contentrecent(); + String eventsoverview_contentrecent_year(); + String eventsoverview_contentupcoming(); + String eventsoverview_contentupcoming_teaser(); + String eventslist(); + String eventslist_header(); + String eventslist_header_item(); + String eventslist_header_itemseries(); + String eventslist_event(); + String eventslist_event_itemname(); + String eventslist_event_link(); + String eventslist_event_item(); + String eventslist_event_itemseries(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.css new file mode 100644 index 00000000000..c18438b66af --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.css @@ -0,0 +1,31 @@ +/** + * Website footer styling + */ +.sitefooter { + font-size: 16px; + font-size: 1rem; + background: #333; + border-top: 5px solid #fff; + color: #fff; + height: 4.6875em; + line-height: 4.375em; + width: 100%; +} +.sitefooter_copyright { + font-weight: 600; +} +.sitefooter_links { + text-align: right; + font-size: 0.9375em; + font-weight: 600; + text-transform: uppercase; +} +.sitefooter_links_link { + display: inline-block; + color: #fff; + text-decoration: none; + margin-right: 3.75em; +} +.sitefooter_links_link:last-child { + margin-right: 0; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.java new file mode 100644 index 00000000000..1fe7d54eb3c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.java @@ -0,0 +1,18 @@ +package com.sap.sailing.gwt.home.client.shared.footer; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class Footer extends Composite { + private static FooterPanelUiBinder uiBinder = GWT.create(FooterPanelUiBinder.class); + + interface FooterPanelUiBinder extends UiBinder { + } + + public Footer() { + FooterResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.ui.xml new file mode 100644 index 00000000000..89eaa7f0e98 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.ui.xml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java new file mode 100644 index 00000000000..4f768e0432f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java @@ -0,0 +1,19 @@ +package com.sap.sailing.gwt.home.client.shared.footer; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface FooterResources extends ClientBundle { + public static final FooterResources INSTANCE = GWT.create(FooterResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/footer/Footer.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String sitefooter(); + String sitefooter_copyright(); + String sitefooter_links(); + String sitefooter_links_link(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css new file mode 100755 index 00000000000..70230708216 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css @@ -0,0 +1,40 @@ +/** + * Website header bar + */ +.siteheader { + font-size: 16px; + font-size: 1rem; + background: #333; + border-bottom: 5px solid #f0ab00; + height: 4.0625em; + line-height: 3.75em; + width: 100%; +} +.logo { + margin: 0.625em 0; + float: left; +} +.sitenavigation { + float: right; +} +.sitenavigation_link { + display: inline-block; + font-size: 0.875em; + font-weight: 600; + color: #f6f9fc; + text-transform: uppercase; + padding: 0 2.1875em; +} +.sitenavigation_link:hover { + background-color: rgba(246,249,252,0.1); + text-decoration: none; +} +.sitenavigation_linksearch img { + vertical-align: middle; +} +.sitenavigation_active { + background: #f0ab00; +} +.sitenavigation_active:hover { + background-color: rgba(240,171,0,0.9); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.java new file mode 100644 index 00000000000..5c06dadcd0f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.java @@ -0,0 +1,76 @@ +package com.sap.sailing.gwt.home.client.shared.header; + +import java.util.Arrays; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class Header extends Composite { + @UiField + Anchor startPageLink; + + @UiField + Anchor eventsPageLink; + + @UiField + Anchor aboutUsPageLink; + + @UiField + Anchor contactPageLink; + + private final List links; + + private final MainMenuNavigator navigator; + + interface HeaderUiBinder extends UiBinder { + } + + private static HeaderUiBinder uiBinder = GWT.create(HeaderUiBinder.class); + + public Header(MainMenuNavigator navigator) { + this.navigator = navigator; + HeaderResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + links = Arrays.asList(new Anchor[] { startPageLink, eventsPageLink, aboutUsPageLink, contactPageLink }); + } + + @UiHandler("startPageLink") + public void goToHome(ClickEvent e) { + navigator.goToHome(); + setActiveLink(startPageLink); + } + + @UiHandler("eventsPageLink") + public void goToEvents(ClickEvent e) { + navigator.goToEvents(); + } + + @UiHandler("aboutUsPageLink") + public void goToAboutUs(ClickEvent e) { + navigator.goToAboutUs(); + } + + @UiHandler("contactPageLink") + public void goToContact(ClickEvent e) { + navigator.goToContact(); + } + + private void setActiveLink(Anchor link) { + final String activeStyle = HeaderResources.INSTANCE.css().sitenavigation_active(); + for (Anchor l : links) { + if (l == link) { + l.addStyleName(activeStyle); + } else { + l.removeStyleName(activeStyle); + } + } + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.ui.xml new file mode 100644 index 00000000000..4ee59ef113c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.ui.xml @@ -0,0 +1,32 @@ + + + + + + + +
+
+
+ +
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java new file mode 100755 index 00000000000..ada705c1ea6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java @@ -0,0 +1,28 @@ +package com.sap.sailing.gwt.home.client.shared.header; + +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 HeaderResources extends ClientBundle { + public static final HeaderResources INSTANCE = GWT.create(HeaderResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/header/Header.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String siteheader(); + String logo(); + String sitenavigation(); + String sitenavigation_active(); + String sitenavigation_link(); + String sitenavigation_linksearch(); + } + + @Source("com/sap/sailing/gwt/home/images/logo.png") + ImageResource logo(); + + @Source("com/sap/sailing/gwt/home/images/search-icon.png") + ImageResource searchIcon(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/MainMenuNavigator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/MainMenuNavigator.java new file mode 100755 index 00000000000..63feb694b65 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/MainMenuNavigator.java @@ -0,0 +1,9 @@ +package com.sap.sailing.gwt.home.client.shared.header; + +public interface MainMenuNavigator { + void goToHome(); + void goToEvent(String eventUuidAsString); + void goToEvents(); + void goToAboutUs(); + void goToContact(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.css new file mode 100644 index 00000000000..5364ee74f17 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.css @@ -0,0 +1,5 @@ +.mainevents { + font-size: 16px; + font-size: 1rem; + position: relative; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.java new file mode 100644 index 00000000000..98958403ca9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.java @@ -0,0 +1,52 @@ +package com.sap.sailing.gwt.home.client.shared.mainevents; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.place.shared.PlaceController; +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.Widget; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class MainEvents extends Composite { + + private List recentEvents; + + @UiField(provided=true) RecentEvent event1; + @UiField(provided=true) RecentEvent event2; + @UiField(provided=true) RecentEvent event3; + + interface MainEventsUiBinder extends UiBinder { + } + + private static MainEventsUiBinder uiBinder = GWT.create(MainEventsUiBinder.class); + + public MainEvents(PlaceController placeController) { + event1 = new RecentEvent(placeController); + event2 = new RecentEvent(placeController); + event3 = new RecentEvent(placeController); + MainEventsResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + recentEvents = new ArrayList(); + } + + public void setRecentEvents(List theRecentEvents) { + recentEvents.clear(); + recentEvents.addAll(theRecentEvents); + + int size = recentEvents.size(); + if(size > 0) { + event1.setEvent(recentEvents.get(0)); + } + if(size > 1) { + event2.setEvent(recentEvents.get(1)); + } + if(size > 2) { + event3.setEvent(recentEvents.get(2)); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.ui.xml new file mode 100644 index 00000000000..b561bf67d30 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.ui.xml @@ -0,0 +1,28 @@ + + + + + + + +
+
+
+
+

Latest Events from the Sailing World

+
+
+
+
+ + + +
+ +
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEventsResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEventsResources.java new file mode 100644 index 00000000000..3c9662e2d47 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEventsResources.java @@ -0,0 +1,16 @@ +package com.sap.sailing.gwt.home.client.shared.mainevents; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface MainEventsResources extends ClientBundle { + public static final MainEventsResources INSTANCE = GWT.create(MainEventsResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String mainevents(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.css new file mode 100644 index 00000000000..f2c12136a00 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.css @@ -0,0 +1,37 @@ +/** + * Events on the main page + */ +.eventteaser { + font-size: 16px; + font-size: 1rem; + display: inline-block; + margin-bottom: 2.8125em; +} +.eventteaser:hover { + text-decoration: none; +} +.eventteaser:hover .eventteaser_name { + text-decoration: underline; +} +.eventteaser:hover .eventteaser_image { + opacity: 0.9; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; + filter: literal("alpha(opacity=90)"); +} +.eventteaser_image { + position: relative; +} +.eventteaser_series { + position: absolute; + bottom: 0; + right: 0; +} +.eventteaser_name { + font-size: 1.375em; + font-weight: bold; + color: #008fcc; + margin-top: 0.3125em; +} +.eventteaser_location { + color: #222; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.java new file mode 100644 index 00000000000..c3e488b42ab --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.java @@ -0,0 +1,73 @@ +package com.sap.sailing.gwt.home.client.shared.mainevents; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.place.shared.PlaceController; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.place.event.EventPlace; +import com.sap.sailing.gwt.home.client.place.event.EventPlace.Tokenizer; +import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class RecentEvent extends Composite { + + @UiField SpanElement eventName; + @UiField SpanElement venueName; + @UiField SpanElement eventStartDate; + @UiField Anchor eventOverviewLink; + @UiField ImageElement eventImage; + + private EventDTO event; + + private final String defaultImageUrl = "http://static.sapsailing.com/newhome/event-1.jpg"; + + interface RecentEventUiBinder extends UiBinder { + } + + private static RecentEventUiBinder uiBinder = GWT.create(RecentEventUiBinder.class); + + private final PlaceController placeController; + + public RecentEvent(PlaceController placeController) { + this.placeController = placeController; + RecentEventResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + } + + public void setEvent(EventDTO event) { + this.event = event; + updateUI(); + } + + @UiHandler("eventOverviewLink") + public void goToHome(ClickEvent e) { + EventPlace eventPlace = new EventPlace(event.id.toString()); + if(event.getBaseURL().contains("localhost") || event.getBaseURL().contains("127.0.0.1")) { + placeController.goTo(eventPlace); + } else { + EventPlace.Tokenizer t = new Tokenizer(); + String remoteEventUrl = event.getBaseURL() + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); + Window.Location.replace(remoteEventUrl); + } + } + + private void updateUI() { + eventName.setInnerText(event.getName()); + venueName.setInnerText(event.venue.getName()); + eventStartDate.setInnerText(EventDatesFormatterUtil.formatDateRangeWithoutYear(event.startDate, event.endDate)); + + if(event.getImageURLs().size() == 0) { + eventImage.setSrc(defaultImageUrl); + } else { + eventImage.setSrc(event.getImageURLs().get(0)); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.ui.xml new file mode 100644 index 00000000000..bf74e493eb3 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.ui.xml @@ -0,0 +1,20 @@ + + + + + + +
+ +
+
+
+
+
,  +
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEventResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEventResources.java new file mode 100644 index 00000000000..1162ff4a992 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/RecentEventResources.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.home.client.shared.mainevents; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface RecentEventResources extends ClientBundle { + public static final RecentEventResources INSTANCE = GWT.create(RecentEventResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/mainevents/RecentEvent.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String eventteaser(); + String eventteaser_name(); + String eventteaser_image(); + String eventteaser_series(); + String eventteaser_location(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css new file mode 100644 index 00000000000..fd9b2520a06 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css @@ -0,0 +1,92 @@ +/** + * Websites main media element + */ +.media { + background: #f8f8f8; + font-size: 16px; + font-size: 1rem; +} +.media_photo { + width: 100%; + height: 100%; +} +.media_slideshow { + position: relative; + height: 550px; + width: 830px; + margin: 0 auto; +} +.media_slideshow_controls { + position: absolute; + background-position: 0 0; + background-repeat: no-repeat; + cursor: pointer; +} +.media_slideshow_controlsnext, +.media_slideshow_controlsprev { + top: 50%; + -webkit-transform: translate(0, -50%); + -moz-transform: translate(0, -50%); + -o-transform: translate(0, -50%); + -ms-transform: translate(0, -50%); + transform: translate(0, -50%); + width: 60px; + height: 60px; + background-image: url("../images/media-controls__arrows.png"); +} +.media_slideshow_controlsprev { + left: 0; + background-position: 0 0; +} +.media_slideshow_controlsprev:hover { + background-position: 0 100%; +} +.media_slideshow_controlsnext { + right: 0; + background-position: 100% 0; +} +.media_slideshow_controlsnext:hover { + background-position: 100% 100%; +} +.media_slideshow_controlsfullsize { + background-image: url("../images/media-controls__fullsize.png"); + right: 0; + bottom: 0; + width: 40px; + height: 40px; +} +.mediavideos { + background: #333; + color: #fff; +} +.mediavideos .mainsection_header_title { + color: #fff; +} +.videopreview { + font-size: 16px; + font-size: 1rem; + margin-bottom: 2.5em; + cursor: pointer; +} +.videopreview:before { + content: ' '; + position: absolute; + left: 50%; + top: 70px; + -webkit-transform: translate(-50%, 0); + -moz-transform: translate(-50%, 0); + -o-transform: translate(-50%, 0); + -ms-transform: translate(-50%, 0); + transform: translate(-50%, 0); + width: 69px; + height: 69px; + background: url("/images/video__preview--play.png"); + -webkit-background-size: contain; + -moz-background-size: contain; + background-size: contain; +} +.videopreview_title { + font-size: 1.375em; + font-weight: bold; + margin: 0; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java new file mode 100644 index 00000000000..0fcf6e5330f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.home.client.shared.mainmedia; + +import com.google.gwt.core.client.GWT; +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.Label; +import com.google.gwt.user.client.ui.Widget; + +public class MainMedia extends Composite { + + @UiField Label videoLength; + @UiField Label videoTitle; + + interface MainMediaUiBinder extends UiBinder { + } + + private static MainMediaUiBinder uiBinder = GWT.create(MainMediaUiBinder.class); + + public MainMedia() { + MainMediaResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml new file mode 100644 index 00000000000..3b107373e0e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml @@ -0,0 +1,53 @@ + + + + + + +
+
+
+
+

Sailing loves Photography

+

Get lost in the latest and most beautiful impressions from your favorite sailing event.

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Recent Video Entries

+

Immerse yourself in the world of sailing and check out our latest events for more videos.

+
+
+
+
+
+
+
+

+
+
+
+
+
+
+ +
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java new file mode 100644 index 00000000000..db9f2033ba9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java @@ -0,0 +1,26 @@ +package com.sap.sailing.gwt.home.client.shared.mainmedia; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface MainMediaResources extends ClientBundle { + public static final MainMediaResources INSTANCE = GWT.create(MainMediaResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String media(); + String media_photo(); + String media_slideshow(); + String media_slideshow_controls(); + String media_slideshow_controlsnext(); + String media_slideshow_controlsprev(); + String media_slideshow_controlsfullsize(); + String mediavideos(); + String mainsection_header_title(); + String videopreview(); + String videopreview_title(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.css new file mode 100644 index 00000000000..5c755e56dfd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.css @@ -0,0 +1,8 @@ +/** + * A box with a sponsoring and solution teaser + */ +.mainsponsors { + font-size: 16px; + font-size: 1rem; + position: relative; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.java new file mode 100644 index 00000000000..dad5c702ca9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.home.client.shared.mainsponsors; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class MainSponsors extends Composite { + + interface MainSponsorsUiBinder extends UiBinder { + } + + private static MainSponsorsUiBinder uiBinder = GWT.create(MainSponsorsUiBinder.class); + + public MainSponsors() { + MainSponsorsResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.ui.xml new file mode 100644 index 00000000000..90474efaa8e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.ui.xml @@ -0,0 +1,27 @@ + + + + + + + +
+
+
+

Solutions

+
+
Here goes the solution description short text Read more
+
+
+
+

Sponsored Teams

+
+
Here goes the solution description short text Read more
+
+
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsorsResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsorsResources.java new file mode 100644 index 00000000000..b5d89593fbf --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsorsResources.java @@ -0,0 +1,23 @@ +package com.sap.sailing.gwt.home.client.shared.mainsponsors; + +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 MainSponsorsResources extends ClientBundle { + public static final MainSponsorsResources INSTANCE = GWT.create(MainSponsorsResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/mainsponsors/MainSponsors.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String mainsponsors(); + } + + @Source("com/sap/sailing/gwt/home/images/temp--sponsor-1.png") + ImageResource sponsorTeaserImage(); + + @Source("com/sap/sailing/gwt/home/images/temp--sponsor-1.png") + ImageResource solutionTeaserImage(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.css new file mode 100644 index 00000000000..5333ffd1fbe --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.css @@ -0,0 +1,66 @@ +/** + * Events on the main page + */ +.socialfooter { + font-size: 16px; + font-size: 1rem; + background: #008fcc; + color: #fff; +} +.socialfooter .mainsection_header { + margin-bottom: 1.25em; +} +.socialfooter .mainsection_header_title { + color: #fff; +} +.socialfooter_button { + position: relative; + display: inline-block; + width: 19.0625em; + height: 3.75em; + margin: 0 0.9375em; +} +.socialfooter_button:before { + content: ' '; + position: absolute; + width: 3.125em; + height: 3.75em; + left: 0; + background: url("../images/social--icons.png"); +} +.socialfooter_buttontwitter { + background: #58aeee; +} +.socialfooter_buttontwitter:before { + background-position: 0 0; +} +.socialfooter_buttonfacebook { + background: #3b5998; +} +.socialfooter_buttonfacebook:before { + background-position: 100% 0; +} +.socialfooter_button_text { + position: absolute; + left: 3.125em; + width: 10em; + padding: 0.3125em 0.625em; + margin: 0.3125em 0; + border-left: 1px solid rgba(255,255,255,0.2); + text-align: left; + line-height: 1; +} +.socialfooter_button_text span { + font-size: 1.5em; + font-weight: 600; +} +.socialfooter_button_widget { + position: absolute; + right: 0.625em; + top: 50%; + -webkit-transform: translate(0, -40%); + -moz-transform: translate(0, -40%); + -o-transform: translate(0, -40%); + -ms-transform: translate(0, -40%); + transform: translate(0, -40%); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.java new file mode 100644 index 00000000000..707ce6fbd1c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.home.client.shared.socialfooter; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class SocialFooter extends Composite { + + interface SocialFooterUiBinder extends UiBinder { + } + + private static SocialFooterUiBinder uiBinder = GWT.create(SocialFooterUiBinder.class); + + public SocialFooter() { + SocialFooterResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.ui.xml new file mode 100644 index 00000000000..810594de7cf --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.ui.xml @@ -0,0 +1,30 @@ + + + + + + + +
+
+
+
+

Recommend SAP Sailing Analytics

+
+
+
+
Share this on Twitter
+
+
+
+
Share this on Facebook
+
+
+
+
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooterResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooterResources.java new file mode 100644 index 00000000000..756ebf96843 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooterResources.java @@ -0,0 +1,23 @@ +package com.sap.sailing.gwt.home.client.shared.socialfooter; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface SocialFooterResources extends ClientBundle { + public static final SocialFooterResources INSTANCE = GWT.create(SocialFooterResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String socialfooter(); + String mainsection_header(); + String mainsection_header_title(); + String socialfooter_button(); + String socialfooter_buttontwitter(); + String socialfooter_buttonfacebook(); + String socialfooter_button_text(); + String socialfooter_button_widget(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css new file mode 100644 index 00000000000..b353c6d1a14 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css @@ -0,0 +1,85 @@ +/** + * Main stage area + */ +.stage { + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-orient: vertical; + -moz-box-orient: vertical; + -o-box-orient: vertical; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -moz-box-pack: start; + -o-box-pack: start; + -ms-flex-pack: start; + -webkit-justify-content: flex-start; + justify-content: flex-start; + font-size: 16px; + font-size: 1rem; + line-height: 1; + background-position: top center; + background-repeat: no-repeat; + -webkit-background-size: cover; + -moz-background-size: cover; + background-size: cover; + height: 36.25em; + position: relative; +} +.stage_header { + padding: 4.875em; + text-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); + text-align: center; +} +.stage_header_subtitle { + font-size: 1.875em; + font-weight: 400; + color: #fff; + margin: 0; +} +.stage_header_title { + font-size: 4.5em; + font-weight: 700; + color: #fff; + margin: 0; +} +.stage_header_message { + font-size: 1.875em; + font-weight: 600; + color: #fff; + margin: 0; +} +.stage_band { + position: absolute; + bottom: 0; + right: 0; + left: 0; + background-color: rgba(51,51,51,0.6); + text-align: center; + padding: 1.5625em; +} +.stage_band_message { + display: inline-block; + color: #f0ab00; + font-size: 1.5em; + font-weight: 700; + text-shadow: 0 0 0.625em rgba(51,51,51,0.45); + text-transform: uppercase; +} +.stage_band_name { + display: inline-block; + color: #fff; + font-size: 1.5em; + text-shadow: 0 0 0.625em rgba(51,51,51,0.45); +} +.stage_band_action { + font-size: 1.125em; + padding: 0.875em 1.875em; + margin-left: 1.25em; + text-transform: none; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.java new file mode 100644 index 00000000000..4cbc1f9b66b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.java @@ -0,0 +1,45 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.SpanElement; +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.Label; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class Stage extends Composite { + + @SuppressWarnings("unused") + private EventDTO featuredEvent; + + @UiField Label subtitle; + @UiField Label title; + @UiField Label message; + @UiField SpanElement regattaState; + @UiField SpanElement actionMessage; + + interface StageUiBinder extends UiBinder { + } + + private static StageUiBinder uiBinder = GWT.create(StageUiBinder.class); + + public Stage() { + StageResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + } + + public void setFeaturedEvent(EventDTO featuredEvent) { + this.featuredEvent = featuredEvent; + + title.setText(featuredEvent.getName()); + subtitle.setText(featuredEvent.venue.getName()); + + message.setText(EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)); + actionMessage.setInnerText("[Race 6]"); + regattaState.setInnerText("Next Race"); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml new file mode 100644 index 00000000000..93d3b5c29a0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml @@ -0,0 +1,29 @@ + + + + + + + +
+
+

+

+
+
+
+
+
+
 - +
+
+ +
+
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java new file mode 100644 index 00000000000..749273fd8cb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface StageResources extends ClientBundle { + public static final StageResources INSTANCE = GWT.create(StageResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/stage/Stage.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String stage(); + String stage_header(); + String stage_header_subtitle(); + String stage_header_title(); + String stage_header_message(); + String stage_band(); + String stage_band_message(); + String stage_band_name(); + String stage_band_action(); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardByNameAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardByNameAction.java index a7e989f3fd3..4d6b53f6121 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardByNameAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardByNameAction.java @@ -17,7 +17,7 @@ import com.sap.sse.gwt.client.player.Timer; * the logic how differential, incremental leaderboard fetching and transmission works. This helps guarantee the * requirement of {@link IncrementalOrFullLeaderboardDTO#getLeaderboardDTO(LeaderboardDTO)} (only pass null * as the previous leaderboard if null was passed as identifier of the previous leaderboard to - * {@link SailingServiceAsync#getLeaderboardByName(String, Date, Collection, String, AsyncCallback)} too. + * {@link SailingServiceAsync#getLeaderboardByName(String, Date, Collection, boolean, String, AsyncCallback)} too. *

* * When a valid {@link IncrementalOrFullLeaderboardDTO} object is received, the @@ -32,20 +32,22 @@ public class GetLeaderboardByNameAction implements AsyncAction { private final String leaderboardName; private final Date date; private final Collection namesOfRacesForWhichToLoadLegDetails; + private final boolean addOverallDetails; private final LeaderboardDTO previousLeaderboard; private final StringMessages stringMessages; private final ErrorReporter errorReporter; private final Timer timerToAdjustOffetIn; public GetLeaderboardByNameAction(SailingServiceAsync sailingService, String leaderboardName, Date date, - final Collection namesOfRacesForWhichToLoadLegDetails, LeaderboardDTO previousLeaderboard, - Timer timerToAdjustOffsetIn, ErrorReporter errorReporter, StringMessages stringMessages) { + final Collection namesOfRacesForWhichToLoadLegDetails, boolean addOverallDetails, + LeaderboardDTO previousLeaderboard, Timer timerToAdjustOffsetIn, ErrorReporter errorReporter, StringMessages stringMessages) { this.sailingService = sailingService; this.errorReporter = errorReporter; this.stringMessages = stringMessages; this.leaderboardName = leaderboardName; this.date = date; this.namesOfRacesForWhichToLoadLegDetails = namesOfRacesForWhichToLoadLegDetails; + this.addOverallDetails = addOverallDetails; this.previousLeaderboard = previousLeaderboard; this.timerToAdjustOffetIn = timerToAdjustOffsetIn; } @@ -53,9 +55,8 @@ public class GetLeaderboardByNameAction implements AsyncAction { @Override public void execute(final AsyncCallback callback) { final long clientTimeWhenRequestWasSent = System.currentTimeMillis(); - sailingService.getLeaderboardByName(leaderboardName, date, namesOfRacesForWhichToLoadLegDetails, - previousLeaderboard==null?null:previousLeaderboard.getId(), - new AsyncCallback() { + sailingService.getLeaderboardByName(leaderboardName, date, namesOfRacesForWhichToLoadLegDetails, addOverallDetails, + previousLeaderboard==null?null:previousLeaderboard.getId(), new AsyncCallback() { @Override public void onFailure(Throwable caught) { callback.onFailure(caught); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardDataEntriesAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardDataEntriesAction.java index e3f63ecff29..46b335e4bbb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardDataEntriesAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardDataEntriesAction.java @@ -6,11 +6,11 @@ import java.util.List; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.AsyncAction; -public class GetLeaderboardDataEntriesAction implements AsyncAction, List>>> { +public class GetLeaderboardDataEntriesAction implements AsyncAction, List>>> { private final SailingServiceAsync sailingService; private final DetailType detailType; private final String leaderboardName; @@ -24,7 +24,7 @@ public class GetLeaderboardDataEntriesAction implements AsyncAction, List>>> callback) { + public void execute(AsyncCallback, List>>> callback) { sailingService.getLeaderboardDataEntriesForAllRaceColumns(leaderboardName, date, detailType, callback); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetWindInfoAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetWindInfoAction.java index 21592ec5d1d..c7a061c99f6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetWindInfoAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetWindInfoAction.java @@ -12,23 +12,28 @@ import com.sap.sse.gwt.client.async.AsyncAction; public class GetWindInfoAction implements AsyncAction { private final SailingServiceAsync sailingService; private final RegattaAndRaceIdentifier raceIdentifier; - private Date from; - private long millisecondsStepWidth; - private int numberOfFixes; - private Collection windSourceTypeNames; + private final Date from; + private final long millisecondsStepWidth; + private final int numberOfFixes; + private final Collection windSourceTypeNames; - private long resolutionInMilliseconds; - private Date fromDate; - private Date toDate; + private final long resolutionInMilliseconds; + private final Date fromDate; + private final Date toDate; + private final boolean onlyUpToNewestEvent; private enum CallVariants { Variant1, Variant2 }; private final CallVariants callVariant; public GetWindInfoAction(SailingServiceAsync sailingService, RegattaAndRaceIdentifier raceIdentifier, Date from, - long millisecondsStepWidth, int numberOfFixes, Collection windSourceTypeNames) { + long millisecondsStepWidth, int numberOfFixes, Collection windSourceTypeNames, boolean onlyUpToNewestEvent) { this.sailingService = sailingService; + this.onlyUpToNewestEvent = onlyUpToNewestEvent; this.raceIdentifier = raceIdentifier; this.from = from; + this.toDate = null; // not needed in this variant + this.fromDate = null; // not needed in this variant + this.resolutionInMilliseconds = 0; // not needed in this variant this.millisecondsStepWidth = millisecondsStepWidth; this.numberOfFixes = numberOfFixes; this.windSourceTypeNames = windSourceTypeNames; @@ -36,11 +41,15 @@ public class GetWindInfoAction implements AsyncAction { } public GetWindInfoAction(SailingServiceAsync sailingService, RegattaAndRaceIdentifier raceIdentifier, Date fromDate, - Date toDate, long resolutionInMilliseconds, Collection windSourceTypeNames) { + Date toDate, long resolutionInMilliseconds, Collection windSourceTypeNames, boolean onlyUpToNewestEvent) { this.sailingService = sailingService; + this.onlyUpToNewestEvent = onlyUpToNewestEvent; this.raceIdentifier = raceIdentifier; this.fromDate = fromDate; this.toDate = toDate; + this.numberOfFixes = 0; // not needed in this variant + this.millisecondsStepWidth = 0; // not needed in this variant + this.from = null; // not needed in this variant this.resolutionInMilliseconds = resolutionInMilliseconds; this.windSourceTypeNames = windSourceTypeNames; callVariant = CallVariants.Variant2; @@ -50,10 +59,12 @@ public class GetWindInfoAction implements AsyncAction { public void execute(AsyncCallback callback) { switch (callVariant) { case Variant1: - sailingService.getAveragedWindInfo(raceIdentifier, from, millisecondsStepWidth, numberOfFixes, windSourceTypeNames, callback); + sailingService.getAveragedWindInfo(raceIdentifier, from, millisecondsStepWidth, numberOfFixes, windSourceTypeNames, + onlyUpToNewestEvent, callback); break; case Variant2: - sailingService.getAveragedWindInfo(raceIdentifier, fromDate, toDate, resolutionInMilliseconds, windSourceTypeNames, callback); + sailingService.getAveragedWindInfo(raceIdentifier, fromDate, toDate, resolutionInMilliseconds, windSourceTypeNames, + onlyUpToNewestEvent, callback); break; } } 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 82222b38d8d..3b43de1b9bc 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 @@ -8,8 +8,8 @@ import java.util.List; import com.google.gwt.user.client.ui.ListBox; import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel; import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.RegattaRefresher; 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; import com.sap.sailing.gwt.ui.shared.RegattaDTO; 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 new file mode 100644 index 00000000000..94936ba80e1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java @@ -0,0 +1,492 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +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.cellview.client.CellTable; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CaptionPanel; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.HorizontalPanel; +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.gwt.ui.client.ErrorReporter; +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.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.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.common.Util; +import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; + +public abstract class AbstractLeaderboardConfigPanel extends FormPanel implements SelectedLeaderboardProvider, + RegattasDisplayer, RaceSelectionChangeListener, TrackedRaceChangedListener, LeaderboardsDisplayer { + protected final VerticalPanel mainPanel; + + protected final TrackedRacesListComposite trackedRacesListComposite; + + protected final StringMessages stringMessages; + + protected final SailingServiceAsync sailingService; + + protected final ListDataProvider leaderboardList; + + protected final ErrorReporter errorReporter; + + protected final CellTable leaderboardTable; + + protected final RaceTableWrapper> raceColumnTable; + protected final SetSelectionModel raceColumnTableSelectionModel; + + protected RaceColumnDTOAndFleetDTOWithNameBasedEquality selectedRaceInLeaderboard; + + protected final CaptionPanel selectedLeaderBoardPanel; + protected final CaptionPanel trackedRacesCaptionPanel; + protected final List allRegattas; + + protected LabeledAbstractFilterablePanel filterLeaderboardPanel; + + protected List availableLeaderboardList; + + protected final MultiSelectionModel leaderboardSelectionModel; + + protected final RaceSelectionProvider raceSelectionProvider; + + private final LeaderboardsRefresher leaderboardsRefresher; + + public static class RaceColumnDTOAndFleetDTOWithNameBasedEquality extends Util.Pair { + private static final long serialVersionUID = -8742476113296862662L; + + public RaceColumnDTOAndFleetDTOWithNameBasedEquality(RaceColumnDTO a, FleetDTO b) { + super(a, b); + } + + @Override + public int hashCode() { + return getA().getName().hashCode() ^ getB().getName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + if (obj == null) { + return false; + } else { + return equalNamesOrBothNull(getA(), ((RaceColumnDTOAndFleetDTOWithNameBasedEquality) obj).getA()) + && equalNamesOrBothNull(getB(), ((RaceColumnDTOAndFleetDTOWithNameBasedEquality) obj).getB()); + } + } + } + + private boolean equalNamesOrBothNull(NamedDTO a, NamedDTO b) { + if (a == null) { + return b == null; + } else { + if (b == null) { + return false; + } else { + return Util.equalsWithNull(a.getName(), b.getName()); + } + } + } + } + + public AbstractLeaderboardConfigPanel(final SailingServiceAsync sailingService, RegattaRefresher regattaRefresher, + LeaderboardsRefresher leaderboardsRefresher, final ErrorReporter errorReporter, + StringMessages theStringConstants, SetSelectionModel raceColumnTableSelectionModel) { + this.stringMessages = theStringConstants; + this.sailingService = sailingService; + leaderboardList = new ListDataProvider(); + allRegattas = new ArrayList(); + this.errorReporter = errorReporter; + this.leaderboardsRefresher = leaderboardsRefresher; + this.availableLeaderboardList = new ArrayList(); + mainPanel = new VerticalPanel(); + mainPanel.setWidth("100%"); + this.setWidget(mainPanel); + + //Create leaderboards list and functionality + CaptionPanel leaderboardsCaptionPanel = new CaptionPanel(stringMessages.leaderboards()); + leaderboardsCaptionPanel.setStyleName("bold"); + leaderboardsCaptionPanel.setWidth("75%"); + mainPanel.add(leaderboardsCaptionPanel); + + VerticalPanel leaderboardsPanel = new VerticalPanel(); + leaderboardsCaptionPanel.add(leaderboardsPanel); + + HorizontalPanel leaderboardConfigControlsPanel = new HorizontalPanel(); + Label lblFilterEvents = new Label(stringMessages.filterLeaderboardsByName() + ": "); + leaderboardConfigControlsPanel.setSpacing(5); + + AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); + leaderboardTable = new CellTable(/* pageSize */10000, tableRes); + filterLeaderboardPanel = new LabeledAbstractFilterablePanel(lblFilterEvents, + availableLeaderboardList, leaderboardTable, leaderboardList) { + @Override + public List getSearchableStrings(StrippedLeaderboardDTO t) { + List strings = new ArrayList(); + strings.add(t.name); + strings.add(t.displayName); + return strings; + } + }; + leaderboardConfigControlsPanel.add(filterLeaderboardPanel); + + addLeaderboardConfigControls(leaderboardConfigControlsPanel); + + leaderboardsPanel.add(leaderboardConfigControlsPanel); + leaderboardTable.ensureDebugId("AvailableLeaderboardsTable"); + + addColumnsToLeaderboardTable(leaderboardTable); + + leaderboardTable.setWidth("100%"); + leaderboardSelectionModel = new MultiSelectionModel(); + leaderboardTable.setSelectionModel(leaderboardSelectionModel); + leaderboardSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + public void onSelectionChange(SelectionChangeEvent event) { + leaderboardSelectionChanged(); + raceColumnTable.setSelectedLeaderboardName(getSelectedLeaderboardName()); + } + }); + leaderboardList.addDataDisplay(leaderboardTable); + leaderboardsPanel.add(leaderboardTable); + HorizontalPanel leaderboardButtonPanel = new HorizontalPanel(); + leaderboardButtonPanel.setSpacing(5); + leaderboardsPanel.add(leaderboardButtonPanel); + + addLeaderboardCreateControls(leaderboardButtonPanel); + + + mainPanel.add(new Grid(1, 1)); + + // caption panels for the selected leaderboard and tracked races + HorizontalPanel splitPanel = new HorizontalPanel(); + splitPanel.ensureDebugId("LeaderboardDetailsPanel"); + mainPanel.add(splitPanel); + + selectedLeaderBoardPanel = new CaptionPanel(stringMessages.leaderboard()); + selectedLeaderBoardPanel.setWidth("50%"); + splitPanel.add(selectedLeaderBoardPanel); + + VerticalPanel vPanel = new VerticalPanel(); + vPanel.setWidth("100%"); + selectedLeaderBoardPanel.setContentWidget(vPanel); + + trackedRacesCaptionPanel = new CaptionPanel(stringMessages.trackedRaces()); + trackedRacesCaptionPanel.setWidth("50%"); + splitPanel.add(trackedRacesCaptionPanel); + + VerticalPanel trackedRacesPanel = new VerticalPanel(); + trackedRacesPanel.setWidth("100%"); + trackedRacesCaptionPanel.setContentWidget(trackedRacesPanel); + trackedRacesCaptionPanel.setStyleName("bold"); + + raceSelectionProvider = new RaceSelectionModel(); + trackedRacesListComposite = new TrackedRacesListComposite(sailingService, errorReporter, regattaRefresher, + raceSelectionProvider, stringMessages, /* multiselection */false); + trackedRacesListComposite.ensureDebugId("TrackedRacesListComposite"); + trackedRacesPanel.add(trackedRacesListComposite); + trackedRacesListComposite.addTrackedRaceChangeListener(this); + raceSelectionProvider.addRaceSelectionChangeListener(this); + + Button reloadAllRaceLogs = new Button(stringMessages.reloadAllRaceLogs()); + reloadAllRaceLogs.ensureDebugId("ReloadAllRaceLogsButton"); + reloadAllRaceLogs.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + StrippedLeaderboardDTO leaderboard = getSelectedLeaderboard(); + for (RaceColumnDTO column : leaderboard.getRaceList()) { + for (FleetDTO fleet : column.getFleets()) { + refreshRaceLog(column, fleet, false); + } + } + Window.alert(stringMessages.raceLogReloaded()); + } + }); + vPanel.add(reloadAllRaceLogs); + + + Label lblRaceNamesIn = new Label(stringMessages.races()); + vPanel.add(lblRaceNamesIn); + + raceColumnTable = new RaceTableWrapper>( + sailingService, stringMessages, errorReporter, raceColumnTableSelectionModel); + raceColumnTable.getTable().setWidth("500px"); + addColumnsToRacesTable(raceColumnTable.getTable()); + this.raceColumnTableSelectionModel = raceColumnTableSelectionModel; + raceColumnTableSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + public void onSelectionChange(SelectionChangeEvent event) { + leaderboardRaceColumnSelectionChanged(); + } + }); + vPanel.add(raceColumnTable); + + HorizontalPanel selectedLeaderboardRaceButtonPanel = new HorizontalPanel(); + selectedLeaderboardRaceButtonPanel.setSpacing(5); + vPanel.add(selectedLeaderboardRaceButtonPanel); + + addSelectedLeaderboardRacesControls(selectedLeaderboardRaceButtonPanel); + } + + protected abstract void addLeaderboardConfigControls(Panel configPanel); + protected abstract void addLeaderboardCreateControls(Panel createPanel); + protected abstract void addSelectedLeaderboardRacesControls(Panel racesPanel); + protected abstract void addColumnsToLeaderboardTable(CellTable leaderboardTable); + protected abstract void addColumnsToRacesTable(CellTable racesTable); + + + @Override + public void fillLeaderboards(Iterable leaderboards) { + availableLeaderboardList.clear(); + Util.addAll(leaderboards, availableLeaderboardList); + filterLeaderboardPanel.updateAll(availableLeaderboardList); // also maintains the filtered leaderboardList + leaderboardSelectionChanged(); + leaderboardRaceColumnSelectionChanged(); + } + + /** + * @param nameOfRaceColumnToSelect + * if not null, selects the first race column name with this name found in the leaderboard + * after the refresh has successfully completed. See {@link #selectRaceColumn(String)}. + */ + public void loadAndRefreshLeaderboard(final String leaderboardName, final String nameOfRaceColumnToSelect) { + sailingService.getLeaderboard(leaderboardName, new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onSuccess(StrippedLeaderboardDTO leaderboard) { + for (StrippedLeaderboardDTO leaderboardDTO : leaderboardSelectionModel.getSelectedSet()) { + if (leaderboardDTO.name.equals(leaderboardName)) { + leaderboardSelectionModel.setSelected(leaderboardDTO, false); + break; + } + } + replaceLeaderboardInList(availableLeaderboardList, leaderboardName, leaderboard); + filterLeaderboardPanel.updateAll(availableLeaderboardList); // also updates leaderboardList provider + leaderboardSelectionModel.setSelected(leaderboard, true); + if (nameOfRaceColumnToSelect != null) { + selectRaceColumn(nameOfRaceColumnToSelect); + } + leaderboardSelectionChanged(); + getLeaderboardsRefresher().updateLeaderboards(leaderboardList.getList(), AbstractLeaderboardConfigPanel.this); + } + + @Override + public void onFailure(Throwable t) { + AbstractLeaderboardConfigPanel.this.errorReporter.reportError("Error trying to update leaderboard with name " + leaderboardName + " : " + + t.getMessage()); + } + })); + } + + private void replaceLeaderboardInList(List leaderboardList, String leaderboardToReplace, StrippedLeaderboardDTO newLeaderboard) { + int index = -1; + for (StrippedLeaderboardDTO existingLeaderboard : leaderboardList) { + index++; + if (existingLeaderboard.name.equals(leaderboardToReplace)) { + break; + } + } + if (index >= 0) { + leaderboardList.set(index, newLeaderboard); + } + } + + protected void unlinkRaceColumnFromTrackedRace(final String raceColumnName, final FleetDTO fleet) { + final String selectedLeaderboardName = getSelectedLeaderboardName(); + sailingService.disconnectLeaderboardColumnFromTrackedRace(selectedLeaderboardName, raceColumnName, fleet.getName(), + new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to unlink tracked race from column " + raceColumnName + + " from leaderboard " + selectedLeaderboardName + ": " + t.getMessage()); + } + + @Override + public void onSuccess(Void arg0) { + trackedRacesListComposite.clearSelection(); + getSelectedRaceColumnWithFleet().getA().setRaceIdentifier(fleet, null); + raceColumnTable.getDataProvider().refresh(); + } + }); + } + + protected void refreshRaceLog(final RaceColumnDTO raceColumnDTO, final FleetDTO fleet, final boolean showAlerts) { + final String selectedLeaderboardName = getSelectedLeaderboardName(); + sailingService.reloadRaceLog(selectedLeaderboardName, raceColumnDTO, fleet, new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + if (showAlerts) { + errorReporter.reportError(caught.getMessage()); + } + } + @Override + public void onSuccess(Void result) { + if (showAlerts) { + Window.alert(stringMessages.raceLogReloaded()); + } + } + })); + } + + protected abstract void leaderboardRaceColumnSelectionChanged(); + + protected void selectRaceColumn(String raceColumnName) { + List list = raceColumnTable.getDataProvider().getList(); + for (RaceColumnDTOAndFleetDTOWithNameBasedEquality pair : list) { + if (pair.getA().getName().equals(raceColumnName)) { + raceColumnTable.getSelectionModel().setSelected(pair, true); + break; + } + } + } + + protected void selectTrackedRaceInRaceList() { + final String selectedLeaderboardName = getSelectedLeaderboardName(); + if (selectedLeaderboardName != null) { + final RaceColumnDTOAndFleetDTOWithNameBasedEquality selectedRaceColumnAndFleetNameInLeaderboard = getSelectedRaceColumnWithFleet(); + final String selectedRaceColumnName = selectedRaceColumnAndFleetNameInLeaderboard.getA().getRaceColumnName(); + final String selectedFleetName = selectedRaceColumnAndFleetNameInLeaderboard.getB().getName(); + sailingService.getRegattaAndRaceNameOfTrackedRaceConnectedToLeaderboardColumn(selectedLeaderboardName, + selectedRaceColumnName, new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to determine tracked race linked to race column " + + selectedRaceColumnName + " in leaderboard " + selectedLeaderboardName + ": " + + t.getMessage()); + } + + @Override + public void onSuccess(Map regattaAndRaceNamesPerFleet) { + if (regattaAndRaceNamesPerFleet != null && !regattaAndRaceNamesPerFleet.isEmpty()) { + RegattaAndRaceIdentifier raceIdentifier = regattaAndRaceNamesPerFleet.get(selectedFleetName); + + if (raceIdentifier != null) { + selectRaceInList(raceIdentifier.getRegattaName(), raceIdentifier.getRaceName()); + } else { + trackedRacesListComposite.clearSelection(); + } + } else { + trackedRacesListComposite.clearSelection(); + } + } + })); + } + } + + protected void selectRaceInList(String regattaName, String raceName) { + RegattaNameAndRaceName raceIdentifier = new RegattaNameAndRaceName(regattaName, raceName); + trackedRacesListComposite.selectRaceByIdentifier(raceIdentifier); + } + + protected RaceColumnDTOAndFleetDTOWithNameBasedEquality getSelectedRaceColumnWithFleet() { + if (raceColumnTable.getSelectionModel().getSelectedSet().isEmpty()) { + return null; + } + return raceColumnTable.getSelectionModel().getSelectedSet().iterator().next(); + } + + protected String getSelectedLeaderboardName() { + return getSelectedLeaderboard() != null ? getSelectedLeaderboard().name : null; + } + + protected abstract void leaderboardSelectionChanged(); + + @Override + public void fillRegattas(List regattas) { + trackedRacesListComposite.fillRegattas(regattas); + + allRegattas.clear(); + allRegattas.addAll(regattas); + } + + @Override + public void changeTrackingRace(Iterable regattaAndRaceIdentifiers, boolean isTracked) { + for (RegattaAndRaceIdentifier regattaAndRaceIdentifier : regattaAndRaceIdentifiers) { + for (RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnAndFleetName : raceColumnTable.getDataProvider().getList()) { + if (raceColumnAndFleetName.getA().getRaceColumnName().equals(regattaAndRaceIdentifier.getRaceName())) { + raceColumnAndFleetName.getA().setRaceIdentifier(raceColumnAndFleetName.getB(), + regattaAndRaceIdentifier); + } + } + raceColumnTable.getDataProvider().refresh(); + } + } + + @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 + .getRaceColumnName(), fleet.getName(), selectedRace, + new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to link tracked race " + selectedRace + " to race column named " + + selectedRaceInLeaderboard.getRaceColumnName() + " of leaderboard " + + getSelectedLeaderboardName() + ": " + t.getMessage()); + trackedRacesListComposite.clearSelection(); + } + + @Override + public void onSuccess(Boolean success) { + if (success) { + // TODO consider enabling the Unlink button + selectedRaceInLeaderboard.setRaceIdentifier(fleet, selectedRace); + raceColumnTable.getDataProvider().refresh(); + } + } + })); + } + + @Override + public StrippedLeaderboardDTO getSelectedLeaderboard() { + return leaderboardSelectionModel.getSelectedSet().isEmpty() ? null : leaderboardSelectionModel.getSelectedSet().iterator().next(); + } + + protected LeaderboardsRefresher getLeaderboardsRefresher() { + return leaderboardsRefresher; + } +} \ No newline at end of file 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 0e450e20aa6..6a288255e69 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 @@ -38,18 +38,19 @@ import com.sap.sailing.domain.common.impl.NaturalComparator; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; -import com.sap.sailing.gwt.ui.client.RegattaDisplayer; 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.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; -import com.sap.sailing.gwt.ui.client.shared.panels.LabeledAbstractFilterablePanel; +import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; public abstract class AbstractTrackedRacesListComposite extends SimplePanel implements Component, - RegattaDisplayer, RaceSelectionChangeListener { + RegattasDisplayer, RaceSelectionChangeListener { protected final long DEFAULT_LIVE_DELAY_IN_MILLISECONDS = 5000; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingDialog.java new file mode 100644 index 00000000000..43a1a5ecd26 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingDialog.java @@ -0,0 +1,245 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import com.github.gwtbootstrap.datetimepicker.client.ui.base.HasViewMode.ViewMode; +import com.google.gwt.event.logical.shared.AttachEvent; +import com.google.gwt.event.logical.shared.AttachEvent.Handler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.CaptionPanel; +import com.google.gwt.user.client.ui.Grid; +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.TextBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SelectionModel; +import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; +import com.sap.sailing.gwt.ui.shared.DeviceIdentifierDTO; +import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; +import com.sap.sailing.gwt.ui.shared.MarkDTO; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class AddDeviceMappingDialog extends DataEntryDialog { + private final BetterDateTimeBox from; + private final BetterDateTimeBox to; + private final ListBox deviceType; + private final TextBox deviceId; + private final CompetitorTableWrapper> competitorTable; + private final MarkTableWrapper> markTable; + private final StringMessages stringMessages; + + private Serializable selectedItem; + + public AddDeviceMappingDialog(SailingServiceAsync sailingService, final ErrorReporter errorReporter, + final StringMessages stringMessages, String leaderboardName, String raceColumnName, String fleetName, + DialogCallback callback, final DeviceMappingDTO mapping) { + super(stringMessages.add(stringMessages.deviceMappings()), stringMessages.add(stringMessages.deviceMappings()), + stringMessages.add(), stringMessages.cancel(), new DataEntryDialog.Validator() { + @Override + public String getErrorMessage(DeviceMappingDTO valueToValidate) { + String deviceType = valueToValidate.deviceIdentifier.deviceType; + String deviceId = valueToValidate.deviceIdentifier.deviceId; + if (deviceType == null || deviceType.isEmpty()) { + return stringMessages.pleaseEnterA(stringMessages.deviceType()); + } + if (deviceId == null || deviceId.isEmpty()) { + return stringMessages.pleaseEnterA(stringMessages.deviceId()); + } + if ((valueToValidate.from == null || valueToValidate.from.compareTo(new Date(Long.MIN_VALUE)) == 0) + && (valueToValidate.to == null || valueToValidate.to.compareTo(new Date(Long.MAX_VALUE)) == 0)) { + return stringMessages.atMostOneEndOfTheTimeRangeMayBeOpen(); + } + if (valueToValidate.from != null && valueToValidate.to != null && + valueToValidate.to.before(valueToValidate.from)) { + return stringMessages.startOfTimeRangeMustLieBeforeEnd(); + } + if (valueToValidate.mappedTo == null) { + return stringMessages.pleaseSelectAnItemToMapTo(); + } + return null; + } + }, true, callback); + + this.stringMessages = stringMessages; + + from = initTimeBox(); + to = initTimeBox(); + + deviceType = createListBox(false); + sailingService.getDeserializableDeviceIdentifierTypes(new AsyncCallback>() { + @Override + public void onSuccess(List result) { + String typeToPreselect = mapping != null ? mapping.deviceIdentifier.deviceType : null; + int i = 0; + for (String type : result) { + deviceType.addItem(type); + if (type.equals(typeToPreselect)) { + deviceType.setSelectedIndex(i); + } + i++; + } + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load deserializable device identifier types: " + caught.getMessage()); + } + }); + + deviceId = createTextBox(""); + + final SingleSelectionModel markSelectionModel = new SingleSelectionModel(); + competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, + new SingleSelectionModel()); + markTable = new MarkTableWrapper>( + markSelectionModel, sailingService, stringMessages, errorReporter); + + competitorTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + if (competitorTable.getSelectionModel().getSelectedSet().size() == 1) { + selectedItem = competitorTable.getSelectionModel().getSelectedSet().iterator().next(); + deselectAll(markTable.getSelectionModel(), markTable.getDataProvider().getList()); + validate(); + } + } + }); + + markTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + if (markSelectionModel.getSelectedSet().size() == 1) { + selectedItem = markSelectionModel.getSelectedSet().iterator().next(); + deselectAll(competitorTable.getSelectionModel(), competitorTable.getAllCompetitors()); + validate(); + } + } + }); + + sailingService.getCompetitorRegistrations(leaderboardName, raceColumnName, fleetName, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + competitorTable.refreshCompetitorList(result); + if (mapping != null && mapping.mappedTo instanceof CompetitorDTO) { + //got new DTOs with new object identities, so have to go through them one by one + CompetitorDTO old = (CompetitorDTO) mapping.mappedTo; + for (CompetitorDTO inList : competitorTable.getAllCompetitors()) { + if (old.getIdAsString().equals(inList.getIdAsString())) { + competitorTable.getSelectionModel().setSelected(inList, true); + } + } + } + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load competitors: " + caught.getMessage()); + } + }); + + sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + markTable.refresh(result); + if (mapping != null && mapping.mappedTo instanceof MarkDTO) { + //got new DTOs with new object identities, so have to go through them one by one + MarkDTO old = (MarkDTO) mapping.mappedTo; + for (MarkDTO inList : markTable.getDataProvider().getList()) { + if (old.getIdAsString().equals(inList.getIdAsString())) { + markTable.getSelectionModel().setSelected(inList, true); + } + } + } + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load marks: " + caught.getMessage()); + } + }); + + if (mapping != null) { + deviceId.setValue(mapping.deviceIdentifier.deviceId); + from.setValue(mapping.from); + to.setValue(mapping.to); + } + } + + @Override + protected Widget getAdditionalWidget() { + HorizontalPanel panel = new HorizontalPanel(); + VerticalPanel tablesPanel = new VerticalPanel(); + Grid entryGrid = new Grid(4, 2); + CaptionPanel marksPanel = new CaptionPanel(stringMessages.mark()); + CaptionPanel competitorsPanel = new CaptionPanel(stringMessages.competitor()); + + entryGrid.setWidget(0, 0, new Label(stringMessages.deviceType())); + entryGrid.setWidget(0, 1, deviceType); + entryGrid.setWidget(1, 0, new Label(stringMessages.deviceId())); + entryGrid.setWidget(1, 1, deviceId); + entryGrid.setWidget(2, 0, new Label(stringMessages.from())); + entryGrid.setWidget(2, 1, from); + entryGrid.setWidget(3, 0, new Label(stringMessages.to())); + entryGrid.setWidget(3, 1, to); + + panel.add(entryGrid); + panel.add(tablesPanel); + tablesPanel.add(marksPanel); + tablesPanel.add(competitorsPanel); + + marksPanel.setContentWidget(markTable.getTable()); + competitorsPanel.setContentWidget(competitorTable.getTable()); + + return panel; + } + + private static void deselectAll(SelectionModel selectionModel, Iterable list) { + for (T t : list) { + selectionModel.setSelected(t, false); + } + } + + private BetterDateTimeBox initTimeBox() { + final BetterDateTimeBox timeBox = new BetterDateTimeBox(); + timeBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + validate(); + } + }); + timeBox.addAttachHandler(new Handler() { + @Override + public void onAttachOrDetach(AttachEvent event) { + if (event.isAttached()) { + addAutoHidePartner(timeBox.getPicker()); + } + } + }); + timeBox.setAutoClose(true); + timeBox.setStartView(ViewMode.HOUR); + timeBox.setFormat("dd/mm/yyyy hh:ii"); + return timeBox; + } + + @Override + protected DeviceMappingDTO getResult() { + String deviceTypeS = deviceType.getSelectedIndex() < 0 ? null : deviceType.getValue(deviceType.getSelectedIndex()); + DeviceIdentifierDTO deviceIdentifier = new DeviceIdentifierDTO(deviceTypeS, deviceId.getValue()); + return new DeviceMappingDTO(deviceIdentifier, from.getValue(), to.getValue(), + selectedItem, null); + } + +} 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 old mode 100644 new mode 100755 index f9b82ec355e..f1e44d52e07 --- 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 @@ -15,15 +15,20 @@ import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.DockPanel; import com.google.gwt.user.client.ui.HasHorizontalAlignment; +import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.TabLayoutPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; +import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer; +import com.sap.sailing.gwt.ui.client.LeaderboardGroupsRefresher; +import com.sap.sailing.gwt.ui.client.LeaderboardsDisplayer; +import com.sap.sailing.gwt.ui.client.LeaderboardsRefresher; import com.sap.sailing.gwt.ui.client.MediaService; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; -import com.sap.sailing.gwt.ui.client.RegattaDisplayer; import com.sap.sailing.gwt.ui.client.RegattaRefresher; +import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.client.SailingService; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -34,26 +39,48 @@ import com.sap.sailing.gwt.ui.client.shared.panels.SystemInformationPanel; import com.sap.sailing.gwt.ui.client.shared.panels.UserStatusPanel; import com.sap.sailing.gwt.ui.masterdataimport.MasterDataImportPanel; import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sailing.gwt.ui.shared.UserDTO; +import com.sap.sailing.gwt.ui.usermanagement.UserRoles; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.async.MarkedAsyncCallback; -public class AdminConsoleEntryPoint extends AbstractEntryPoint implements RegattaRefresher { - private Set regattaDisplayers; +public class AdminConsoleEntryPoint extends AbstractEntryPoint implements RegattaRefresher, LeaderboardsRefresher, LeaderboardGroupsRefresher { + private Set regattasDisplayers; + private Set leaderboardsDisplayers; + private Set leaderboardGroupsDisplayers; private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); private final SailingServiceAsync sailingService = GWT.create(SailingService.class); private final MediaServiceAsync mediaService = GWT.create(MediaService.class); private final UserManagementServiceAsync userManagementService = GWT.create(UserManagementService.class); - + @Override protected void doOnModuleLoad() { super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) userManagementService, RemoteServiceMappingConstants.userManagementServiceRemotePath); - registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); - registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) userManagementService, RemoteServiceMappingConstants.userManagementServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath); + userManagementService.getUser(new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + reportError("Could not read user: " + caught.getMessage()); + } + + @Override + public void onSuccess(UserDTO result) { + createUI(result); + } + }); + } + + private void createUI(UserDTO user) { BetterDateTimeBox.initialize(); RootLayoutPanel rootPanel = RootLayoutPanel.get(); @@ -62,7 +89,7 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt DockPanel topInformationPanel = new DockPanel(); topInformationPanel.setSize("100%", "95%"); - UserStatusPanel userStatusPanel = new UserStatusPanel(userManagementService, this); + UserStatusPanel userStatusPanel = new UserStatusPanel(user); userStatusPanel.ensureDebugId("UserStatus"); topInformationPanel.add(userStatusPanel, DockPanel.WEST); topInformationPanel.add(persistentAlertLabel, DockPanel.CENTER); @@ -72,101 +99,101 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt dockPanel.addNorth(topInformationPanel, 2.5); - TabLayoutPanel tabPanel = new ScrolledTabLayoutPanel(2.5, Unit.EM, resources.arrowLeft(), resources.arrowRight(), 200); + final TabLayoutPanel tabPanel = new ScrolledTabLayoutPanel(2.5, Unit.EM, resources.arrowLeft(), resources.arrowRight(), 200); tabPanel.ensureDebugId("AdministrationTabs"); - regattaDisplayers = new HashSet(); + regattasDisplayers = new HashSet<>(); + leaderboardsDisplayers = new HashSet<>(); + leaderboardGroupsDisplayers = new HashSet<>(); - SailingEventManagementPanel sailingEventManagementPanel = new SailingEventManagementPanel(sailingService, this, - stringMessages); - sailingEventManagementPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, sailingEventManagementPanel, stringMessages.events()); + final EventManagementPanel eventManagementPanel = new EventManagementPanel(sailingService, this, stringMessages); + addToTabPanel(tabPanel, user, eventManagementPanel, stringMessages.events(), AdminConsoleFeatures.MANAGE_EVENTS); + leaderboardGroupsDisplayers.add(eventManagementPanel); - RegattaStructureManagementPanel eventStructureManagementPanel = new RegattaStructureManagementPanel( + RegattaStructureManagementPanel regattaManagementPanel = new RegattaStructureManagementPanel( sailingService, this, stringMessages, this); - eventStructureManagementPanel.ensureDebugId("RegattaStructureManagement"); - eventStructureManagementPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, eventStructureManagementPanel, stringMessages.regattas()); - regattaDisplayers.add(eventStructureManagementPanel); + regattaManagementPanel.ensureDebugId("RegattaStructureManagement"); + addToTabPanel(tabPanel, user, regattaManagementPanel, stringMessages.regattas(), AdminConsoleFeatures.MANAGE_REGATTAS); + regattasDisplayers.add(regattaManagementPanel); TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(sailingService, this, this, stringMessages); tractracEventManagementPanel.ensureDebugId("TracTracEventManagement"); - tractracEventManagementPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, tractracEventManagementPanel, stringMessages.tracTracEvents()); - regattaDisplayers.add(tractracEventManagementPanel); + addToTabPanel(tabPanel, user, tractracEventManagementPanel, stringMessages.tracTracEvents(), AdminConsoleFeatures.MANAGE_TRACKED_RACES); + regattasDisplayers.add(tractracEventManagementPanel); SwissTimingReplayConnectorPanel swissTimingReplayConnectorPanel = new SwissTimingReplayConnectorPanel( sailingService, this, this, stringMessages); - swissTimingReplayConnectorPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, swissTimingReplayConnectorPanel, stringMessages.swissTimingArchiveConnector()); - regattaDisplayers.add(swissTimingReplayConnectorPanel); + addToTabPanel(tabPanel, user, swissTimingReplayConnectorPanel, stringMessages.swissTimingArchiveConnector(), AdminConsoleFeatures.MANAGE_TRACKED_RACES); + regattasDisplayers.add(swissTimingReplayConnectorPanel); SwissTimingEventManagementPanel swisstimingEventManagementPanel = new SwissTimingEventManagementPanel( sailingService, this, this, stringMessages); // swisstimingEventManagementPanel.ensureDebugId("SwissTimingEventManagement"); - swisstimingEventManagementPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, swisstimingEventManagementPanel, stringMessages.swissTimingEvents()); - regattaDisplayers.add(swisstimingEventManagementPanel); + addToTabPanel(tabPanel, user, swisstimingEventManagementPanel, stringMessages.swissTimingEvents(), AdminConsoleFeatures.MANAGE_TRACKED_RACES); + regattasDisplayers.add(swisstimingEventManagementPanel); + + final RaceLogTrackingEventManagementPanel raceLogTrackingEventManagementPanel = new RaceLogTrackingEventManagementPanel( + sailingService, this, this, this, stringMessages); + addToTabPanel(tabPanel, user, raceLogTrackingEventManagementPanel, stringMessages.raceLogTracking(), AdminConsoleFeatures.MANAGE_RACELOG_TRACKING); + regattasDisplayers.add(raceLogTrackingEventManagementPanel); + leaderboardsDisplayers.add(raceLogTrackingEventManagementPanel); IgtimiAccountsPanel igtimiAccountsPanel = new IgtimiAccountsPanel(sailingService, this, stringMessages); igtimiAccountsPanel.ensureDebugId("IgtimiAccounts"); - igtimiAccountsPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, igtimiAccountsPanel, stringMessages.igtimiAccounts()); + addToTabPanel(tabPanel, user, igtimiAccountsPanel, stringMessages.igtimiAccounts(), AdminConsoleFeatures.MANAGE_IGTIMI_ACCOUNTS); TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(sailingService, this, this, stringMessages); trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); - trackedRacesManagementPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, trackedRacesManagementPanel, stringMessages.trackedRaces()); - regattaDisplayers.add(trackedRacesManagementPanel); + addToTabPanel(tabPanel, user, trackedRacesManagementPanel, stringMessages.trackedRaces(), AdminConsoleFeatures.SHOW_TRACKED_RACES); + regattasDisplayers.add(trackedRacesManagementPanel); final CompetitorPanel competitorPanel = new CompetitorPanel(sailingService, stringMessages, this); - // trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); - trackedRacesManagementPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, competitorPanel, stringMessages.competitors()); + addToTabPanel(tabPanel, user, competitorPanel, stringMessages.competitors(), AdminConsoleFeatures.MANAGE_ALL_COMPETITORS); - RaceCourseManagementPanel raceCourseManagementPanel = new RaceCourseManagementPanel(sailingService, this, this, - stringMessages); + RaceCourseManagementPanel raceCourseManagementPanel = new RaceCourseManagementPanel(sailingService, this, this, stringMessages); // raceCourseManagementPanel.ensureDebugId("RaceCourseManagement"); - raceCourseManagementPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, raceCourseManagementPanel, stringMessages.courseLayout()); - regattaDisplayers.add(raceCourseManagementPanel); + addToTabPanel(tabPanel, user, raceCourseManagementPanel, stringMessages.courseLayout(), AdminConsoleFeatures.MANAGE_COURSE_LAYOUT); + regattasDisplayers.add(raceCourseManagementPanel); final AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); WindPanel windPanel = new WindPanel(sailingService, asyncActionsExecutor, this, this, stringMessages); // windPanel.ensureDebugId("WindManagement"); - regattaDisplayers.add(windPanel); - windPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, windPanel, stringMessages.wind()); + regattasDisplayers.add(windPanel); + addToTabPanel(tabPanel, user, windPanel, stringMessages.wind(), AdminConsoleFeatures.MANAGE_WIND); final LeaderboardGroupConfigPanel leaderboardGroupConfigPanel = new LeaderboardGroupConfigPanel(sailingService, - this, this, stringMessages); + this, this, this, this, stringMessages); leaderboardGroupConfigPanel.ensureDebugId("LeaderboardGroupConfiguration"); - leaderboardGroupConfigPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, leaderboardGroupConfigPanel, stringMessages.leaderboardGroupConfiguration()); - regattaDisplayers.add(leaderboardGroupConfigPanel); + addToTabPanel(tabPanel, user, leaderboardGroupConfigPanel, stringMessages.leaderboardGroupConfiguration(), AdminConsoleFeatures.MANAGE_LEADERBOARD_GROUPS); + regattasDisplayers.add(leaderboardGroupConfigPanel); + leaderboardGroupsDisplayers.add(leaderboardGroupConfigPanel); + leaderboardsDisplayers.add(leaderboardGroupConfigPanel); final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, - stringMessages, - /* showRaceDetails */true); + stringMessages, /* showRaceDetails */true, this); leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration"); - leaderboardConfigPanel.setSize("90%", "90%"); - addScrollableTab(tabPanel, leaderboardConfigPanel, stringMessages.leaderboardConfiguration()); - regattaDisplayers.add(leaderboardConfigPanel); + addToTabPanel(tabPanel, user, leaderboardConfigPanel, stringMessages.leaderboardConfiguration(), AdminConsoleFeatures.MANAGE_LEADERBOARDS); + regattasDisplayers.add(leaderboardConfigPanel); + leaderboardsDisplayers.add(leaderboardConfigPanel); - addScrollableTab(tabPanel, new ResultImportUrlsManagementPanel(sailingService, this, stringMessages), - stringMessages.resultImportUrls()); + ResultImportUrlsManagementPanel resultImportUrlsManagementPanel = new ResultImportUrlsManagementPanel(sailingService, this, stringMessages); + addToTabPanel(tabPanel, user, resultImportUrlsManagementPanel, stringMessages.resultImportUrls(), AdminConsoleFeatures.MANAGE_RESULT_IMPORT_URLS); - ReplicationPanel replicationPanel = new ReplicationPanel(sailingService, this, stringMessages); + RemoteSailingServerInstancesManagementPanel sailingServerInstancesManagementPanel = new RemoteSailingServerInstancesManagementPanel(sailingService, this, stringMessages); + addToTabPanel(tabPanel, user, sailingServerInstancesManagementPanel, stringMessages.sailingServers(), AdminConsoleFeatures.MANAGE_SAILING_SERVER_INSTANCES); + + final ReplicationPanel replicationPanel = new ReplicationPanel(sailingService, this, stringMessages); // replicationPanel.ensureDebugId("ReplicationManagement"); - addScrollableTab(tabPanel, replicationPanel, stringMessages.replication()); + addToTabPanel(tabPanel, user, replicationPanel, stringMessages.replication(), AdminConsoleFeatures.MANAGE_REPLICATION); + final MediaPanel mediaPanel = new MediaPanel(mediaService, this, stringMessages); - addScrollableTab(tabPanel, mediaPanel, stringMessages.mediaPanel()); + addToTabPanel(tabPanel, user, mediaPanel, stringMessages.mediaPanel(), AdminConsoleFeatures.MANAGE_MEDIA); final MasterDataImportPanel masterDataImportPanel = new MasterDataImportPanel(stringMessages, sailingService, - this, sailingEventManagementPanel, leaderboardGroupConfigPanel); - addScrollableTab(tabPanel, masterDataImportPanel, stringMessages.masterDataImportPanel()); + this, eventManagementPanel, this, this); + addToTabPanel(tabPanel, user, masterDataImportPanel, stringMessages.masterDataImportPanel(), AdminConsoleFeatures.MANAGE_MASTERDATA_IMPORT); /*final DeviceConfigurationPanel deviceConfigurationAdminPanel = new DeviceConfigurationPanel(sailingService, stringMessages, this); @@ -174,24 +201,33 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt final DeviceConfigurationPanel deviceConfigurationUserPanel = new DeviceConfigurationUserPanel(sailingService, stringMessages, this); - addScrollableTab(tabPanel, deviceConfigurationUserPanel, stringMessages.deviceConfiguration()); + addToTabPanel(tabPanel, user, deviceConfigurationUserPanel, stringMessages.deviceConfiguration(), AdminConsoleFeatures.MANAGE_DEVICE_CONFIGURATION); tabPanel.selectTab(0); tabPanel.addSelectionHandler(new SelectionHandler() { @Override public void onSelection(SelectionEvent event) { - if (leaderboardConfigPanel.isVisible()) { - leaderboardConfigPanel.loadLeaderboards(); - } - if (mediaPanel.isVisible()) { + final Widget selectedPanel = ((ScrollPanel) tabPanel.getWidget(tabPanel.getSelectedIndex())).getWidget(); + if (selectedPanel == eventManagementPanel) { + eventManagementPanel.fillEvents(); + fillLeaderboardGroups(); + } else if (selectedPanel == leaderboardConfigPanel) { + fillLeaderboards(); + } else if (selectedPanel == mediaPanel) { mediaPanel.onShow(); - } - if (competitorPanel.isVisible()) { + } else if (selectedPanel == competitorPanel) { competitorPanel.refreshCompetitorList(); + } else if (selectedPanel == raceLogTrackingEventManagementPanel) { + fillLeaderboards(); + } else if (selectedPanel == leaderboardGroupConfigPanel) { + fillLeaderboardGroups(); + fillLeaderboards(); } } }); fillRegattas(); + fillLeaderboardGroups(); + fillLeaderboards(); SystemInformationPanel sysinfoPanel = new SystemInformationPanel(sailingService, this); sysinfoPanel.ensureDebugId("SystemInformation"); @@ -199,26 +235,96 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt dockPanel.add(tabPanel); } - private void addScrollableTab(TabLayoutPanel tabPanel, Widget widget, String tabTitle) { - ScrollPanel widgetScroller = new ScrollPanel(); - widgetScroller.add(widget); - tabPanel.add(widgetScroller, tabTitle, false); + private void addToTabPanel(TabLayoutPanel tabPanel, UserDTO user, Panel panelToAdd, String tabTitle, AdminConsoleFeatures feature) { + if(user != null && isUserInRole(user, feature.getEnabledRoles())) { + ScrollPanel scrollPanel = new ScrollPanel(); + scrollPanel.add(panelToAdd); + panelToAdd.setSize("90%", "90%"); + tabPanel.add(scrollPanel, tabTitle, false); + } + } + + private boolean isUserInRole(UserDTO user, UserRoles[] roles) { + boolean result = false; + for(UserRoles enabledRole: roles) { + if (user.roles.contains(enabledRole.name())) { + result = true; + break; + } + } + return result; + } + + @Override + public void fillLeaderboards() { + sailingService.getLeaderboards(new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onSuccess(List leaderboards) { + for (LeaderboardsDisplayer leaderboardsDisplayer : leaderboardsDisplayers) { + leaderboardsDisplayer.fillLeaderboards(leaderboards); + } + } + + @Override + public void onFailure(Throwable t) { + reportError("Error trying to obtain list of leaderboards: "+ t.getMessage()); + } + })); + } + + @Override + public void updateLeaderboards(Iterable updatedLeaderboards, LeaderboardsDisplayer origin) { + for (LeaderboardsDisplayer leaderboardsDisplayer : leaderboardsDisplayers) { + if (leaderboardsDisplayer != origin) { + leaderboardsDisplayer.fillLeaderboards(updatedLeaderboards); + } + } + } + + @Override + public void fillLeaderboardGroups() { + sailingService.getLeaderboardGroups(false /*withGeoLocationData*/, + new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onSuccess(List groups) { + for (LeaderboardGroupsDisplayer leaderboardGroupsDisplayer : leaderboardGroupsDisplayers) { + leaderboardGroupsDisplayer.fillLeaderboardGroups(groups); + } + } + @Override + public void onFailure(Throwable t) { + reportError("Error trying to obtain list of leaderboard groups: " + t.getMessage()); + } + })); + } + + @Override + public void updateLeaderboardGroups(Iterable updatedLeaderboardGroups, + LeaderboardGroupsDisplayer origin) { + for (LeaderboardGroupsDisplayer leaderboardGroupsDisplayer : leaderboardGroupsDisplayers) { + if (leaderboardGroupsDisplayer != origin) { + leaderboardGroupsDisplayer.fillLeaderboardGroups(updatedLeaderboardGroups); + } + } } @Override public void fillRegattas() { - sailingService.getRegattas(new AsyncCallback>() { - @Override - public void onSuccess(List result) { - for (RegattaDisplayer regattaDisplayer : regattaDisplayers) { - regattaDisplayer.fillRegattas(result); - } - } - - @Override - public void onFailure(Throwable caught) { - reportError("Remote Procedure Call getRegattas() - Failure"); - } - }); + sailingService.getRegattas(new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onSuccess(List result) { + for (RegattasDisplayer regattaDisplayer : regattasDisplayers) { + regattaDisplayer.fillRegattas(result); + } + } + + @Override + public void onFailure(Throwable caught) { + reportError("Remote Procedure Call getRegattas() - Failure"); + } + })); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleFeatures.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleFeatures.java new file mode 100644 index 00000000000..a0a6b1f2867 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleFeatures.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.sap.sailing.gwt.ui.usermanagement.UserRoles; + +public enum AdminConsoleFeatures { + + MANAGE_EVENTS (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_REGATTAS (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_TRACKED_RACES (UserRoles.eventmanager, UserRoles.administrator), + SHOW_TRACKED_RACES (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_RACELOG_TRACKING (UserRoles.administrator), + MANAGE_ALL_COMPETITORS (UserRoles.administrator), + MANAGE_COURSE_LAYOUT (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_WIND (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_IGTIMI_ACCOUNTS (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_LEADERBOARDS (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_LEADERBOARD_GROUPS (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_RESULT_IMPORT_URLS (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_MEDIA (UserRoles.eventmanager, UserRoles.administrator), + MANAGE_SAILING_SERVER_INSTANCES (UserRoles.administrator), + MANAGE_REPLICATION (UserRoles.administrator), + MANAGE_MASTERDATA_IMPORT (UserRoles.administrator), + MANAGE_DEVICE_CONFIGURATION (UserRoles.eventmanager, UserRoles.administrator); + + private UserRoles[] enabledRoles; + + AdminConsoleFeatures(UserRoles... enabledRoles) { + this.enabledRoles = enabledRoles; + } + + public UserRoles[] getEnabledRoles() { + return enabledRoles; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java index 1a486f4eba0..5a8d8debba0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java @@ -61,4 +61,37 @@ interface AdminConsoleResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/xml.png") ImageResource exportXMLIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/add_racelog_tracker.png") + ImageResource addRaceLogTracker(); + + @Source("com/sap/sailing/gwt/ui/client/images/denote_for_racelog_tracking.png") + ImageResource denoteForRaceLogTracking(); + + @Source("com/sap/sailing/gwt/ui/client/images/undenote_for_racelog_tracking.png") + ImageResource unDenoteForRaceLogTracking(); + + @Source("com/sap/sailing/gwt/ui/client/images/start_racelog_tracking.png") + ImageResource startRaceLogTracking(); + + @Source("com/sap/sailing/gwt/ui/client/images/competitor_registrations.png") + ImageResource competitorRegistrations(); + + @Source("com/sap/sailing/gwt/ui/client/images/define_course.png") + ImageResource defineCourse(); + + @Source("com/sap/sailing/gwt/ui/client/images/map_devices.png") + ImageResource mapDevices(); + + @Source("com/sap/sailing/gwt/ui/client/images/ping.png") + ImageResource ping(); + + @Source("com/sap/sailing/gwt/ui/client/images/copy_course.png") + ImageResource copyCourse(); + + @Source("com/sap/sailing/gwt/ui/client/images/copy.png") + ImageResource copy(); + + @Source("com/sap/sailing/gwt/ui/client/images/close_time_range.png") + ImageResource closeTimeRange(); } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ColorColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ColorColumn.java new file mode 100644 index 00000000000..495f9183565 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ColorColumn.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.cell.client.SafeHtmlCell; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.Column; +import com.sap.sailing.domain.common.Color; + +public class ColorColumn extends Column { + public interface ColorRetriever { + Color getColor(T t); + } + + private final ColorRetriever colorRetriever; + + public ColorColumn(ColorRetriever colorRetriever) { + super(new SafeHtmlCell()); + this.colorRetriever = colorRetriever; + } + + @Override + public SafeHtml getValue(T t) { + SafeHtmlBuilder sb = new SafeHtmlBuilder(); + if (colorRetriever.getColor(t) != null) { + String colorString = colorRetriever.getColor(t).getAsHtml(); + sb.appendHtmlConstant(""); + sb.appendHtmlConstant(colorString); + sb.appendHtmlConstant(""); + } else { + sb.appendHtmlConstant(" "); + } + return sb.toSafeHtml(); + } +}; \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index 184c2746ac1..170d60f6852 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -13,12 +13,12 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.CountryCode; import com.sap.sailing.domain.common.CountryCodeFactory; +import com.sap.sailing.domain.common.dto.BoatClassDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTOImpl; import com.sap.sailing.domain.common.impl.RGBColor; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.dialog.DataEntryDialog; /** @@ -33,6 +33,7 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class CompetitorEditDialog extends DataEntryDialog { private final CompetitorDTO competitorToEdit; private final TextBox name; + private final ListBox boatClassName; private final TextBox displayColorTextBox; private final ListBox threeLetterIocCountryCode; private final TextBox sailId; @@ -54,24 +55,50 @@ public class CompetitorEditDialog extends DataEntryDialog { if (displayColor instanceof InvalidColor) { result = displayColor.getAsHtml(); } + } else if (valueToValidate.getBoatClass().getName() == null || valueToValidate.getBoatClass().getName().isEmpty()) { + result = stringMessages.pleaseEnterABoatClass(); } return result; } }, /* animationEnabled */true, callback); this.stringMessages = stringMessages; this.competitorToEdit = competitorToEdit; + + this.boatClassName = createListBox(/* isMultipleSelect */ false); + int i=0; + List boatClassNamesList = new ArrayList(); + for (BoatTypes t : BoatTypes.values()) { + boatClassNamesList.add(t.getName()); + } + String competitorsBoatClassName = competitorToEdit.getBoatClass() != null ? competitorToEdit.getBoatClass().getName() : null; + Collections.sort(boatClassNamesList); + for (String name : boatClassNamesList) { + boatClassName.addItem(name); + if (name.equals(competitorsBoatClassName)) { + boatClassName.setSelectedIndex(i); + } + i++; + } + if (boatClassName.getSelectedIndex() == -1 && competitorsBoatClassName != null) { + boatClassName.addItem(competitorsBoatClassName); + boatClassName.setSelectedIndex(i); + } + if (competitorToEdit.getBoatClass() != null) { + boatClassName.setEnabled(false); + } + this.name = createTextBox(competitorToEdit.getName()); this.displayColorTextBox = createTextBox(competitorToEdit.getColor() == null ? "" : competitorToEdit.getColor().getAsHtml()); this.threeLetterIocCountryCode = createListBox(/* isMultipleSelect */ false); CountryCodeFactory ccf = CountryCodeFactory.INSTANCE; - int i=0; + i=0; List ccs = new ArrayList(); Util.addAll(ccf.getAll(), ccs); ccs.add(null); // representing no nationality (NONE / white flag) Collections.sort(ccs, new Comparator() { @Override public int compare(CountryCode o1, CountryCode o2) { - return Util.compareToWithNull(o1 == null ? null : o1.getThreeLetterIOCCode(), o2 == null ? null : o2.getThreeLetterIOCCode()); + return Util.compareToWithNull(o1 == null ? null : o1.getThreeLetterIOCCode(), o2 == null ? null : o2.getThreeLetterIOCCode(), /* nullIsLess */ true); } }); for (CountryCode cc : ccs) { @@ -113,12 +140,12 @@ public class CompetitorEditDialog extends DataEntryDialog { } @Override - public Triple getAsRGB() { + public com.sap.sse.common.Util.Triple getAsRGB() { return null; } @Override - public Triple getAsHSV() { + public com.sap.sse.common.Util.Triple getAsHSV() { return null; } @@ -132,22 +159,27 @@ public class CompetitorEditDialog extends DataEntryDialog { @Override protected CompetitorDTO getResult() { Color color; - try { - color = new RGBColor(displayColorTextBox.getText()); - } catch (IllegalArgumentException iae) { - color = new InvalidColor(iae); + if (displayColorTextBox.getText() == null || displayColorTextBox.getText().isEmpty()) { + color = null; + } else { + try { + color = new RGBColor(displayColorTextBox.getText()); + } catch (IllegalArgumentException iae) { + color = new InvalidColor(iae); + } } + BoatClassDTO boatClass = new BoatClassDTO(boatClassName.getValue(boatClassName.getSelectedIndex()), 0); CompetitorDTO result = new CompetitorDTOImpl(name.getText(), color, /* twoLetterIsoCountryCode */ null, threeLetterIocCountryCode.getValue(threeLetterIocCountryCode.getSelectedIndex()), /* countryName */ null, sailId.getText(), competitorToEdit.getIdAsString(), - competitorToEdit.getBoatClass()); + boatClass); return result; } @Override protected Widget getAdditionalWidget() { - Grid result = new Grid(4, 2); + Grid result = new Grid(5, 2); result.setWidget(0, 0, new Label(stringMessages.name())); result.setWidget(0, 1, name); result.setWidget(1, 0, new Label(stringMessages.sailNumber())); @@ -156,6 +188,8 @@ public class CompetitorEditDialog extends DataEntryDialog { result.setWidget(2, 1, threeLetterIocCountryCode); result.setWidget(3, 0, new Label(stringMessages.color())); result.setWidget(3, 1, displayColorTextBox); + result.setWidget(4, 0, new Label(stringMessages.boatClass())); + result.setWidget(4, 1, boatClassName); return result; } 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 3a243f8d61f..3ac56593223 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 @@ -2,41 +2,26 @@ 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.cell.client.FieldUpdater; -import com.google.gwt.cell.client.SafeHtmlCell; -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.resources.client.ImageResource; -import com.google.gwt.safehtml.shared.SafeHtml; -import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -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; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.ImageResourceRenderer; -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.SelectionChangeEvent.Handler; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.impl.NaturalComparator; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.common.dto.CompetitorDTOImpl; import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.FlagImageResolver; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.shared.panels.LabeledAbstractFilterablePanel; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; /** @@ -49,14 +34,9 @@ public class CompetitorPanel extends SimplePanel { private final SailingServiceAsync sailingService; private final StringMessages stringMessages; private final ErrorReporter errorReporter; - private CellTable competitorTable; - private MultiSelectionModel competitorSelectionModel; - private ListDataProvider competitorProvider; - private List allCompetitors; + private final CompetitorTableWrapper> competitorTable; + private final MultiSelectionModel competitorSelectionModel; private final String leaderboardName; - private LabeledAbstractFilterablePanel filterField; - - private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); public CompetitorPanel(final SailingServiceAsync sailingService, final StringMessages stringMessages, final ErrorReporter errorReporter) { @@ -70,8 +50,9 @@ public class CompetitorPanel extends SimplePanel { this.stringMessages = stringMessages; this.errorReporter = errorReporter; this.leaderboardName = leaderboardName; - competitorProvider = new ListDataProvider(); - ListHandler competitorColumnListHandler = new ListHandler(competitorProvider.getList()); + this.competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, + new MultiSelectionModel()); + this.competitorSelectionModel = competitorTable.getSelectionModel(); VerticalPanel mainPanel = new VerticalPanel(); this.setWidget(mainPanel); mainPanel.setWidth("100%"); @@ -96,80 +77,18 @@ public class CompetitorPanel extends SimplePanel { } }); buttonPanel.add(allowReloadButton); + Button addCompetitorButton = new Button(stringMessages.add()); + addCompetitorButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + openAddCompetitorDialog(); + } + }); + buttonPanel.add(addCompetitorButton); competitorsPanel.add(buttonPanel); - // competitors table - TextColumn competitorNameColumn = new TextColumn() { - @Override - public String getValue(CompetitorDTO competitor) { - return competitor.getName(); - } - }; - competitorNameColumn.setSortable(true); - competitorColumnListHandler.setComparator(competitorNameColumn, new Comparator() { - @Override - public int compare(CompetitorDTO o1, CompetitorDTO o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - - TextColumn boatClassColumn = new TextColumn() { - @Override - public String getValue(CompetitorDTO competitor) { - return competitor.getBoatClass() != null ? competitor.getBoatClass().getName() : ""; - } - }; - boatClassColumn.setSortable(true); - competitorColumnListHandler.setComparator(boatClassColumn, new Comparator() { - @Override - public int compare(CompetitorDTO o1, CompetitorDTO o2) { - return new NaturalComparator(false).compare(o1.getBoatClass().getName(), o2.getBoatClass().getName()); - } - }); - - Column sailIdColumn = new Column(new SafeHtmlCell()) { - @Override - public SafeHtml getValue(CompetitorDTO competitor) { - SafeHtmlBuilder sb = new SafeHtmlBuilder(); - ImageResourceRenderer renderer = new ImageResourceRenderer(); - final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode(); - final ImageResource flagImageResource; - if (twoLetterIsoCountryCode==null || twoLetterIsoCountryCode.isEmpty()) { - flagImageResource = FlagImageResolver.getEmptyFlagImageResource(); - } else { - flagImageResource = FlagImageResolver.getFlagImageResource(twoLetterIsoCountryCode); - } - if (flagImageResource != null) { - sb.append(renderer.render(flagImageResource)); - sb.appendHtmlConstant(" "); - } - sb.appendEscaped(competitor.getSailID()); - return sb.toSafeHtml(); - } - }; - sailIdColumn.setSortable(true); - competitorColumnListHandler.setComparator(sailIdColumn, new Comparator() { - @Override - public int compare(CompetitorDTO o1, CompetitorDTO o2) { - return new NaturalComparator(false).compare(o1.getSailID(), o2.getSailID()); - } - }); - - Column displayColorColumn = new Column(new SafeHtmlCell()) { - @Override - public SafeHtml getValue(CompetitorDTO competitor) { - SafeHtmlBuilder sb = new SafeHtmlBuilder(); - if (competitor.getColor() != null) { - sb.appendHtmlConstant(""); - sb.appendHtmlConstant(competitor.getColor().getAsHtml()); - sb.appendHtmlConstant(""); - } else { - sb.appendHtmlConstant(" "); - } - return sb.toSafeHtml(); - } - }; + //competitor table ImagesBarColumn competitorActionColumn = new ImagesBarColumn( new CompetitorConfigImagesBarCell(stringMessages)); competitorActionColumn.setFieldUpdater(new FieldUpdater() { @@ -184,46 +103,7 @@ public class CompetitorPanel extends SimplePanel { }); - TextColumn competitorIdColumn = new TextColumn() { - @Override - public String getValue(CompetitorDTO competitor) { - return competitor.getIdAsString(); - } - - }; - - competitorIdColumn.setSortable(true); - competitorColumnListHandler.setComparator(competitorIdColumn, new Comparator() { - @Override - public int compare(CompetitorDTO o1, CompetitorDTO o2) { - return new NaturalComparator(false).compare(o1.getIdAsString(), o2.getIdAsString()); - } - }); - competitorTable = new CellTable(10000, tableRes); - filterField = new LabeledAbstractFilterablePanel(new Label(stringMessages.filterCompetitors()), - allCompetitors, competitorTable, competitorProvider) { - - @Override - public Iterable getSearchableStrings(CompetitorDTO t) { - List string = new ArrayList(); - string.add(t.getName()); - string.add(t.getSailID()); - string.add(t.getBoatClass().getName()); - return string; - } - }; - - mainPanel.add(filterField); - competitorProvider.addDataDisplay(competitorTable); - competitorTable.addColumnSortHandler(competitorColumnListHandler); - competitorTable.addColumn(sailIdColumn, stringMessages.sailNumber()); - competitorTable.addColumn(competitorNameColumn, stringMessages.name()); - competitorTable.addColumn(boatClassColumn, stringMessages.boatClass()); - competitorTable.addColumn(displayColorColumn, stringMessages.color()); - competitorTable.addColumn(competitorIdColumn, stringMessages.id()); - competitorTable.addColumn(competitorActionColumn, stringMessages.actions()); - competitorSelectionModel = new MultiSelectionModel(); - competitorTable.setSelectionModel(competitorSelectionModel); + competitorTable.getTable().addColumn(competitorActionColumn, stringMessages.actions()); mainPanel.add(competitorTable); competitorSelectionModel.addSelectionChangeHandler(new Handler() { @Override @@ -252,11 +132,15 @@ public class CompetitorPanel extends SimplePanel { }); } + private void openAddCompetitorDialog() { + openEditCompetitorDialog(new CompetitorDTOImpl()); + } + private void openEditCompetitorDialog(CompetitorDTO competitor) { new CompetitorEditDialog(stringMessages, competitor, new DialogCallback() { @Override public void ok(CompetitorDTO competitor) { - sailingService.updateCompetitor(competitor, new AsyncCallback() { + sailingService.addOrUpdateCompetitor(competitor, new AsyncCallback() { @Override public void onFailure(Throwable caught) { errorReporter.reportError("Error trying to update competitor: "+caught.getMessage()); @@ -264,14 +148,7 @@ public class CompetitorPanel extends SimplePanel { @Override public void onSuccess(CompetitorDTO updatedCompetitor) { - // replace updated competitor in competitor list - for (int i=0; i>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage()); - } - - @Override - public void onSuccess(Iterable result) { - getFilteredCompetitors(result); - } - }); - } else { - sailingService.getCompetitors(new AsyncCallback>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage()); - } - - @Override - public void onSuccess(Iterable result) { - getFilteredCompetitors(result); - } - }); - } - } - - private void getFilteredCompetitors(Iterable result) { - allCompetitors = new ArrayList(); - for (CompetitorDTO c : result) { - allCompetitors.add(c); - } - filterField.updateAll(allCompetitors); + + public void refreshCompetitorList() { + competitorTable.refreshCompetitorList(leaderboardName); } } 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 new file mode 100644 index 00000000000..bdf904378e2 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -0,0 +1,203 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import com.google.gwt.cell.client.SafeHtmlCell; +import com.google.gwt.core.client.Callback; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +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; +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.Color; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.impl.NaturalComparator; +import com.sap.sailing.gwt.ui.adminconsole.ColorColumn.ColorRetriever; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.FlagImageResolver; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; + +public class CompetitorTableWrapper> extends TableWrapper { + private final LabeledAbstractFilterablePanel filterField; + + public CompetitorTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,ErrorReporter errorReporter, + S selectionModel) { + super(sailingService, stringMessages, errorReporter, selectionModel); + + ListHandler competitorColumnListHandler = new ListHandler(dataProvider.getList()); + + // competitors table + TextColumn competitorNameColumn = new TextColumn() { + @Override + public String getValue(CompetitorDTO competitor) { + return competitor.getName(); + } + }; + competitorNameColumn.setSortable(true); + competitorColumnListHandler.setComparator(competitorNameColumn, new Comparator() { + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + + TextColumn boatClassColumn = new TextColumn() { + @Override + public String getValue(CompetitorDTO competitor) { + return competitor.getBoatClass() != null ? competitor.getBoatClass().getName() : ""; + } + }; + boatClassColumn.setSortable(true); + competitorColumnListHandler.setComparator(boatClassColumn, new Comparator() { + private final NaturalComparator comparator = new NaturalComparator(/* caseSensitive */ false); + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return comparator.compare(o1.getBoatClass().getName(), o2.getBoatClass().getName()); + } + }); + + Column sailIdColumn = new Column(new SafeHtmlCell()) { + @Override + public SafeHtml getValue(CompetitorDTO competitor) { + SafeHtmlBuilder sb = new SafeHtmlBuilder(); + ImageResourceRenderer renderer = new ImageResourceRenderer(); + final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode(); + final ImageResource flagImageResource; + if (twoLetterIsoCountryCode==null || twoLetterIsoCountryCode.isEmpty()) { + flagImageResource = FlagImageResolver.getEmptyFlagImageResource(); + } else { + flagImageResource = FlagImageResolver.getFlagImageResource(twoLetterIsoCountryCode); + } + if (flagImageResource != null) { + sb.append(renderer.render(flagImageResource)); + sb.appendHtmlConstant(" "); + } + sb.appendEscaped(competitor.getSailID()); + return sb.toSafeHtml(); + } + }; + sailIdColumn.setSortable(true); + competitorColumnListHandler.setComparator(sailIdColumn, new Comparator() { + private final NaturalComparator comparator = new NaturalComparator(/* case sensitive */ false); + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return comparator.compare(o1.getSailID(), o2.getSailID()); + } + }); + + Column displayColorColumn = new ColorColumn<>(new ColorRetriever() { + @Override + public Color getColor(CompetitorDTO t) { + return t.getColor(); + } + }); + + TextColumn competitorIdColumn = new TextColumn() { + @Override + public String getValue(CompetitorDTO competitor) { + return competitor.getIdAsString(); + } + + }; + + competitorIdColumn.setSortable(true); + competitorColumnListHandler.setComparator(competitorIdColumn, new Comparator() { + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return new NaturalComparator(false).compare(o1.getIdAsString(), o2.getIdAsString()); + } + }); + + filterField = new LabeledAbstractFilterablePanel(new Label(stringMessages.filterCompetitors()), + new ArrayList(), table, dataProvider) { + + @Override + public Iterable getSearchableStrings(CompetitorDTO t) { + List string = new ArrayList(); + string.add(t.getName()); + string.add(t.getSailID()); + string.add(t.getBoatClass().getName()); + return string; + } + }; + + mainPanel.add(filterField); + table.addColumnSortHandler(competitorColumnListHandler); + table.addColumn(sailIdColumn, stringMessages.sailNumber()); + table.addColumn(competitorNameColumn, stringMessages.name()); + table.addColumn(boatClassColumn, stringMessages.boatClass()); + table.addColumn(displayColorColumn, stringMessages.color()); + table.addColumn(competitorIdColumn, stringMessages.id()); + mainPanel.add(table); + } + + public Iterable getAllCompetitors() { + return filterField.getAll(); + } + + public LabeledAbstractFilterablePanel getFilterField() { + return filterField; + } + + public void refreshCompetitorList(Iterable competitors) { + getFilteredCompetitors(competitors); + } + + public void refreshCompetitorList(String leaderboardName) { + refreshCompetitorList(leaderboardName, false, null); + } + + public void refreshCompetitorList(String leaderboardName, boolean lookInRaceLogs) { + refreshCompetitorList(leaderboardName, lookInRaceLogs, null); + } + + /** + * @param leaderboardName If null, all existing competitors are loaded + */ + public void refreshCompetitorList(String leaderboardName, boolean lookInRaceLogs, final Callback, + Throwable> callback) { + if(leaderboardName != null) { + sailingService.getCompetitorsOfLeaderboard(leaderboardName, lookInRaceLogs, new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage()); + if (callback != null) callback.onFailure(caught); + } + + @Override + public void onSuccess(Iterable result) { + refreshCompetitorList(result); + if (callback != null) callback.onSuccess(result); + } + }); + } else { + sailingService.getCompetitors(new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage()); + if (callback != null) callback.onFailure(caught); + } + + @Override + public void onSuccess(Iterable result) { + getFilteredCompetitors(result); + refreshCompetitorList(result); + if (callback != null) callback.onSuccess(result); + } + }); + } + } + + private void getFilteredCompetitors(Iterable result) { + filterField.updateAll(result); + } +} 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 new file mode 100644 index 00000000000..1002b056cbc --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ControlPointTableWrapper.java @@ -0,0 +1,41 @@ +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.ErrorReporter; +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; + +public class ControlPointTableWrapper> extends TableWrapper { + public ControlPointTableWrapper(S selectionModel, SailingServiceAsync sailingService, final StringMessages stringMessages, + ErrorReporter errorReporter) { + super(sailingService, stringMessages, errorReporter, selectionModel); + + mainPanel.add(table); + + TextColumn nameColumn = new TextColumn() { + @Override + public String getValue(ControlPointDTO d) { + return d.getName(); + } + }; + table.addColumn(nameColumn, stringMessages.name()); + + TextColumn typeColumn = new TextColumn() { + @Override + public String getValue(ControlPointDTO d) { + if (d instanceof GateDTO) { + return "2x " + stringMessages.mark(); + } else if (d instanceof MarkDTO) { + return stringMessages.mark(); + } else { + return stringMessages.unknown(); + } + } + }; + table.addColumn(typeColumn, stringMessages.type()); + } +} 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 new file mode 100644 index 00000000000..c823af68a0f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidget.java @@ -0,0 +1,283 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gwt.cell.client.FieldUpdater; +import com.google.gwt.core.shared.GWT; +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.CaptionPanel; +import com.google.gwt.user.client.ui.Grid; +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.ErrorReporter; +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.sailing.gwt.ui.shared.RaceCourseDTO; +import com.sap.sailing.gwt.ui.shared.WaypointDTO; +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 Grid mainPanel; + + protected final SailingServiceAsync sailingService; + protected final ErrorReporter errorReporter; + protected final StringMessages stringMessages; + + protected final HorizontalPanel waypointsBtnsPanel; + protected final HorizontalPanel controlPointsBtnsPanel; + protected final HorizontalPanel marksBtnsPanel; + protected final HorizontalPanel buttonsPanel; + + protected final Button insertWaypointBefore; + protected final Button insertWaypointAfter; + + protected final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); + + @Override + public Widget asWidget() { + return mainPanel; + } + + public CourseManagementWidget(final SailingServiceAsync sailingService, ErrorReporter errorReporter, + final StringMessages stringMessages) { + this.sailingService = sailingService; + this.errorReporter = errorReporter; + this.stringMessages = stringMessages; + + mainPanel = new Grid(3, 3); + mainPanel.setCellPadding(5); + mainPanel.getRowFormatter().setVerticalAlign(0, HasVerticalAlignment.ALIGN_TOP); + + waypoints = new WaypointTableWrapper>( + new SingleSelectionModel(), sailingService, stringMessages, errorReporter); + multiMarkControlPoints = new ControlPointTableWrapper>( + new MultiSelectionModel(), sailingService, stringMessages, errorReporter); + marks = new MarkTableWrapper>( + new MultiSelectionModel(), sailingService, stringMessages, errorReporter); + + CaptionPanel waypointsPanel = new CaptionPanel(stringMessages.waypoints()); + CaptionPanel controlPointsPanel = new CaptionPanel(stringMessages.twoMarkControlPoint()); + CaptionPanel marksPanel = new CaptionPanel(stringMessages.mark()); + waypointsPanel.add(waypoints); + controlPointsPanel.add(multiMarkControlPoints); + marksPanel.add(marks); + mainPanel.setWidget(0, 0, waypointsPanel); + mainPanel.setWidget(0, 1, controlPointsPanel); + mainPanel.setWidget(0, 2, marksPanel); + + waypointsBtnsPanel = new HorizontalPanel(); + controlPointsBtnsPanel = new HorizontalPanel(); + marksBtnsPanel = new HorizontalPanel(); + mainPanel.setWidget(1, 0, waypointsBtnsPanel); + mainPanel.setWidget(1, 1, controlPointsBtnsPanel); + mainPanel.setWidget(1, 2, marksBtnsPanel); + + ImagesBarColumn waypointsActionColumn = + new ImagesBarColumn( + new CourseManagementWidgetWaypointsImagesBarCell(stringMessages)); + waypointsActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, WaypointDTO waypoint, String value) { + if (CourseManagementWidgetWaypointsImagesBarCell.ACTION_DELETE.equals(value)) { + removeWaypoint(waypoint); + } + } + }); + waypoints.getTable().addColumn(waypointsActionColumn); + + waypoints.getSelectionModel().addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + WaypointDTO waypoint = waypoints.getSelectionModel().getSelectedObject(); + if (waypoint != null) { + selectControlPoints(waypoint); + selectMarks(waypoint.controlPoint.getMarks()); + } + updateWaypointButtons(); + } + }); + + multiMarkControlPoints.getSelectionModel().addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + if (multiMarkControlPoints.getSelectionModel().getSelectedSet().size() > 0) { + ControlPointDTO first = multiMarkControlPoints.getSelectionModel().getSelectedSet().iterator().next(); + selectMarks(first.getMarks()); + } + } + }); + + insertWaypointBefore = new Button(stringMessages.insertWaypointBeforeSelected()); + insertWaypointBefore.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + insertWaypoint(/* before */ true); + } + }); + insertWaypointBefore.setEnabled(false); + waypointsBtnsPanel.add(insertWaypointBefore); + insertWaypointAfter = new Button(stringMessages.insertWaypointAfterSelected()); + insertWaypointAfter.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + insertWaypoint(/* before */ false); + } + }); + insertWaypointAfter.setEnabled(false); + waypointsBtnsPanel.add(insertWaypointAfter); + + final Button addControlPoint = new Button(stringMessages.add(stringMessages.twoMarkControlPoint())); + addControlPoint.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + addMultiMarkControlPoint(); + } + }); + controlPointsBtnsPanel.add(addControlPoint); + + buttonsPanel = new HorizontalPanel(); + mainPanel.setWidget(2, 2, buttonsPanel); + Button refreshBtn = new Button(stringMessages.refresh()); + refreshBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + refresh(); + } + }); + buttonsPanel.add(refreshBtn); + Button saveBtn = new Button(stringMessages.save()); + saveBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + save(); + } + }); + buttonsPanel.add(saveBtn); + } + + private void removeWaypoint(WaypointDTO waypoint) { + waypoints.getDataProvider().getList().remove(waypoint); + } + + private void selectControlPoints(WaypointDTO waypoint) { + for (ControlPointDTO controlPoint : multiMarkControlPoints.getDataProvider().getList()) { + multiMarkControlPoints.getSelectionModel().setSelected( + controlPoint, waypoint.controlPoint == controlPoint); + } + } + + private void selectMarks(Iterable newMarks) { + marks.getSelectionModel().clear(); + for (MarkDTO toSelect : newMarks) { + marks.getSelectionModel().setSelected(toSelect, true); + } + } + + protected abstract void save(); + + private T getFirstSelected(SetSelectionModel selectionModel) { + if (selectionModel.getSelectedSet().isEmpty()) { + return null; + } + return selectionModel.getSelectedSet().iterator().next(); + } + + private void insert(TableWrapper> tableWrapper, T toInsert, boolean beforeSelection) { + T selected = getFirstSelected(tableWrapper.getSelectionModel()); + int index = -1; + if (tableWrapper.getDataProvider().getList().isEmpty()) { + index = 0; + } else if (selected != null) { + index = tableWrapper.getDataProvider().getList().indexOf(selected) + (beforeSelection?0:1); + } + if (index != -1) { + tableWrapper.getDataProvider().getList().add(index, toInsert); + } + } + + private void addMultiMarkControlPoint() { + new GateCreationDialog(sailingService, errorReporter, stringMessages, tableRes, + marks.getDataProvider().getList(), new DataEntryDialog.DialogCallback() { + @Override + public void cancel() {} + + @Override + public void ok(GateDTO result) { + multiMarkControlPoints.dataProvider.getList().add(result); + } + }).show(); + } + + private void insertWaypoint(final boolean beforeSelection) { + List allControlPoints = new ArrayList<>(); + allControlPoints.addAll(multiMarkControlPoints.getDataProvider().getList()); + allControlPoints.addAll(marks.getDataProvider().getList()); + new WaypointCreationDialog(sailingService, errorReporter, stringMessages, tableRes, + allControlPoints, new DataEntryDialog.DialogCallback() { + @Override + public void cancel() {} + + @Override + public void ok(WaypointDTO result) { + insert(waypoints, result, beforeSelection); + } + }).show(); + } + + public abstract void refresh(); + + protected void updateWaypointsAndControlPoints(RaceCourseDTO raceCourseDTO) { + waypoints.getDataProvider().getList().clear(); + multiMarkControlPoints.getDataProvider().getList().clear(); + waypoints.getDataProvider().getList().addAll(raceCourseDTO.waypoints); + + Map noDuplicateCPs = new HashMap<>(); + for (ControlPointDTO controlPoint : raceCourseDTO.getControlPoints()) { + if (controlPoint instanceof GateDTO) { + noDuplicateCPs.put(controlPoint.getIdAsString(), controlPoint); + } + } + multiMarkControlPoints.getDataProvider().getList().addAll(noDuplicateCPs.values()); + + updateWaypointButtons(); + } + + protected List> createWaypointPairs() { + List> result = new ArrayList<>(); + for (WaypointDTO waypoint : waypoints.getDataProvider().getList()) { + result.add(new com.sap.sse.common.Util.Pair<>(waypoint.controlPoint, waypoint.passingInstructions)); + } + return result; + } + + protected void updateWaypointButtons() { + if (waypoints.getDataProvider().getList().isEmpty() || + waypoints.getSelectionModel().getSelectedObject() != null) { + insertWaypointAfter.setEnabled(true); + insertWaypointBefore.setEnabled(true); + } else { + insertWaypointBefore.setEnabled(false); + insertWaypointAfter.setEnabled(false); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java new file mode 100644 index 00000000000..6f7e9c87be9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; + +import com.google.gwt.core.client.GWT; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; + +public class CourseManagementWidgetWaypointsImagesBarCell extends ImagesBarCell { + public static final String ACTION_DELETE = "ACTION_DELETE"; + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + private final StringMessages stringMessages; + + public CourseManagementWidgetWaypointsImagesBarCell(StringMessages stringMessages) { + super(); + this.stringMessages = stringMessages; + } + + @Override + protected Iterable getImageSpecs() { + ArrayList result = new ArrayList(); + result.add(new ImageSpec(ACTION_DELETE, stringMessages.remove(), makeImagePrototype(resources.removeIcon()))); + return result; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationDetailComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationDetailComposite.java index 918031ef0b6..d7f469c2e82 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationDetailComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationDetailComposite.java @@ -39,8 +39,6 @@ public class DeviceConfigurationDetailComposite extends Composite { void onCloneRequested(DeviceConfigurationMatcherDTO matcher, DeviceConfigurationDTO configuration); } - private static List suggestedCourseAreaNames = Arrays.asList("Alpha", "Bravo", "Charly", "Delta", - "Foxtrott", "Stadium", "Offshore"); private static List suggestedCourseNames = Arrays.asList("Upwind", "Downwind"); private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); @@ -147,7 +145,7 @@ public class DeviceConfigurationDetailComposite extends Composite { overwriteRegattaConfigurationBox.addValueChangeHandler(new ValueChangeHandler() { @Override public void onValueChange(ValueChangeEvent event) { - markAsDrity(true); + markAsDirty(true); editButton.setEnabled(event.getValue()); } }); @@ -161,7 +159,7 @@ public class DeviceConfigurationDetailComposite extends Composite { @Override public void ok(RegattaConfigurationDTO newConfiguration) { currentRegattaConfiguration = newConfiguration; - markAsDrity(true); + markAsDirty(true); } @Override @@ -208,7 +206,8 @@ public class DeviceConfigurationDetailComposite extends Composite { List initialValues = originalConfiguration.allowedCourseAreaNames == null ? Collections . emptyList() : originalConfiguration.allowedCourseAreaNames; - allowedCourseAreasList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseAreas(), resources.removeIcon(), suggestedCourseAreaNames); + allowedCourseAreasList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseAreas(), resources.removeIcon(), + SuggestedCourseAreaNames.suggestedCourseAreaNames); allowedCourseAreasList.setWidth("80%"); allowedCourseAreasList.addValueChangeHandler(dirtyValueMarker); @@ -244,7 +243,7 @@ public class DeviceConfigurationDetailComposite extends Composite { contentPanel.clear(); } - private void markAsDrity(boolean dirty) { + private void markAsDirty(boolean dirty) { if (dirty && !(captionPanel.getTitle() == stringMessages.configuration())) { captionPanel.setCaptionText(stringMessages.configuration() + "* (CHANGED)"); } else { @@ -284,7 +283,7 @@ public class DeviceConfigurationDetailComposite extends Composite { new AsyncCallback() { @Override public void onSuccess(DeviceConfigurationMatcherDTO matcher) { - markAsDrity(false); + markAsDirty(false); } @Override @@ -297,14 +296,14 @@ public class DeviceConfigurationDetailComposite extends Composite { private KeyUpHandler dirtyMarker = new KeyUpHandler() { @Override public void onKeyUp(KeyUpEvent event) { - markAsDrity(true); + markAsDirty(true); } }; private ValueChangeHandler> dirtyValueMarker = new ValueChangeHandler>() { @Override public void onValueChange(ValueChangeEvent> event) { - markAsDrity(true); + markAsDirty(true); } }; 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 new file mode 100644 index 00000000000..375b4482043 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceIdentifierTableWrapper.java @@ -0,0 +1,34 @@ +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.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.DeviceIdentifierDTO; + +public class DeviceIdentifierTableWrapper extends TableWrapper> { + + public DeviceIdentifierTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, + ErrorReporter errorReporter) { + super(sailingService, stringMessages, errorReporter, new MultiSelectionModel()); + + TextColumn typeColumn = new TextColumn() { + @Override + public String getValue(DeviceIdentifierDTO object) { + return object.deviceType; + } + }; + + TextColumn idColumn = new TextColumn() { + @Override + public String getValue(DeviceIdentifierDTO object) { + return object.deviceId; + } + }; + + table.addColumn(typeColumn, stringMessages.deviceType()); + table.addColumn(idColumn, stringMessages.deviceId()); + } + +} 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 new file mode 100644 index 00000000000..cf4a8165876 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingTableWrapper.java @@ -0,0 +1,128 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Comparator; +import java.util.List; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +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; + +public class DeviceMappingTableWrapper extends TableWrapper> { + + public DeviceMappingTableWrapper(SailingServiceAsync sailingService, final StringMessages stringMessages, + ErrorReporter errorReporter) { + super(sailingService, stringMessages, errorReporter, new SingleSelectionModel()); + + table.setWidth("1000px", true); + table.addStyleName("wrap-cols"); + + ListHandler listHandler = new ListHandler(dataProvider.getList()); + + TextColumn itemTypeCol = new TextColumn() { + @Override + public String getValue(DeviceMappingDTO mapping) { + if (mapping.mappedTo instanceof CompetitorDTO) return stringMessages.competitor(); + else return stringMessages.mark(); + } + }; + itemTypeCol.setSortable(true); + listHandler.setComparator(itemTypeCol, new Comparator() { + @Override + public int compare(DeviceMappingDTO o1, DeviceMappingDTO o2) { + return o1.mappedTo.getClass().getName().compareTo(o2.mappedTo.getClass().getName()); + } + }); + table.addColumn(itemTypeCol, stringMessages.mappedToType()); + + TextColumn itemCol = new TextColumn() { + @Override + public String getValue(DeviceMappingDTO mapping) { + return mapping.mappedTo.toString(); + } + }; + itemCol.setSortable(true); + listHandler.setComparator(itemCol, new Comparator() { + @Override + public int compare(DeviceMappingDTO o1, DeviceMappingDTO o2) { + return o1.mappedTo.toString().compareTo(o2.mappedTo.toString()); + } + }); + table.addColumn(itemCol, stringMessages.mappedTo()); + + TextColumn deviceTypeCol = new TextColumn() { + @Override + public String getValue(DeviceMappingDTO mapping) { + return mapping.deviceIdentifier.deviceType; + } + }; + deviceTypeCol.setSortable(true); + listHandler.setComparator(deviceTypeCol, new Comparator() { + @Override + public int compare(DeviceMappingDTO o1, DeviceMappingDTO o2) { + return o1.deviceIdentifier.deviceType.compareTo(o2.deviceIdentifier.deviceType); + } + }); + table.addColumn(deviceTypeCol, stringMessages.deviceType()); + + TextColumn deviceIdCol = new TextColumn() { + @Override + public String getValue(DeviceMappingDTO mapping) { + return mapping.deviceIdentifier.deviceId; + } + }; + deviceIdCol.setSortable(true); + listHandler.setComparator(deviceIdCol, new Comparator() { + @Override + public int compare(DeviceMappingDTO o1, DeviceMappingDTO o2) { + return o1.deviceIdentifier.deviceId.compareTo(o2.deviceIdentifier.deviceId); + } + }); + table.addColumn(deviceIdCol, stringMessages.deviceId()); + table.setColumnWidth(deviceIdCol, 400, Unit.PX); + + TextColumn fromCol = new TextColumn() { + @Override + public String getValue(DeviceMappingDTO mapping) { + return DateAndTimeFormatterUtil.formatDateAndTime(mapping.from); + } + }; + fromCol.setSortable(true); + listHandler.setComparator(fromCol, new Comparator() { + @Override + public int compare(DeviceMappingDTO o1, DeviceMappingDTO o2) { + return o1.from.compareTo(o2.from); + } + }); + table.addColumn(fromCol, stringMessages.from()); + + TextColumn toCol = new TextColumn() { + @Override + public String getValue(DeviceMappingDTO mapping) { + return DateAndTimeFormatterUtil.formatDateAndTime(mapping.to); + } + }; + toCol.setSortable(true); + listHandler.setComparator(toCol, new Comparator() { + @Override + public int compare(DeviceMappingDTO o1, DeviceMappingDTO o2) { + return o1.to.compareTo(o2.to); + } + }); + table.addColumn(toCol, stringMessages.to()); + + table.addColumnSortHandler(listHandler); + mainPanel.add(table); + } + + public void refresh(List mappings) { + dataProvider.getList().clear(); + dataProvider.getList().addAll(mappings); + } +} 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 16ffbd369df..9b6640c68a2 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 @@ -1,14 +1,16 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.UUID; -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.core.client.GWT; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Label; @@ -17,30 +19,32 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.datepicker.client.DateBox; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.listedit.StringListInlineEditorComposite; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.VenueDTO; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public abstract class EventDialog extends DataEntryDialog { - protected StringMessages stringConstants; + private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + protected StringMessages stringMessages; protected TextBox nameEntryField; protected TextBox venueEntryField; protected DateBox startDateBox; protected DateBox endDateBox; protected CheckBox isPublicCheckBox; protected UUID id; - protected List courseAreaNameEntryFields; - - private Grid courseAreasGrid; + protected StringListInlineEditorComposite courseAreaNameList; + protected StringListInlineEditorComposite imageURLList; + protected StringListInlineEditorComposite videoURLList; protected static class EventParameterValidator implements Validator { - private StringMessages stringConstants; + private StringMessages stringMessages; private ArrayList existingEvents; - public EventParameterValidator(StringMessages stringConstants, Collection existingEvents) { - this.stringConstants = stringConstants; + public EventParameterValidator(StringMessages stringMessages, Collection existingEvents) { + this.stringMessages = stringMessages; this.existingEvents = new ArrayList(existingEvents); } @@ -73,22 +77,22 @@ public abstract class EventDialog extends DataEntryDialog { // remark: startDate == null and endDate == null is valid if(startDate != null && endDate != null) { if(startDate.after(endDate)) { - datesErrorMessage = stringConstants.pleaseEnterStartAndEndDate(); + datesErrorMessage = stringMessages.pleaseEnterStartAndEndDate(); } } else if((startDate != null && endDate == null) || (startDate == null && endDate != null)) { - datesErrorMessage = stringConstants.pleaseEnterStartAndEndDate(); + datesErrorMessage = stringMessages.pleaseEnterStartAndEndDate(); } if(datesErrorMessage != null) { errorMessage = datesErrorMessage; } else if (!nameNotEmpty) { - errorMessage = stringConstants.pleaseEnterAName(); + errorMessage = stringMessages.pleaseEnterAName(); } else if (!venueNotEmpty) { - errorMessage = stringConstants.pleaseEnterNonEmptyVenue(); + errorMessage = stringMessages.pleaseEnterNonEmptyVenue(); } else if (!courseAreaNotEmpty) { - errorMessage = stringConstants.pleaseEnterNonEmptyCourseArea(); + errorMessage = stringMessages.pleaseEnterNonEmptyCourseArea(); } else if (!unique) { - errorMessage = stringConstants.eventWithThisNameAlreadyExists(); + errorMessage = stringMessages.eventWithThisNameAlreadyExists(); } return errorMessage; @@ -96,26 +100,32 @@ public abstract class EventDialog extends DataEntryDialog { } - public EventDialog(EventParameterValidator validator, StringMessages stringConstants, + public EventDialog(EventParameterValidator validator, StringMessages stringMessages, DialogCallback callback) { - super(stringConstants.event(), null, stringConstants.ok(), stringConstants.cancel(), validator, + super(stringMessages.event(), null, stringMessages.ok(), stringMessages.cancel(), validator, callback); - this.stringConstants = stringConstants; - courseAreaNameEntryFields = new ArrayList(); - courseAreasGrid = new Grid(0, 0); - } - - protected void addCourseAreaWidget(String courseAreaName, boolean isEnabled) { - createCourseAreaNameWidget(courseAreaName, isEnabled); - } - - private Widget createCourseAreaNameWidget(String defaultName, boolean isEnabled) { - TextBox textBox = createTextBox(defaultName); - textBox.setVisibleLength(40); - textBox.setEnabled(isEnabled); - textBox.setWidth("175px"); - courseAreaNameEntryFields.add(textBox); - return textBox; + this.stringMessages = stringMessages; + final ValueChangeHandler> valueChangeHandler = new ValueChangeHandler>() { + @Override + public void onValueChange(ValueChangeEvent> event) { + validate(); + } + }; + courseAreaNameList = new StringListInlineEditorComposite(Collections. emptyList(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), /* suggestValues */ + SuggestedCourseAreaNames.suggestedCourseAreaNames)); + courseAreaNameList.addValueChangeHandler(valueChangeHandler); + final List imageSuggestionURLs = Arrays.asList(new String[] { "http://", "https://", "http://www.", "https://www" }); + imageURLList = new StringListInlineEditorComposite(Collections. emptyList(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + /* suggestValues */ imageSuggestionURLs)); + imageURLList.addValueChangeHandler(valueChangeHandler); + List videoURLSuggestions = new ArrayList<>(imageSuggestionURLs); + videoURLSuggestions.add("http://www.youtube.com/watch?v="); + videoURLList = new StringListInlineEditorComposite(Collections. emptyList(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + /* suggestValues */ videoURLSuggestions)); + videoURLList.addValueChangeHandler(valueChangeHandler); } @Override @@ -129,13 +139,17 @@ public abstract class EventDialog extends DataEntryDialog { result.id = id; List courseAreas = new ArrayList(); - int count = courseAreaNameEntryFields.size(); - for(int i = 0; i < count; i++) { + for (String courseAreaName : courseAreaNameList.getValue()) { CourseAreaDTO courseAreaDTO = new CourseAreaDTO(); - courseAreaDTO.setName(courseAreaNameEntryFields.get(i).getValue()); + courseAreaDTO.setName(courseAreaName); courseAreas.add(courseAreaDTO); } - + for (String imageURL : imageURLList.getValue()) { + result.addImageURL(imageURL); + } + for (String videoURL : videoURLList.getValue()) { + result.addVideoURL(videoURL); + } result.venue = new VenueDTO(venueEntryField.getText(), courseAreas); return result; } @@ -151,47 +165,26 @@ public abstract class EventDialog extends DataEntryDialog { Grid formGrid = new Grid(5, 2); panel.add(formGrid); - formGrid.setWidget(0, 0, new Label(stringConstants.name() + ":")); + formGrid.setWidget(0, 0, new Label(stringMessages.name() + ":")); formGrid.setWidget(0, 1, nameEntryField); - formGrid.setWidget(1, 0, new Label(stringConstants.venue() + ":")); + formGrid.setWidget(1, 0, new Label(stringMessages.venue() + ":")); formGrid.setWidget(1, 1, venueEntryField); - formGrid.setWidget(2, 0, new Label(stringConstants.startDate() + ":")); + formGrid.setWidget(2, 0, new Label(stringMessages.startDate() + ":")); formGrid.setWidget(2, 1, startDateBox); - formGrid.setWidget(3, 0, new Label(stringConstants.endDate() + ":")); + formGrid.setWidget(3, 0, new Label(stringMessages.endDate() + ":")); formGrid.setWidget(3, 1, endDateBox); - formGrid.setWidget(4, 0, new Label(stringConstants.isPublic() + ":")); + formGrid.setWidget(4, 0, new Label(stringMessages.isPublic() + ":")); formGrid.setWidget(4, 1, isPublicCheckBox); - panel.add(createHeadlineLabel(stringConstants.courseAreas())); - panel.add(courseAreasGrid); - updateCourseAreasGrid(panel); - - Button addCourseAreaButton = new Button(stringConstants.addCourseArea()); - addCourseAreaButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - addCourseAreaWidget("", true); - updateCourseAreasGrid(panel); - courseAreaNameEntryFields.get(courseAreaNameEntryFields.size()-1).setFocus(true); - } - }); - panel.add(addCourseAreaButton); + panel.add(createHeadlineLabel(stringMessages.courseAreas())); + panel.add(courseAreaNameList); + panel.add(createHeadlineLabel(stringMessages.imageURLs())); + panel.add(imageURLList); + panel.add(createHeadlineLabel(stringMessages.videoURLs())); + panel.add(videoURLList); return panel; } - protected void updateCourseAreasGrid(VerticalPanel parentPanel) { - int widgetIndex = parentPanel.getWidgetIndex(courseAreasGrid); - parentPanel.remove(courseAreasGrid); - int courseAreasCount = courseAreaNameEntryFields.size(); - courseAreasGrid = new Grid(courseAreasCount + 1, 1); - courseAreasGrid.setCellSpacing(4); - courseAreasGrid.setHTML(0, 0, stringConstants.name()); - for(int i = 0; i < courseAreasCount; i++) { - courseAreasGrid.setWidget(i+1, 0, courseAreaNameEntryFields.get(i)); - } - parentPanel.insert(courseAreasGrid, widgetIndex); - } - @Override public void show() { super.show(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java index d93d9fa2865..9a19cf2026c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java @@ -1,6 +1,8 @@ package com.sap.sailing.gwt.ui.adminconsole; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; @@ -8,6 +10,7 @@ import com.google.gwt.user.datepicker.client.DateBox; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.common.Util; public class EventEditDialog extends EventDialog { @@ -26,11 +29,18 @@ public class EventEditDialog extends EventDialog { isPublicCheckBox.setValue(event.isPublic); id = event.id; + List courseAreaNames = new ArrayList<>(); if (event.venue.getCourseAreas() != null && event.venue.getCourseAreas().size() > 0) { for (CourseAreaDTO courseArea : event.venue.getCourseAreas()) { - addCourseAreaWidget(courseArea.getName(), false); + courseAreaNames.add(courseArea.getName()); } - } + courseAreaNameList.setValue(courseAreaNames); + List imageURLStringsAsList = new ArrayList<>(); + Util.addAll(event.getImageURLs(), imageURLStringsAsList); + imageURLList.setValue(imageURLStringsAsList); + List videoURLStringsAsList = new ArrayList<>(); + Util.addAll(event.getVideoURLs(), videoURLStringsAsList); + videoURLList.setValue(videoURLStringsAsList); } } 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 new file mode 100644 index 00000000000..d568dbed898 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java @@ -0,0 +1,887 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import com.google.gwt.cell.client.AbstractCell; +import com.google.gwt.cell.client.FieldUpdater; +import com.google.gwt.cell.client.SafeHtmlCell; +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.client.SafeHtmlTemplates; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +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; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CaptionPanel; +import com.google.gwt.user.client.ui.HorizontalPanel; +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.user.client.ui.Widget; +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.impl.NaturalComparator; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +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.URLEncoder; +import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; +import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.common.Util; +import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; + +/** + * Allows administrators to manage data of a sailing event. This is a temporary panel because the managed event is not + * connected with any regatta and it used only to manage of basic attributes of the an event. + * + * @author Frank Mittag (C5163974) + * @author Axel Uhl (d043530) + */ +public class EventManagementPanel extends SimplePanel implements EventsRefresher, LeaderboardGroupsDisplayer { + private final SailingServiceAsync sailingService; + private final ErrorReporter errorReporter; + private final StringMessages stringMessages; + + private CellTable eventTable; + private MultiSelectionModel eventSelectionModel; + private ListDataProvider eventProvider; + private List allEvents; + private LabeledAbstractFilterablePanel filterTextbox; + private Button removeEventsButton; + private TextColumn startEndDateColumn; + + private CellTable selectedEventLeaderboardGroupsTable; + private MultiSelectionModel selectedEventLeaderboardGroupsSelectionModel; + private ListDataProvider selectedEventLeaderboardGroupsProvider; + + private LabeledAbstractFilterablePanel availableLeaderboardGroupsFilterablePanel; + private CellTable availableLeaderboardGroupsTable; + private MultiSelectionModel availableLeaderboardGroupsSelectionModel; + private ListDataProvider availableLeaderboardGroupsProvider; + + public static class AnchorCell extends AbstractCell { + @Override + public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) { + sb.append(safeHtml); + } + } + + interface AnchorTemplates extends SafeHtmlTemplates { + @SafeHtmlTemplates.Template("{1}") + SafeHtml cell(String url, String displayName); + } + + private static AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); + + private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); + private final HorizontalPanel splitPanel; + private Button addToEventButton; + private Button removeFromEventButton; + private Iterable availableLeaderboardGroups; + + public EventManagementPanel(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, + final StringMessages stringMessages) { + availableLeaderboardGroups = Collections.emptySet(); + this.sailingService = sailingService; + this.stringMessages = stringMessages; + this.errorReporter = errorReporter; + + VerticalPanel mainPanel = new VerticalPanel(); + this.setWidget(mainPanel); + mainPanel.setWidth("100%"); + + HorizontalPanel eventsPanel = new HorizontalPanel(); + eventsPanel.setSpacing(5); + mainPanel.add(eventsPanel); + + Button refreshButton = new Button(stringMessages.refresh()); + refreshButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + fillEvents(); + } + }); + eventsPanel.add(refreshButton); + + Button createEventBtn = new Button(stringMessages.actionAddEvent()); + createEventBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + openCreateEventDialog(); + } + }); + eventsPanel.add(createEventBtn); + + removeEventsButton = new Button(stringMessages.remove()); + removeEventsButton.setEnabled(false); + removeEventsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (Window.confirm(stringMessages.doYouReallyWantToRemoveEvents())) { + removeEvents(eventSelectionModel.getSelectedSet()); + } + } + }); + eventsPanel.add(removeEventsButton); + + // sailing events table + AnchorCell anchorCell = new AnchorCell(); + Column eventNameColumn = new Column(anchorCell) { + @Override + public SafeHtml getValue(EventDTO event) { + String debugParam = Window.Location.getParameter("gwt.codesvr"); + String link = URLEncoder.encode("/gwt/RegattaOverview.html?event=" + event.id + + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); + return ANCHORTEMPLATE.cell(link, event.getName()); + } + }; + + TextColumn venueNameColumn = new TextColumn() { + @Override + public String getValue(EventDTO event) { + return event.venue != null ? event.venue.getName() : ""; + } + }; + + startEndDateColumn = new TextColumn() { + @Override + public String getValue(EventDTO event) { + return DateAndTimeFormatterUtil.formatDateRange(event.startDate, event.endDate); + } + }; + + TextColumn isPublicColumn = new TextColumn() { + @Override + public String getValue(EventDTO event) { + return event.isPublic ? stringMessages.yes() : stringMessages.no(); + } + }; + + final SafeHtmlCell courseAreasCell = new SafeHtmlCell(); + Column courseAreasColumn = new Column(courseAreasCell) { + @Override + public SafeHtml getValue(EventDTO event) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + int courseAreasCount = event.venue.getCourseAreas().size(); + int i = 1; + for (CourseAreaDTO courseArea : event.venue.getCourseAreas()) { + builder.appendEscaped(courseArea.getName()); + if (i < courseAreasCount) { + builder.appendHtmlConstant("
"); + } + i++; + } + return builder.toSafeHtml(); + } + }; + + final SafeHtmlCell leaderboardGroupsCell = new SafeHtmlCell(); + Column leaderboardGroupsColumn = new Column(leaderboardGroupsCell) { + @Override + public SafeHtml getValue(EventDTO event) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + boolean first = true; + for (LeaderboardGroupDTO lg : event.getLeaderboardGroups()) { + if (first) { + first = false; + } else { + builder.appendHtmlConstant("
"); + } + builder.appendEscaped(lg.getName()); + } + return builder.toSafeHtml(); + } + }; + + final SafeHtmlCell imageURLsCell = new SafeHtmlCell(); + Column imageURLsColumn = new Column(imageURLsCell) { + @Override + public SafeHtml getValue(EventDTO event) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + boolean first = true; + for (String imageURL : event.getImageURLs()) { + if (first) { + first = false; + } else { + builder.appendHtmlConstant("
"); + } + builder.appendEscaped(imageURL); + } + return builder.toSafeHtml(); + } + }; + + final SafeHtmlCell videoURLsCell = new SafeHtmlCell(); + Column videoURLsColumn = new Column(videoURLsCell) { + @Override + public SafeHtml getValue(EventDTO event) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + boolean first = true; + for (String videoURL : event.getVideoURLs()) { + if (first) { + first = false; + } else { + builder.appendHtmlConstant("
"); + } + builder.appendEscaped(videoURL); + } + return builder.toSafeHtml(); + } + }; + + final SafeHtmlCell associatedRegattasCell = new SafeHtmlCell(); + Column associatedRegattasColumn = new Column(associatedRegattasCell) { + @Override + public SafeHtml getValue(EventDTO event) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + int regattaCount = event.regattas.size(); + int i = 1; + for (RegattaDTO regatta : event.regattas) { + builder.appendEscaped(regatta.getName()); + if (i < regattaCount) { + builder.appendHtmlConstant("
"); + } + i++; + } + return builder.toSafeHtml(); + } + }; + + ImagesBarColumn eventActionColumn = new ImagesBarColumn( + new EventConfigImagesBarCell(stringMessages)); + eventActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, EventDTO event, String value) { + if (EventConfigImagesBarCell.ACTION_REMOVE.equals(value)) { + if (Window.confirm(stringMessages.doYouReallyWantToRemoveEvent(event.getName()))) { + removeEvent(event); + } + } else if (EventConfigImagesBarCell.ACTION_EDIT.equals(value)) { + openEditEventDialog(event); + } + } + }); + + eventNameColumn.setSortable(true); + venueNameColumn.setSortable(true); + isPublicColumn.setSortable(true); + startEndDateColumn.setSortable(true); + courseAreasColumn.setSortable(true); + imageURLsColumn.setSortable(true); + videoURLsColumn.setSortable(true); + leaderboardGroupsColumn.setSortable(true); + + eventTable = new CellTable(10000, tableRes); + eventTable.addColumn(eventNameColumn, stringMessages.event()); + eventTable.addColumn(venueNameColumn, stringMessages.venue()); + eventTable.addColumn(startEndDateColumn, stringMessages.from() + "/" + stringMessages.to()); + eventTable.addColumn(isPublicColumn, stringMessages.isPublic()); + eventTable.addColumn(courseAreasColumn, stringMessages.courseAreas()); + eventTable.addColumn(leaderboardGroupsColumn, stringMessages.leaderboardGroups()); + eventTable.addColumn(imageURLsColumn, stringMessages.imageURLs()); + eventTable.addColumn(videoURLsColumn, stringMessages.videoURLs()); + eventTable.addColumn(associatedRegattasColumn, stringMessages.regattas()); + eventTable.addColumn(eventActionColumn, stringMessages.actions()); + + eventSelectionModel = new MultiSelectionModel(); + eventSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + final boolean somethingSelected = !eventSelectionModel.getSelectedSet().isEmpty(); + removeEventsButton.setEnabled(somethingSelected); + updateLeaderboardGroupAssignmentPanel(eventSelectionModel.getSelectedSet()); + } + }); + eventTable.setSelectionModel(eventSelectionModel); + + eventProvider = new ListDataProvider(); + eventProvider.addDataDisplay(eventTable); + mainPanel.add(eventTable); + allEvents = new ArrayList(); + eventTable.addColumnSortHandler(getEventTableColumnSortHandler(eventProvider.getList(), eventNameColumn, + venueNameColumn, startEndDateColumn, isPublicColumn, courseAreasColumn, imageURLsColumn, videoURLsColumn)); + eventTable.getColumnSortList().push(startEndDateColumn); + filterTextbox = new LabeledAbstractFilterablePanel(new Label(stringMessages.filterEventsByName()), allEvents, + eventTable, eventProvider) { + @Override + public Iterable getSearchableStrings(EventDTO t) { + List result = new ArrayList(); + result.add(t.getName()); + result.add(t.venue.getName()); + for (CourseAreaDTO c : t.venue.getCourseAreas()) { + result.add(c.getName()); + } + for (LeaderboardGroupDTO lg : t.getLeaderboardGroups()) { + result.add(lg.getName()); + } + return result; + } + }; + eventsPanel.add(filterTextbox); + fillEvents(); + + // prepare the caption panels and buttons for leaderboard group assignment but leave invisible for now + splitPanel = new HorizontalPanel(); + splitPanel.ensureDebugId("EventLeaderboardGroupsAssignmentDetailsPanel"); + splitPanel.setSpacing(5); + splitPanel.setWidth("100%"); + splitPanel.setVisible(false); + mainPanel.add(splitPanel); + + splitPanel.setVerticalAlignment(HorizontalPanel.ALIGN_TOP); + final Widget switchingButtonsPanel = createSwitchLeaderboardGroupsGUI(); // initialize this before the two tables because their event + // handlers require the buttons to be assigned + splitPanel.add(createLeaderboardGroupOfEventsPanel()); + splitPanel.add(switchingButtonsPanel); + splitPanel.add(createAvailableLeaderboardGroupsPanel()); + } + + private Widget createLeaderboardGroupOfEventsPanel() { + selectedEventLeaderboardGroupsTable = new CellTable(10000000, tableRes); + CaptionPanel result = new CaptionPanel(stringMessages.leaderboardGroupsOfSelectedEvent()); + result.add(selectedEventLeaderboardGroupsTable); + selectedEventLeaderboardGroupsSelectionModel = new MultiSelectionModel<>(); + selectedEventLeaderboardGroupsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + removeFromEventButton.setEnabled(!selectedEventLeaderboardGroupsSelectionModel.getSelectedSet().isEmpty()); + } + }); + selectedEventLeaderboardGroupsTable.setSelectionModel(selectedEventLeaderboardGroupsSelectionModel); + selectedEventLeaderboardGroupsProvider = new ListDataProvider<>(); + selectedEventLeaderboardGroupsProvider.addDataDisplay(selectedEventLeaderboardGroupsTable); + + TextColumn leaderboardGroupNameColumn = new TextColumn() { + @Override + public String getValue(LeaderboardGroupDTO event) { + return event.getName(); + } + }; + TextColumn leaderboardGroupDescriptionColumn = new TextColumn() { + @Override + public String getValue(LeaderboardGroupDTO event) { + return event.description; + } + }; + final SafeHtmlCell leaderboardsCell = new SafeHtmlCell(); + Column associatedLeaderboardsColumn = new Column(leaderboardsCell) { + @Override + public SafeHtml getValue(LeaderboardGroupDTO leaderboardGroup) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + boolean first = true; + for (StrippedLeaderboardDTO leaderboard : leaderboardGroup.getLeaderboards()) { + if (first) { + first = false; + } else { + builder.appendHtmlConstant("
"); + } + builder.appendEscaped(leaderboard.name); + } + return builder.toSafeHtml(); + } + }; + + leaderboardGroupNameColumn.setSortable(true); + leaderboardGroupDescriptionColumn.setSortable(true); + selectedEventLeaderboardGroupsTable.addColumn(leaderboardGroupNameColumn, stringMessages.name()); + selectedEventLeaderboardGroupsTable.addColumn(leaderboardGroupDescriptionColumn, stringMessages.description()); + selectedEventLeaderboardGroupsTable.addColumn(associatedLeaderboardsColumn, stringMessages.leaderboards()); + ListHandler sortHandler = new ListHandler(selectedEventLeaderboardGroupsProvider.getList()); + sortHandler.setComparator(leaderboardGroupNameColumn, new Comparator() { + @Override + public int compare(LeaderboardGroupDTO e1, LeaderboardGroupDTO e2) { + return new NaturalComparator().compare(e1.getName(), e2.getName()); + } + }); + sortHandler.setComparator(leaderboardGroupDescriptionColumn, new Comparator() { + @Override + public int compare(LeaderboardGroupDTO e1, LeaderboardGroupDTO e2) { + return new NaturalComparator().compare(e1.description, e2.description); + } + }); + selectedEventLeaderboardGroupsTable.addColumnSortHandler(sortHandler); + return result; + } + + private Widget createAvailableLeaderboardGroupsPanel() { + availableLeaderboardGroupsTable = new CellTable(10000000, tableRes); + CaptionPanel result = new CaptionPanel(stringMessages.availableLeaderboardGroups()); + availableLeaderboardGroupsSelectionModel = new MultiSelectionModel<>(); + availableLeaderboardGroupsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + addToEventButton.setEnabled(!availableLeaderboardGroupsSelectionModel.getSelectedSet().isEmpty()); + } + }); + availableLeaderboardGroupsTable.setSelectionModel(availableLeaderboardGroupsSelectionModel); + availableLeaderboardGroupsProvider = new ListDataProvider<>(); + availableLeaderboardGroupsProvider.addDataDisplay(availableLeaderboardGroupsTable); + availableLeaderboardGroupsFilterablePanel = new LabeledAbstractFilterablePanel(new Label( + stringMessages.filterLeaderboardGroupsByName()), Collections. emptyList(), + availableLeaderboardGroupsTable, availableLeaderboardGroupsProvider) { + @Override + public Iterable getSearchableStrings(LeaderboardGroupDTO t) { + List result = new ArrayList<>(); + result.add(t.getName()); + result.add(t.description); + return result; + } + }; + VerticalPanel vp = new VerticalPanel(); + vp.add(availableLeaderboardGroupsFilterablePanel); + vp.add(availableLeaderboardGroupsTable); + result.add(vp); + + TextColumn leaderboardGroupNameColumn = new TextColumn() { + @Override + public String getValue(LeaderboardGroupDTO event) { + return event.getName(); + } + }; + TextColumn leaderboardGroupDescriptionColumn = new TextColumn() { + @Override + public String getValue(LeaderboardGroupDTO event) { + return event.description; + } + }; + final SafeHtmlCell leaderboardsCell = new SafeHtmlCell(); + Column associatedLeaderboardsColumn = new Column(leaderboardsCell) { + @Override + public SafeHtml getValue(LeaderboardGroupDTO leaderboardGroup) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + boolean first = true; + for (StrippedLeaderboardDTO leaderboard : leaderboardGroup.getLeaderboards()) { + if (first) { + first = false; + } else { + builder.appendHtmlConstant("
"); + } + builder.appendEscaped(leaderboard.name); + } + return builder.toSafeHtml(); + } + }; + + leaderboardGroupNameColumn.setSortable(true); + leaderboardGroupDescriptionColumn.setSortable(true); + availableLeaderboardGroupsTable.addColumn(leaderboardGroupNameColumn, stringMessages.name()); + availableLeaderboardGroupsTable.addColumn(leaderboardGroupDescriptionColumn, stringMessages.description()); + availableLeaderboardGroupsTable.addColumn(associatedLeaderboardsColumn, stringMessages.leaderboards()); + ListHandler sortHandler = new ListHandler(availableLeaderboardGroupsProvider.getList()); + sortHandler.setComparator(leaderboardGroupNameColumn, new Comparator() { + @Override + public int compare(LeaderboardGroupDTO e1, LeaderboardGroupDTO e2) { + return new NaturalComparator().compare(e1.getName(), e2.getName()); + } + }); + sortHandler.setComparator(leaderboardGroupDescriptionColumn, new Comparator() { + @Override + public int compare(LeaderboardGroupDTO e1, LeaderboardGroupDTO e2) { + return new NaturalComparator().compare(e1.description, e2.description); + } + }); + availableLeaderboardGroupsTable.addColumnSortHandler(sortHandler); + return result; + } + + private void updateLeaderboardGroupAssignmentPanel(Set set) { + if (set.isEmpty() || set.size() > 1) { + splitPanel.setVisible(false); + } else { + splitPanel.setVisible(true); + selectedEventLeaderboardGroupsProvider.getList().clear(); + final Iterable leaderboardGroupsOfSelectedEvent = set.iterator().next().getLeaderboardGroups(); + Util.addAll(leaderboardGroupsOfSelectedEvent, selectedEventLeaderboardGroupsProvider.getList()); + // deselect all items that are no longer in the list; this shall adjust the switch button enablement + for (LeaderboardGroupDTO lg : selectedEventLeaderboardGroupsSelectionModel.getSelectedSet()) { + if (!selectedEventLeaderboardGroupsProvider.getList().contains(lg)) { + selectedEventLeaderboardGroupsSelectionModel.setSelected(lg, false); + } + } + selectedEventLeaderboardGroupsProvider.refresh(); + List allButEventsLeaderboardGroups = new ArrayList<>(); + Util.addAll(availableLeaderboardGroups, allButEventsLeaderboardGroups); + Util.removeAll(leaderboardGroupsOfSelectedEvent, allButEventsLeaderboardGroups); + availableLeaderboardGroupsFilterablePanel.updateAll(allButEventsLeaderboardGroups); + // deselect all items that are no longer in the list; this shall adjust the switch button enablement + for (LeaderboardGroupDTO lg : availableLeaderboardGroupsSelectionModel.getSelectedSet()) { + if (!allButEventsLeaderboardGroups.contains(lg)) { + availableLeaderboardGroupsSelectionModel.setSelected(lg, false); + } + } + addToEventButton.setEnabled(!availableLeaderboardGroupsSelectionModel.getSelectedSet().isEmpty()); + removeFromEventButton.setEnabled(!selectedEventLeaderboardGroupsSelectionModel.getSelectedSet().isEmpty()); + } + } + + private Widget createSwitchLeaderboardGroupsGUI() { + VerticalPanel switchLeaderboardGroupsPanel = new VerticalPanel(); + switchLeaderboardGroupsPanel.setSpacing(5); + switchLeaderboardGroupsPanel.setWidth("5%"); + addToEventButton = new Button("<-"); + addToEventButton.ensureDebugId("AddLeaderboardGroupsButton"); + addToEventButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + addSelectedLeaderboardGroupsToEvent(); + } + }); + addToEventButton.setEnabled(false); + switchLeaderboardGroupsPanel.add(addToEventButton); + + removeFromEventButton = new Button("->"); + removeFromEventButton.ensureDebugId("RemoveLeaderboardGroupsButton"); + removeFromEventButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + removeSelectedLeaderboardGroupsFromEvent(); + } + }); + removeFromEventButton.setEnabled(false); + switchLeaderboardGroupsPanel.add(removeFromEventButton); + return switchLeaderboardGroupsPanel; + } + + private void addSelectedLeaderboardGroupsToEvent() { + Set leaderboardGroupsToAdd = availableLeaderboardGroupsSelectionModel.getSelectedSet(); + EventDTO selectedEvent = eventSelectionModel.getSelectedSet().iterator().next(); + List eventLeaderboardGroupUUIDs = new ArrayList<>(); + for (LeaderboardGroupDTO lg : selectedEvent.getLeaderboardGroups()) { + eventLeaderboardGroupUUIDs.add(lg.getId()); + } + for (LeaderboardGroupDTO lg : leaderboardGroupsToAdd) { + eventLeaderboardGroupUUIDs.add(lg.getId()); + } + updateEventLeaderboardGroups(selectedEvent, eventLeaderboardGroupUUIDs); + } + + private void updateEventLeaderboardGroups(final EventDTO event, List eventLeaderboardGroupUUIDs) { + sailingService.updateEvent(event.id, event.getName(), event.startDate, event.endDate, event.venue, + event.isPublic, eventLeaderboardGroupUUIDs, event.getImageURLs(), event.getVideoURLs(), + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to update event's leaderboard list: "+caught.getMessage()); + } + + @Override + public void onSuccess(EventDTO updatedEvent) { + final boolean eventWasSelected = eventSelectionModel.getSelectedSet().contains(event); + if (eventWasSelected) { + eventSelectionModel.setSelected(event, false); + } + // replace event in events table and re-calculate everything else from there + int i=0; + for (EventDTO e : allEvents) { + if (e.id.equals(updatedEvent.id)) { + allEvents.set(i, updatedEvent); + filterTextbox.updateAll(allEvents); + if (eventWasSelected) { + eventSelectionModel.setSelected(updatedEvent, true); + } + updateLeaderboardGroupAssignmentPanel(eventSelectionModel.getSelectedSet()); + break; + } + i++; + } + } + }); + } + + private void removeSelectedLeaderboardGroupsFromEvent() { + Set leaderboardGroupsToRemove = selectedEventLeaderboardGroupsSelectionModel.getSelectedSet(); + EventDTO selectedEvent = eventSelectionModel.getSelectedSet().iterator().next(); + List eventLeaderboardGroupUUIDs = new ArrayList<>(); + for (LeaderboardGroupDTO lg : selectedEvent.getLeaderboardGroups()) { + eventLeaderboardGroupUUIDs.add(lg.getId()); + } + for (LeaderboardGroupDTO lg : leaderboardGroupsToRemove) { + eventLeaderboardGroupUUIDs.remove(lg.getId()); + } + updateEventLeaderboardGroups(selectedEvent, eventLeaderboardGroupUUIDs); + } + + private ListHandler getEventTableColumnSortHandler(List eventRecords, + Column eventNameColumn, TextColumn venueNameColumn, + TextColumn startEndDateColumn, TextColumn isPublicColumn, + Column courseAreasColumn, Column imageURLsColumn, + Column videoURLsColumn) { + ListHandler result = new ListHandler(eventRecords); + result.setComparator(eventNameColumn, new Comparator() { + @Override + public int compare(EventDTO e1, EventDTO e2) { + return new NaturalComparator().compare(e1.getName(), e2.getName()); + } + }); + result.setComparator(venueNameColumn, new Comparator() { + @Override + public int compare(EventDTO e1, EventDTO e2) { + return new NaturalComparator().compare(e1.venue.getName(), e2.venue.getName()); + } + }); + result.setComparator(startEndDateColumn, new Comparator() { + @Override + public int compare(EventDTO e1, EventDTO e2) { + int result; + if(e1.startDate != null && e2.startDate != null) { + result = e2.startDate.compareTo(e1.startDate); + } else if(e1.startDate == null && e2.startDate != null) { + result = 1; + } else if(e1.startDate != null && e2.startDate == null) { + result = -1; + } else { + result = 0; + } + return result; + } + }); + result.setComparator(isPublicColumn, new Comparator() { + @Override + public int compare(EventDTO e1, EventDTO e2) { + return e1.isPublic == e2.isPublic ? 0 : e1.isPublic ? 1 : -1; + } + }); + result.setComparator(courseAreasColumn, new Comparator() { + @Override + public int compare(EventDTO e1, EventDTO e2) { + return new NaturalComparator().compare(Arrays.toString(e1.venue.getCourseAreas().toArray()), + Arrays.toString(e2.venue.getCourseAreas().toArray())); + } + }); + result.setComparator(imageURLsColumn, new Comparator() { + @Override + public int compare(EventDTO e1, EventDTO e2) { + return new NaturalComparator().compare(Arrays.toString(Util.toArray(e1.getImageURLs(), new String[0])), + Arrays.toString(Util.toArray(e2.getImageURLs(), new String[0]))); + } + }); + result.setComparator(videoURLsColumn, new Comparator() { + @Override + public int compare(EventDTO e1, EventDTO e2) { + return new NaturalComparator().compare(Arrays.toString(Util.toArray(e1.getVideoURLs(), new String[0])), + Arrays.toString(Util.toArray(e2.getVideoURLs(), new String[0]))); + } + }); + return result; + } + + private void removeEvents(Collection events) { + if (!events.isEmpty()) { + Collection eventIds = new HashSet(); + for (EventDTO event : events) { + eventIds.add(event.id); + } + sailingService.removeEvents(eventIds, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to remove the events:" + caught.getMessage()); + } + @Override + public void onSuccess(Void result) { + fillEvents(); + } + }); + } + } + + private void removeEvent(final EventDTO event) { + sailingService.removeEvent(event.id, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to remove event " + event.getName() + ": " + caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + fillEvents(); + } + }); + } + + private void openCreateEventDialog() { + List existingEvents = new ArrayList(eventProvider.getList()); + EventCreateDialog dialog = new EventCreateDialog(Collections.unmodifiableCollection(existingEvents), stringMessages, + new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(EventDTO newEvent) { + createNewEvent(newEvent); + } + }); + dialog.show(); + } + + private void openEditEventDialog(final EventDTO selectedEvent) { + List existingEvents = new ArrayList(eventProvider.getList()); + existingEvents.remove(selectedEvent); + EventEditDialog dialog = new EventEditDialog(selectedEvent, Collections.unmodifiableCollection(existingEvents), stringMessages, + new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(EventDTO updatedEvent) { + updateEvent(selectedEvent, updatedEvent); + } + }); + dialog.show(); + } + + private void updateEvent(final EventDTO oldEvent, final EventDTO updatedEvent) { + List courseAreasToAdd = getCourseAreasToAdd(oldEvent, updatedEvent); + List updatedEventLeaderboardGroupIds = new ArrayList<>(); + for (LeaderboardGroupDTO leaderboardGroup : updatedEvent.getLeaderboardGroups()) { + updatedEventLeaderboardGroupIds.add(leaderboardGroup.getId()); + } + sailingService.updateEvent(oldEvent.id, oldEvent.getName(), updatedEvent.startDate, updatedEvent.endDate, updatedEvent.venue, + updatedEvent.isPublic, updatedEventLeaderboardGroupIds, updatedEvent.getImageURLs(), updatedEvent.getVideoURLs(), + new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to update sailing event" + oldEvent.getName() + ": " + t.getMessage()); + } + + @Override + public void onSuccess(EventDTO result) { + fillEvents(); + if (!oldEvent.getName().equals(updatedEvent.getName())) { + sailingService.renameEvent(oldEvent.id, updatedEvent.getName(), new AsyncCallback() { + @Override + public void onSuccess(Void result) { + fillEvents(); + } + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to rename sailing event " + oldEvent.getName() + ": " + t.getMessage()); + } + }); + } + } + }); + + if (courseAreasToAdd.size() > 0) { + for (CourseAreaDTO courseArea : courseAreasToAdd) { + sailingService.createCourseArea(oldEvent.id, courseArea.getName(), new AsyncCallback() { + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to add course area to sailing event " + oldEvent.getName() + ": " + t.getMessage()); + } + + @Override + public void onSuccess(CourseAreaDTO result) { + fillEvents(); + } + + }); + } + } + } + + private List getCourseAreasToAdd(final EventDTO oldEvent, final EventDTO updatedEvent) { + List courseAreasToAdd = new ArrayList(); + List courseAreaNamesToAdd = new ArrayList(); + + List newCourseAreaNames = new ArrayList(); + for (CourseAreaDTO courseArea : updatedEvent.venue.getCourseAreas()) { + newCourseAreaNames.add(courseArea.getName()); + } + + List oldCourseAreaNames = new ArrayList(); + for (CourseAreaDTO courseArea : oldEvent.venue.getCourseAreas()) { + oldCourseAreaNames.add(courseArea.getName()); + } + + for (String newCourseAreaName : newCourseAreaNames) { + if (!oldCourseAreaNames.contains(newCourseAreaName)) + courseAreaNamesToAdd.add(newCourseAreaName); + } + + for (CourseAreaDTO courseArea : updatedEvent.venue.getCourseAreas()) { + if (courseAreaNamesToAdd.contains(courseArea.getName())) { + courseAreasToAdd.add(courseArea); + } + } + + return courseAreasToAdd; + } + + private void createNewEvent(final EventDTO newEvent) { + List courseAreaNames = new ArrayList(); + for (CourseAreaDTO courseAreaDTO : newEvent.venue.getCourseAreas()) { + courseAreaNames.add(courseAreaDTO.getName()); + } + sailingService.createEvent(newEvent.getName(), newEvent.startDate, newEvent.endDate, newEvent.venue.getName(), + newEvent.isPublic, courseAreaNames, newEvent.getImageURLs(), newEvent.getVideoURLs(), new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to create new event " + newEvent.getName() + ": " + t.getMessage()); + } + + @Override + public void onSuccess(EventDTO newEvent) { + fillEvents(); + } + }); + } + + public void fillEvents() { + sailingService.getEvents(new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Remote Procedure Call getEvents() - Failure: " + caught.getMessage()); + } + + @Override + public void onSuccess(List result) { + Set selectedEventUUIDs = new HashSet<>(); + for (EventDTO selectedEvent : eventSelectionModel.getSelectedSet()) { + selectedEventUUIDs.add(selectedEvent.id); + } + eventSelectionModel.clear(); + allEvents.clear(); + allEvents.addAll(result); + filterTextbox.updateAll(allEvents); + for (EventDTO e : allEvents) { + if (selectedEventUUIDs.contains(e.id)) { + eventSelectionModel.setSelected(e, true); + } + } + updateLeaderboardGroupAssignmentPanel(eventSelectionModel.getSelectedSet()); + } + }); + } + + @Override + public void fillLeaderboardGroups(Iterable leaderboardGroups) { + availableLeaderboardGroups = leaderboardGroups; + updateLeaderboardGroupAssignmentPanel(eventSelectionModel.getSelectedSet()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExportPopup.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExportPopup.java index cd6f989b9d9..918e1c629b1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExportPopup.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExportPopup.java @@ -38,7 +38,7 @@ public class ExportPopup extends DialogBox { getCaption().setText(stringMessages.export()); formPanel = new FormPanel("_blank"); - formPanel.setAction("/sailingserver/trackfiles"); + formPanel.setAction("/sailingserver/trackfiles/export"); formPanel.setMethod(FormPanel.METHOD_POST); mainPanel = new VerticalPanel(); formPanel.setWidget(mainPanel); 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 new file mode 100644 index 00000000000..21093935d9b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/GateCreationDialog.java @@ -0,0 +1,88 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.google.gwt.user.client.ui.Grid; +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.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +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.dialog.DataEntryDialog; + +public class GateCreationDialog extends DataEntryDialog { + private final MarkTableWrapper> marksWrapper; + private final TextBox name; + private final StringMessages stringMessages; + + public GateCreationDialog(SailingServiceAsync sailingService, ErrorReporter errorReporter, + final StringMessages stringMessages, AdminConsoleTableResources tableRes, + List marks, DialogCallback callback) { + super(stringMessages.gate(), stringMessages.gate(), + stringMessages.ok(), stringMessages.cancel(), new DataEntryDialog.Validator() { + @Override + public String getErrorMessage(GateDTO valueToValidate) { + if (valueToValidate == null) { + return stringMessages.selectTwoMarksForGate(); + } else if (valueToValidate.getName() == null || valueToValidate.getName().isEmpty()) { + return stringMessages.pleaseEnterAName(); + } + return null; + } + + }, /* animationEnabled */ false, callback); + + this.stringMessages = stringMessages; + + name = createTextBox(""); + + marksWrapper = new MarkTableWrapper>( + new MultiSelectionModel(), sailingService, stringMessages, errorReporter); + marksWrapper.getDataProvider().getList().addAll(marks); + + marksWrapper.getSelectionModel().addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + validate(); + } + }); + } + + @Override + protected GateDTO getResult() { + GateDTO controlPoint = null; + Set selection = marksWrapper.getSelectionModel().getSelectedSet(); + if (selection.size() == 2) { + name.setEnabled(true); + Iterator i = marksWrapper.getSelectionModel().getSelectedSet().iterator(); + MarkDTO first = i.next(); + MarkDTO second = i.next(); + controlPoint = new GateDTO(/* generate UUID on the server */ null, name.getText(), first, second); + } + + return controlPoint; + } + + @Override + protected Widget getAdditionalWidget() { + Grid grid = new Grid(2,1); + + HorizontalPanel nameRow = new HorizontalPanel(); + nameRow.add(new Label(stringMessages.name())); + nameRow.add(name); + grid.setWidget(0, 0, nameRow); + + grid.setWidget(1, 0, marksWrapper); + + return grid; + } +} \ No newline at end of file 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 02f45898584..909f3dd154f 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 @@ -35,8 +35,8 @@ import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; -import com.sap.sailing.gwt.ui.client.shared.panels.LabeledAbstractFilterablePanel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; public class IgtimiAccountsPanel extends FlowPanel { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java new file mode 100644 index 00000000000..1d6431b0992 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java @@ -0,0 +1,203 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.CaptionPanel; +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.SelectionChangeEvent; +import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +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.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.dialog.DataEntryDialog; + +public class ImportFixesAndAddMappingsDialog extends DataEntryDialog> { + TrackFileImportWidget importWidget; + private TrackFileImportDeviceIdentifierTableWrapper deviceIdTable; + private final CompetitorTableWrapper> competitorTable; + private final MarkTableWrapper> markTable; + private final StringMessages stringMessages; + + private final Map mappings = new HashMap<>(); + + private TrackFileImportDeviceIdentifierDTO deviceToSelect; + private CompetitorDTO compToSelect; + private MarkDTO markToSelect; + private boolean inInstableTransitionState = false; + + public ImportFixesAndAddMappingsDialog(SailingServiceAsync sailingService, final ErrorReporter errorReporter, + final StringMessages stringMessages, String leaderboardName, String raceColumnName, String fleetName, + DialogCallback> callback) { + super(stringMessages.add(stringMessages.deviceMappings()), stringMessages.add(stringMessages.deviceMappings()), + stringMessages.add(), stringMessages.cancel(), new DataEntryDialog.Validator>() { + @Override + public String getErrorMessage(Collection valueToValidate) { + return null; + } + }, true, callback); + + this.stringMessages = stringMessages; + + deviceIdTable = new TrackFileImportDeviceIdentifierTableWrapper(sailingService, stringMessages, errorReporter); + importWidget = new TrackFileImportWidget(deviceIdTable, stringMessages, sailingService, errorReporter); + deviceIdTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + deviceSelectionChanged(deviceIdTable.getSelectionModel().getSelectedObject()); + } + }); + + competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, + new SingleSelectionModel()); + markTable = new MarkTableWrapper>( + new SingleSelectionModel(), sailingService, stringMessages, errorReporter); + + competitorTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + mappedToSelectionChanged(competitorTable.getSelectionModel().getSelectedObject()); + } + }); + + markTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + mappedToSelectionChanged(markTable.getSelectionModel().getSelectedObject()); + } + }); + + sailingService.getCompetitorRegistrations(leaderboardName, raceColumnName, fleetName, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + competitorTable.refreshCompetitorList(result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load competitors: " + caught.getMessage()); + } + }); + + sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + markTable.refresh(result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load marks: " + caught.getMessage()); + } + }); + } + + private static void selectOrClear(SingleSelectionModel selectionModel, T object) { + if (object == null) { + selectionModel.clear(); + } else { + selectionModel.setSelected(object, true); + } + } + + /** + * Avoid programmatic deselections that re-trigger the selection listeners and lead to a loop. + */ + private void select() { + if (inInstableTransitionState) { + if (deviceIdTable.getSelectionModel().getSelectedObject() == deviceToSelect && + competitorTable.getSelectionModel().getSelectedObject() == compToSelect && + markTable.getSelectionModel().getSelectedObject() == markToSelect) { + inInstableTransitionState = false; + } + } else { + inInstableTransitionState = true; + selectOrClear(deviceIdTable.getSelectionModel(), deviceToSelect); + selectOrClear(competitorTable.getSelectionModel(), compToSelect); + selectOrClear(markTable.getSelectionModel(), markToSelect); + } + } + + private void mappedToSelectionChanged(Serializable mappedTo) { + if (! inInstableTransitionState) { + TrackFileImportDeviceIdentifierDTO device = deviceIdTable.getSelectionModel().getSelectedObject(); + if (device != null) { + mappings.put(device, mappedTo); + } + + if (mappedTo instanceof CompetitorDTO) { + markToSelect = null; + compToSelect = (CompetitorDTO) mappedTo; + } else { + compToSelect = null; + markToSelect = (MarkDTO) mappedTo; + } + } + select(); + } + + private void deviceSelectionChanged(TrackFileImportDeviceIdentifierDTO deviceId) { + if (! inInstableTransitionState) { + deviceToSelect = deviceId; + compToSelect = null; + markToSelect = null; + + if (deviceId != null) { + Serializable mappedTo = mappings.get(deviceId); + if (mappedTo instanceof CompetitorDTO) { + compToSelect = (CompetitorDTO) mappedTo; + } else if (mappedTo instanceof MarkDTO) { + markToSelect = (MarkDTO) mappedTo; + } + } + } + select(); + } + + @Override + protected Widget getAdditionalWidget() { + HorizontalPanel panel = new HorizontalPanel(); + VerticalPanel leftPanel = new VerticalPanel(); + VerticalPanel tablesPanel = new VerticalPanel(); + CaptionPanel marksPanel = new CaptionPanel(stringMessages.mark()); + CaptionPanel competitorsPanel = new CaptionPanel(stringMessages.competitor()); + + leftPanel.add(importWidget); + leftPanel.add(deviceIdTable); + panel.add(leftPanel); + panel.add(tablesPanel); + tablesPanel.add(marksPanel); + tablesPanel.add(competitorsPanel); + + marksPanel.setContentWidget(markTable.getTable()); + competitorsPanel.setContentWidget(competitorTable.getTable()); + + return panel; + } + + @Override + protected Collection getResult() { + List result = new ArrayList<>(); + for (TrackFileImportDeviceIdentifierDTO device : mappings.keySet()) { + DeviceIdentifierDTO deviceIdDto = new DeviceIdentifierDTO("FILE", device.uuidAsString); + Serializable mappedTo = mappings.get(device); + DeviceMappingDTO mapping = new DeviceMappingDTO(deviceIdDto, device.from, device.to, + mappedTo, null); + result.add(mapping); + } + return result; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java index 613d36e7142..afbe55e69ab 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java @@ -29,7 +29,8 @@ public class LeaderboardConfigImagesBarCell extends ImagesBarCell { @Override protected Iterable getImageSpecs() { - return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), + return Arrays.asList( + new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), new ImageSpec(ACTION_EDIT_SCORES, stringMessages.actionEditScores(), makeImagePrototype(resources.scoresIcon())), new ImageSpec(ACTION_EDIT_COMPETITORS, stringMessages.actionEditCompetitors(), makeImagePrototype(resources.competitorsIcon())), new ImageSpec(ACTION_CONFIGURE_URL, stringMessages.actionConfigureUrl(), makeImagePrototype(resources.settingsActionIcon())), 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 d268f4508c5..caef5f0b85a 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 @@ -1,1341 +1,902 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -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.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.ColumnSortList; -import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.CaptionPanel; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.Grid; -import com.google.gwt.user.client.ui.HasHorizontalAlignment; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.Label; -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.SingleSelectionModel; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.RegattaIdentifier; -import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; -import com.sap.sailing.domain.common.dto.CompetitorDTO; -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.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.gwt.ui.adminconsole.DisablableCheckboxCell.IsEnabled; -import com.sap.sailing.gwt.ui.adminconsole.RaceColumnInLeaderboardDialog.RaceColumnDescriptor; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -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.RegattaDisplayer; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.URLEncoder; -import com.sap.sailing.gwt.ui.client.shared.panels.LabeledAbstractFilterablePanel; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint; -import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; -import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; -import com.sap.sailing.gwt.ui.shared.EventDTO; -import com.sap.sailing.gwt.ui.shared.RaceLogDTO; -import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; -import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sse.gwt.client.async.MarkedAsyncCallback; -import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; - -public class LeaderboardConfigPanel extends FormPanel implements SelectedLeaderboardProvider, RegattaDisplayer, RaceSelectionChangeListener, - TrackedRaceChangedListener { - - private final TrackedRacesListComposite trackedRacesListComposite; - - private final StringMessages stringMessages; - - private final SailingServiceAsync sailingService; - - private final ListDataProvider leaderboardList; - - private final ListDataProvider raceColumnAndFleetList; - - private final ErrorReporter errorReporter; - - private final CellTable leaderboardTable; - - private Button leaderboardRemoveButton; - - private final CellTable raceColumnTable; - - private RaceColumnDTOAndFleetDTOWithNameBasedEquality selectedRaceInLeaderboard; - - private final Button addRaceColumnsButton; - - private final Button columnMoveUpButton; - private final Button columnMoveDownButton; - - private final CaptionPanel selectedLeaderBoardPanel; - private final CaptionPanel trackedRacesCaptionPanel; - private final List allRegattas; - - private LabeledAbstractFilterablePanel filterLeaderboardPanel; - - final SingleSelectionModel raceColumnTableSelectionModel; - - private List availableLeaderboardList; - - private final MultiSelectionModel leaderboardSelectionModel; - - private final RaceSelectionProvider raceSelectionProvider; - - public static class AnchorCell extends AbstractCell { - - @Override - public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) { - sb.append(safeHtml); - } - } - - interface AnchorTemplates extends SafeHtmlTemplates { - @SafeHtmlTemplates.Template("{1}") - SafeHtml cell(String url, String displayName); - } - - private static AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); - - private static class RaceColumnDTOAndFleetDTOWithNameBasedEquality extends Pair { - private static final long serialVersionUID = -8742476113296862662L; - - public RaceColumnDTOAndFleetDTOWithNameBasedEquality(RaceColumnDTO a, FleetDTO b) { - super(a, b); - } - - @Override - public int hashCode() { - return getA().getName().hashCode() ^ getB().getName().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } else { - if (obj == null) { - return false; - } else { - return equalNamesOrBothNull(getA(), ((RaceColumnDTOAndFleetDTOWithNameBasedEquality) obj).getA()) - && equalNamesOrBothNull(getB(), ((RaceColumnDTOAndFleetDTOWithNameBasedEquality) obj).getB()); - } - } - } - - private boolean equalNamesOrBothNull(NamedDTO a, NamedDTO b) { - if (a == null) { - return b == null; - } else { - if (b == null) { - return false; - } else { - return Util.equalsWithNull(a.getName(), b.getName()); - } - } - } - } - - public LeaderboardConfigPanel(final SailingServiceAsync sailingService, AdminConsoleEntryPoint adminConsole, - final ErrorReporter errorReporter, StringMessages theStringConstants, final boolean showRaceDetails) { - this.stringMessages = theStringConstants; - this.sailingService = sailingService; - leaderboardList = new ListDataProvider(); - allRegattas = new ArrayList(); - raceColumnAndFleetList = new ListDataProvider(); - this.errorReporter = errorReporter; - this.availableLeaderboardList = new ArrayList(); - VerticalPanel mainPanel = new VerticalPanel(); - mainPanel.setWidth("100%"); - this.setWidget(mainPanel); - - //Create leaderboards list and functionality - CaptionPanel leaderboardsCaptionPanel = new CaptionPanel(stringMessages.leaderboards()); - leaderboardsCaptionPanel.setStyleName("bold"); - leaderboardsCaptionPanel.setWidth("75%"); - mainPanel.add(leaderboardsCaptionPanel); - - VerticalPanel leaderboardsPanel = new VerticalPanel(); - leaderboardsCaptionPanel.add(leaderboardsPanel); - - HorizontalPanel leaderboardConfigControlsPanel = new HorizontalPanel(); - Label lblFilterEvents = new Label(stringMessages.filterLeaderboardsByName() + ": "); - leaderboardConfigControlsPanel.setSpacing(5); - - AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); - leaderboardTable = new CellTable(/* pageSize */10000, tableRes); - leaderboardTable.ensureDebugId("LeaderboardsCellTable"); - filterLeaderboardPanel = new LabeledAbstractFilterablePanel(lblFilterEvents, availableLeaderboardList, leaderboardTable, leaderboardList) { - @Override - public List getSearchableStrings(StrippedLeaderboardDTO t) { - List strings = new ArrayList(); - strings.add(t.name); - strings.add(t.displayName); - return strings; - } - }; - filterLeaderboardPanel.getTextBox().ensureDebugId("LeaderboardsFilterTextBox"); - - leaderboardRemoveButton = new Button(stringMessages.remove()); - leaderboardRemoveButton.ensureDebugId("LeaderboardsRemoveButton"); - leaderboardRemoveButton.setEnabled(false); - leaderboardRemoveButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (Window.confirm(stringMessages.doYouReallyWantToRemoveLeaderboards())) { - removeLeaderboards(leaderboardSelectionModel.getSelectedSet()); - } - } - }); - leaderboardConfigControlsPanel.add(filterLeaderboardPanel); - leaderboardConfigControlsPanel.add(leaderboardRemoveButton); - leaderboardsPanel.add(leaderboardConfigControlsPanel); - ListHandler leaderboardColumnListHandler = new ListHandler( - leaderboardList.getList()); - - 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 : "")); - return ANCHORTEMPLATE.cell(link, object.name); - } - - }; - linkColumn.setSortable(true); - leaderboardColumnListHandler.setComparator(linkColumn, new Comparator() { - - @Override - public int compare(StrippedLeaderboardDTO o1, StrippedLeaderboardDTO o2) { - boolean ascending = isSortedAscending(); - if (o1.name.equals(o2.name)) { - return 0; - } - int val = -1; - val = (o1 != null && o2 != null && ascending) ? (o1.name.compareTo(o2.name)) : -(o2.name - .compareTo(o1.name)); - - return val; - } - - private boolean isSortedAscending() { - ColumnSortList sortList = leaderboardTable.getColumnSortList(); - return sortList.size() > 0 & sortList.get(0).isAscending(); - } - }); - - TextColumn leaderboardDisplayNameColumn = new TextColumn() { - @Override - public String getValue(StrippedLeaderboardDTO leaderboard) { - return leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : ""; - } - }; - - TextColumn discardingOptionsColumn = new TextColumn() { - @Override - public String getValue(StrippedLeaderboardDTO leaderboard) { - String result = ""; - if (leaderboard.discardThresholds != null) { - for (int discardThreshold : leaderboard.discardThresholds) { - result += discardThreshold + " "; - } - } - return result; - } - }; - - TextColumn leaderboardTypeColumn = new TextColumn() { - @Override - public String getValue(StrippedLeaderboardDTO leaderboard) { - String result = leaderboard.type.isRegattaLeaderboard() ? "Regatta" : "Flexible"; - if (leaderboard.type.isMetaLeaderboard()) { - result += " , Meta"; - } - return result; - } - }; - - TextColumn scoringSystemColumn = new TextColumn() { - @Override - public String getValue(StrippedLeaderboardDTO leaderboard) { - return leaderboard.scoringScheme == null ? "" : ScoringSchemeTypeFormatter.format(leaderboard.scoringScheme, stringMessages); - } - }; - - TextColumn courseAreaColumn = new TextColumn() { - @Override - public String getValue(StrippedLeaderboardDTO leaderboard) { - return leaderboard.defaultCourseAreaId == null ? "" : leaderboard.defaultCourseAreaName; - } - }; - - ImagesBarColumn leaderboardActionColumn = new ImagesBarColumn( - new LeaderboardConfigImagesBarCell(stringMessages)); - leaderboardActionColumn.setFieldUpdater(new FieldUpdater() { - @Override - public void update(int index, StrippedLeaderboardDTO leaderboardDTO, String value) { - if (LeaderboardConfigImagesBarCell.ACTION_REMOVE.equals(value)) { - if (Window.confirm(stringMessages.doYouReallyWantToRemoveLeaderboard(leaderboardDTO.name))) { - removeLeaderboard(leaderboardDTO); - } - } else if (LeaderboardConfigImagesBarCell.ACTION_EDIT.equals(value)) { - final String oldLeaderboardName = leaderboardDTO.name; - List otherExistingLeaderboard = new ArrayList(); - otherExistingLeaderboard.addAll(availableLeaderboardList); - otherExistingLeaderboard.remove(leaderboardDTO); - if (leaderboardDTO.type.isMetaLeaderboard()) { - Window.alert(stringMessages.metaLeaderboardCannotBeChanged()); - } else { - if (leaderboardDTO.type.isRegattaLeaderboard()) { - LeaderboardDescriptor descriptor = new LeaderboardDescriptor(leaderboardDTO.name, - leaderboardDTO.displayName, /* scoring scheme provided by regatta */ null, - leaderboardDTO.discardThresholds, leaderboardDTO.regattaName, - leaderboardDTO.defaultCourseAreaId); - AbstractLeaderboardDialog dialog = new RegattaLeaderboardEditDialog(Collections - .unmodifiableCollection(otherExistingLeaderboard), Collections.unmodifiableCollection(allRegattas), - descriptor, stringMessages, errorReporter, - new DialogCallback() { - @Override - public void cancel() { - } - - @Override - public void ok(LeaderboardDescriptor result) { - updateLeaderboard(oldLeaderboardName, result); - } - }); - dialog.show(); - } else { - LeaderboardDescriptor descriptor = new LeaderboardDescriptor(leaderboardDTO.name, leaderboardDTO.displayName, leaderboardDTO.scoringScheme, leaderboardDTO.discardThresholds, leaderboardDTO.defaultCourseAreaId); - openUpdateFlexibleLeaderboardDialog(leaderboardDTO, otherExistingLeaderboard, leaderboardDTO.name, descriptor); - } - } - } 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); - } else if (LeaderboardConfigImagesBarCell.ACTION_EDIT_COMPETITORS.equals(value)) { - EditCompetitorsDialog editCompetitorsDialog = new EditCompetitorsDialog(sailingService, leaderboardDTO.name, stringMessages, - errorReporter, new DialogCallback>() { - @Override - public void cancel() { - } - - @Override - public void ok(final List result) { - } - }); - editCompetitorsDialog.show(); - - } 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); - } - } - }); - leaderboardTable.addColumn(linkColumn, stringMessages.name()); - leaderboardTable.addColumn(leaderboardDisplayNameColumn, stringMessages.displayName()); - leaderboardTable.addColumn(discardingOptionsColumn, stringMessages.discarding()); - leaderboardTable.addColumn(leaderboardTypeColumn, stringMessages.type()); - leaderboardTable.addColumn(scoringSystemColumn, stringMessages.scoringSystem()); - leaderboardTable.addColumn(courseAreaColumn, stringMessages.courseArea()); - leaderboardTable.addColumn(leaderboardActionColumn, stringMessages.actions()); - leaderboardTable.addColumnSortHandler(leaderboardColumnListHandler); - leaderboardTable.setWidth("100%"); - leaderboardSelectionModel = new MultiSelectionModel(); - leaderboardTable.setSelectionModel(leaderboardSelectionModel); - leaderboardSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { - public void onSelectionChange(SelectionChangeEvent event) { - leaderboardSelectionChanged(); - } - }); - leaderboardList.addDataDisplay(leaderboardTable); - leaderboardsPanel.add(leaderboardTable); - - HorizontalPanel leaderboardButtonPanel = new HorizontalPanel(); - leaderboardButtonPanel.setSpacing(5); - leaderboardsPanel.add(leaderboardButtonPanel); - Button createFlexibleLeaderboardBtn = new Button(stringMessages.createFlexibleLeaderboard() + "..."); - createFlexibleLeaderboardBtn.ensureDebugId("CreateFlexibleLeaderboardButton"); - leaderboardButtonPanel.add(createFlexibleLeaderboardBtn); - createFlexibleLeaderboardBtn.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent clickEvent) { - createFlexibleLeaderboard(); - } - }); - - Button createRegattaLeaderboardBtn = new Button(stringMessages.createRegattaLeaderboard() + "..."); - createRegattaLeaderboardBtn.ensureDebugId("CreateRegattaLeaderboardButton"); - leaderboardButtonPanel.add(createRegattaLeaderboardBtn); - createRegattaLeaderboardBtn.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent clickEvent) { - createRegattaLeaderboard(); - } - }); - - mainPanel.add(new Grid(1, 1)); - - // caption panels for the selected leaderboard and tracked races - HorizontalPanel splitPanel = new HorizontalPanel(); - splitPanel.ensureDebugId("LeaderboardDetailsPanel"); - mainPanel.add(splitPanel); - - selectedLeaderBoardPanel = new CaptionPanel(stringMessages.leaderboard()); - selectedLeaderBoardPanel.setWidth("50%"); - splitPanel.add(selectedLeaderBoardPanel); - - VerticalPanel vPanel = new VerticalPanel(); - vPanel.setWidth("100%"); - selectedLeaderBoardPanel.setContentWidget(vPanel); - - trackedRacesCaptionPanel = new CaptionPanel(stringMessages.trackedRaces()); - trackedRacesCaptionPanel.setWidth("50%"); - splitPanel.add(trackedRacesCaptionPanel); - - VerticalPanel trackedRacesPanel = new VerticalPanel(); - trackedRacesPanel.setWidth("100%"); - trackedRacesCaptionPanel.setContentWidget(trackedRacesPanel); - trackedRacesCaptionPanel.setStyleName("bold"); - - raceSelectionProvider = new RaceSelectionModel(); - trackedRacesListComposite = new TrackedRacesListComposite(sailingService, errorReporter, adminConsole, - raceSelectionProvider, stringMessages, /* multiselection */false); - trackedRacesListComposite.ensureDebugId("TrackedRacesListComposite"); - trackedRacesPanel.add(trackedRacesListComposite); - trackedRacesListComposite.addTrackedRaceChangeListener(this); - raceSelectionProvider.addRaceSelectionChangeListener(this); - - Button reloadAllRaceLogs = new Button(stringMessages.reloadAllRaceLogs()); - reloadAllRaceLogs.ensureDebugId("ReloadAllRaceLogsButton"); - reloadAllRaceLogs.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - StrippedLeaderboardDTO leaderboard = getSelectedLeaderboard(); - for (RaceColumnDTO column : leaderboard.getRaceList()) { - for (FleetDTO fleet : column.getFleets()) { - refreshRaceLog(column, fleet, false); - } - } - Window.alert(stringMessages.raceLogReloaded()); - } - }); - vPanel.add(reloadAllRaceLogs); - - // ------------ races of the selected leaderboard ---------------- - AnchorCell raceAnchorCell = new AnchorCell(); - Column raceLinkColumn = new Column(raceAnchorCell) { - @Override - public SafeHtml getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality raceInLeaderboardDTOAndFleetName) { - if (raceInLeaderboardDTOAndFleetName.getA().getRaceIdentifier(raceInLeaderboardDTOAndFleetName.getB()) != null) { - RegattaNameAndRaceName raceIdentifier = (RegattaNameAndRaceName) raceInLeaderboardDTOAndFleetName - .getA().getRaceIdentifier(raceInLeaderboardDTOAndFleetName.getB()); - String debugParam = Window.Location.getParameter("gwt.codesvr"); - String link = URLEncoder.encode("/gwt/RaceBoard.html?leaderboardName=" - + getSelectedLeaderboard().name + "&raceName=" + raceIdentifier.getRaceName() + "®attaName=" - + raceIdentifier.getRegattaName() - + "&"+RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES + "=true" - + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); - return ANCHORTEMPLATE.cell(link, raceInLeaderboardDTOAndFleetName.getA().getRaceColumnName()); - } else { - return SafeHtmlUtils.fromString(raceInLeaderboardDTOAndFleetName.getA().getRaceColumnName()); - } - } - }; - TextColumn fleetNameColumn = new TextColumn() { - @Override - public String getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality object) { - return object.getB().getName(); - } - }; - TextColumn explicitFactorColumn = new TextColumn() { - @Override - public String getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality object) { - return object.getA().getExplicitFactor() == null ? "" : object.getA().getExplicitFactor().toString(); - } - }; - - Column isMedalRaceCheckboxColumn = new Column( - new DisablableCheckboxCell(new IsEnabled() { - @Override - public boolean isEnabled() { - return getSelectedLeaderboard() != null && !getSelectedLeaderboard().type.isRegattaLeaderboard(); - - } - })) { - @Override - public Boolean getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality race) { - return race.getA().isMedalRace(); - } - }; - isMedalRaceCheckboxColumn.setFieldUpdater(new FieldUpdater() { - @Override - public void update(int index, RaceColumnDTOAndFleetDTOWithNameBasedEquality object, Boolean value) { - setIsMedalRace(getSelectedLeaderboard().name, object.getA(), value); - } - }); - isMedalRaceCheckboxColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); - - TextColumn isLinkedRaceColumn = new TextColumn() { - @Override - public String getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnAndFleetName) { - boolean isTrackedRace = raceColumnAndFleetName.getA().isTrackedRace(raceColumnAndFleetName.getB()); - return isTrackedRace ? stringMessages.yes() : stringMessages.no(); - } - }; - ImagesBarColumn raceActionColumn = - new ImagesBarColumn( - new LeaderboardRaceConfigImagesBarCell(this, stringMessages)); - raceActionColumn.setFieldUpdater(new FieldUpdater() { - @Override - public void update(int index, RaceColumnDTOAndFleetDTOWithNameBasedEquality object, String value) { - if (LeaderboardRaceConfigImagesBarCell.ACTION_REMOVE.equals(value)) { - if (Window.confirm(stringMessages.reallyRemoveRace(object.getA().getRaceColumnName()))) { - removeRaceColumn(object.getA()); - } - } else if (LeaderboardRaceConfigImagesBarCell.ACTION_EDIT.equals(value)) { - editRaceColumnOfLeaderboard(object); - } else if (LeaderboardRaceConfigImagesBarCell.ACTION_UNLINK.equals(value)) { - unlinkRaceColumnFromTrackedRace(object.getA().getRaceColumnName(), object.getB()); - } else if (LeaderboardRaceConfigImagesBarCell.ACTION_REFRESH_RACELOG.equals(value)) { - refreshRaceLog(object.getA(), object.getB(), true); - } else if(LeaderboardRaceConfigImagesBarCell.ACTION_SET_STARTTIME.equals(value)) { - setStartTime(object.getA(), object.getB()); - } else if(LeaderboardRaceConfigImagesBarCell.ACTION_SHOW_RACELOG.equals(value)) { - showRaceLog(object.getA(), object.getB()); - } - } - }); - Label lblRaceNamesIn = new Label(stringMessages.races()); - vPanel.add(lblRaceNamesIn); - raceColumnTable = new CellTable(/* pageSize */200, tableRes); - raceColumnTable.ensureDebugId("RacesCellTable"); - raceColumnTable.addColumn(raceLinkColumn, stringMessages.name()); - raceColumnTable.addColumn(fleetNameColumn, stringMessages.fleet()); - raceColumnTable.addColumn(isMedalRaceCheckboxColumn, stringMessages.medalRace()); - raceColumnTable.addColumn(isLinkedRaceColumn, stringMessages.islinked()); - raceColumnTable.addColumn(explicitFactorColumn, stringMessages.factor()); - raceColumnTable.addColumn(raceActionColumn, stringMessages.actions()); - raceColumnAndFleetList.addDataDisplay(raceColumnTable); - raceColumnTable.setWidth("500px"); - raceColumnTableSelectionModel = new SingleSelectionModel(); - raceColumnTable.setSelectionModel(raceColumnTableSelectionModel); - raceColumnTableSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { - public void onSelectionChange(SelectionChangeEvent event) { - leaderboardRaceColumnSelectionChanged(); - } - }); - vPanel.add(raceColumnTable); - - HorizontalPanel selectedLeaderboardRaceButtonPanel = new HorizontalPanel(); - selectedLeaderboardRaceButtonPanel.setSpacing(5); - vPanel.add(selectedLeaderboardRaceButtonPanel); - - addRaceColumnsButton = new Button(stringMessages.actionAddRaces() + "..."); - addRaceColumnsButton.ensureDebugId("AddRacesButton"); - addRaceColumnsButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (getSelectedLeaderboard().type.isRegattaLeaderboard()) { - Window.alert(stringMessages.cannotAddRacesToRegattaLeaderboardButOnlyToRegatta()); - } else { - addRaceColumnsToLeaderboard(); - } - } - }); - selectedLeaderboardRaceButtonPanel.add(addRaceColumnsButton); - - columnMoveUpButton = new Button(stringMessages.columnMoveUp()); - columnMoveUpButton.ensureDebugId("MoveRaceUpButton"); - columnMoveUpButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - moveSelectedRaceColumnUp(); - } - }); - selectedLeaderboardRaceButtonPanel.add(columnMoveUpButton); - - columnMoveDownButton = new Button(stringMessages.columnMoveDown()); - columnMoveDownButton.ensureDebugId("MoveRaceDownButton"); - columnMoveDownButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - moveSelectedRaceColumnDown(); - } - }); - selectedLeaderboardRaceButtonPanel.add(columnMoveDownButton); - - loadLeaderboards(); - } - - protected void openUpdateFlexibleLeaderboardDialog(final StrippedLeaderboardDTO leaderboardDTO, final List otherExistingLeaderboard, - final String oldLeaderboardName, final LeaderboardDescriptor descriptor) { - sailingService.getEvents(new MarkedAsyncCallback>( - new AsyncCallback>() { - @Override - public void onSuccess(List result) { - openUpdateFlexibleLeaderboardDialog(leaderboardDTO, otherExistingLeaderboard, oldLeaderboardName, - descriptor, result); - } - - @Override - public void onFailure(Throwable caught) { - openUpdateFlexibleLeaderboardDialog(leaderboardDTO, otherExistingLeaderboard, oldLeaderboardName, - descriptor, new ArrayList()); - } - })); - } - - protected void openUpdateFlexibleLeaderboardDialog(StrippedLeaderboardDTO leaderboardDTO, List otherExistingLeaderboard, - final String oldLeaderboardName, LeaderboardDescriptor descriptor, List existingEvents) { - FlexibleLeaderboardEditDialog dialog = new FlexibleLeaderboardEditDialog( - Collections.unmodifiableCollection(otherExistingLeaderboard), descriptor, stringMessages, - Collections.unmodifiableList(existingEvents), errorReporter, - new DialogCallback() { - @Override - public void cancel() { - } - - @Override - public void ok(LeaderboardDescriptor result) { - updateLeaderboard(oldLeaderboardName, result); - } - }); - dialog.show(); - } - - /** - * Allow the user to combine the various URL parameters that exist for the {@link LeaderboardEntryPoint} and obtain the - * resulting URL in a link. The link's reference target is updated dynamically as the user adjusts the settings. Therefore, - * the link can be clicked, bookmarked or copied to the clipboard at any time. The OK / Cancel actions for the dialog shown - * are no-ops. - */ - private void openLeaderboardUrlConfigDialog(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages) { - LeaderboardEntryPoint.getUrlConfigurationDialog(leaderboard, stringMessages).show(); - } - - public void loadLeaderboards() { - sailingService.getLeaderboards(new MarkedAsyncCallback>( - new AsyncCallback>() { - @Override - public void onSuccess(List leaderboards) { - leaderboardList.getList().clear(); - availableLeaderboardList.clear(); - leaderboardList.getList().addAll(leaderboards); - availableLeaderboardList.addAll(leaderboards); - filterLeaderboardPanel.updateAll(availableLeaderboardList); - leaderboardSelectionChanged(); - leaderboardRaceColumnSelectionChanged(); - } - - @Override - public void onFailure(Throwable t) { - LeaderboardConfigPanel.this.errorReporter.reportError("Error trying to obtain list of leaderboards: " - + t.getMessage()); - } - })); - } - - /** - * @param nameOfRaceColumnToSelect - * if not null, selects the first race column name with this name found in the leaderboard - * after the refresh has successfully completed. See {@link #selectRaceColumn(String)}. - */ - public void loadAndRefreshLeaderboard(final String leaderboardName, final String nameOfRaceColumnToSelect) { - sailingService.getLeaderboard(leaderboardName, new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onSuccess(StrippedLeaderboardDTO leaderboard) { - for (StrippedLeaderboardDTO leaderboardDTO : leaderboardSelectionModel.getSelectedSet()) { - if (leaderboardDTO.name.equals(leaderboardName)) { - leaderboardSelectionModel.setSelected(leaderboardDTO, false); - break; - } - } - - replaceLeaderboardInList(leaderboardList.getList(), leaderboardName, leaderboard); - replaceLeaderboardInList(availableLeaderboardList, leaderboardName, leaderboard); - leaderboardSelectionModel.setSelected(leaderboard, true); - if (nameOfRaceColumnToSelect != null) { - selectRaceColumn(nameOfRaceColumnToSelect); - } - leaderboardSelectionChanged(); - } - - @Override - public void onFailure(Throwable t) { - LeaderboardConfigPanel.this.errorReporter.reportError("Error trying to update leaderboard with name " + leaderboardName + " : " - + t.getMessage()); - } - })); - } - - private void replaceLeaderboardInList(List leaderboardList, String leaderboardToReplace, StrippedLeaderboardDTO newLeaderboard) { - int index = -1; - for (StrippedLeaderboardDTO existingLeaderboard : leaderboardList) { - index++; - if (existingLeaderboard.name.equals(leaderboardToReplace)) { - break; - } - } - if (index >= 0) { - leaderboardList.set(index, newLeaderboard); - } - } - - private void unlinkRaceColumnFromTrackedRace(final String raceColumnName, final FleetDTO fleet) { - final String selectedLeaderboardName = getSelectedLeaderboardName(); - sailingService.disconnectLeaderboardColumnFromTrackedRace(selectedLeaderboardName, raceColumnName, fleet.getName(), - new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to unlink tracked race from column " + raceColumnName - + " from leaderboard " + selectedLeaderboardName + ": " + t.getMessage()); - } - - @Override - public void onSuccess(Void arg0) { - trackedRacesListComposite.clearSelection(); - getSelectedRaceColumnWithFleet().getA().setRaceIdentifier(fleet, null); - raceColumnAndFleetList.refresh(); - } - })); - } - - private void setStartTime(RaceColumnDTO raceColumnDTO, FleetDTO fleetDTO) { - new SetStartTimeDialog(sailingService, errorReporter, getSelectedLeaderboardName(), raceColumnDTO.getName(), - fleetDTO.getName(), stringMessages, new DialogCallback() { - @Override - public void ok(RaceLogSetStartTimeAndProcedureDTO editedObject) { - sailingService.setStartTimeAndProcedure(editedObject, new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError(caught.getMessage()); - } - - @Override - public void onSuccess(Boolean result) { - if (!result) { - Window.alert(stringMessages.failedToSetNewStartTime()); - } - } - }); - } - - @Override - public void cancel() { } - }).show(); - } - - private void showRaceLog(final RaceColumnDTO raceColumnDTO, final FleetDTO fleetDTO) { - final String selectedLeaderboardName = getSelectedLeaderboardName(); - sailingService.getRaceLog(selectedLeaderboardName, raceColumnDTO, fleetDTO, - new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError(caught.getMessage(), true); - } - @Override - public void onSuccess(RaceLogDTO result) { - openRaceLogDialog(result); - } - })); - } - - private void openRaceLogDialog(RaceLogDTO raceLogDTO) { - RaceLogDialog dialog = new RaceLogDialog(raceLogDTO, stringMessages, new DialogCallback() { - @Override - public void cancel() { - } - - @Override - public void ok(RaceLogDTO result) { - } - }); - dialog.show(); - } - - private void refreshRaceLog(final RaceColumnDTO raceColumnDTO, final FleetDTO fleet, final boolean showAlerts) { - final String selectedLeaderboardName = getSelectedLeaderboardName(); - sailingService.reloadRaceLog(selectedLeaderboardName, raceColumnDTO, fleet, new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - if (showAlerts) { - errorReporter.reportError(caught.getMessage()); - } - } - @Override - public void onSuccess(Void result) { - if (showAlerts) { - Window.alert(stringMessages.raceLogReloaded()); - } - } - })); - } - - private void removeRaceColumn(final RaceColumnDTO raceColumnDTO) { - final String selectedLeaderboardName = getSelectedLeaderboardName(); - final String raceColumnString = raceColumnDTO.getRaceColumnName(); - sailingService.removeLeaderboardColumn(getSelectedLeaderboardName(), raceColumnString, - new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to remove leaderboard race column " + raceColumnDTO - + " in leaderboard " + getSelectedLeaderboardName() + ": " + t.getMessage()); - } - - @Override - public void onSuccess(Void arg0) { - loadAndRefreshLeaderboard(selectedLeaderboardName, /* raceColumnNameToSelect */ null); - } - })); - } - - /** - * The selected row is potentially only one of several fleet-based rows of the same RaceColumn. In this case, - * move all fleet-based rows of the same RaceColumn down. - */ - private void moveSelectedRaceColumnDown() { - final String selectedLeaderboardName = getSelectedLeaderboardName(); - final String selectedRaceColumnName = raceColumnTableSelectionModel.getSelectedObject().getA().getRaceColumnName(); - sailingService.moveLeaderboardColumnDown(getSelectedLeaderboardName(), selectedRaceColumnName, - new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to move leaderboard race column " - + selectedRaceColumnName + " in leaderboard " + getSelectedLeaderboardName() - + " down: " + caught.getMessage()); - } - - @Override - public void onSuccess(Void result) { - loadAndRefreshLeaderboard(selectedLeaderboardName, selectedRaceColumnName); - } - })); - } - - /** - * The selected row is potentially only one of several fleet-based rows of the same RaceColumn. In this case, - * move all fleet-based rows of the same RaceColumn down. - */ - private void moveSelectedRaceColumnUp() { - final String selectedLeaderboardName = getSelectedLeaderboardName(); - final String selectedRaceColumnName = raceColumnTableSelectionModel.getSelectedObject().getA().getRaceColumnName(); - sailingService.moveLeaderboardColumnUp(getSelectedLeaderboardName(), selectedRaceColumnName, - new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to move leaderboard race column " - + selectedRaceColumnName + " in leaderboard " + getSelectedLeaderboardName() + " up: " - + caught.getMessage()); - } - - @Override - public void onSuccess(Void result) { - loadAndRefreshLeaderboard(selectedLeaderboardName, selectedRaceColumnName); - } - })); - } - - private void leaderboardRaceColumnSelectionChanged() { - selectedRaceInLeaderboard = getSelectedRaceColumnWithFleet(); - if (selectedRaceInLeaderboard != null) { - columnMoveUpButton.setEnabled(true); - columnMoveDownButton.setEnabled(true); - selectTrackedRaceInRaceList(); - } else { - columnMoveUpButton.setEnabled(false); - columnMoveDownButton.setEnabled(false); - trackedRacesListComposite.clearSelection(); - } - } - - private void selectRaceColumn(String raceCoumnName) { - List list = raceColumnAndFleetList.getList(); - for (RaceColumnDTOAndFleetDTOWithNameBasedEquality pair : list) { - if (pair.getA().getName().equals(raceCoumnName)) { - raceColumnTableSelectionModel.setSelected(pair, true); - break; - } - } - } - - private void selectTrackedRaceInRaceList() { - final String selectedLeaderboardName = getSelectedLeaderboardName(); - if (selectedLeaderboardName != null) { - final RaceColumnDTOAndFleetDTOWithNameBasedEquality selectedRaceColumnAndFleetNameInLeaderboard = getSelectedRaceColumnWithFleet(); - final String selectedRaceColumnName = selectedRaceColumnAndFleetNameInLeaderboard.getA().getRaceColumnName(); - final String selectedFleetName = selectedRaceColumnAndFleetNameInLeaderboard.getB().getName(); - sailingService.getRegattaAndRaceNameOfTrackedRaceConnectedToLeaderboardColumn(selectedLeaderboardName, - selectedRaceColumnName, new MarkedAsyncCallback>( - new AsyncCallback>() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to determine tracked race linked to race column " - + selectedRaceColumnName + " in leaderboard " + selectedLeaderboardName + ": " - + t.getMessage()); - } - - @Override - public void onSuccess(Map regattaAndRaceNamesPerFleet) { - if (regattaAndRaceNamesPerFleet != null && !regattaAndRaceNamesPerFleet.isEmpty()) { - RegattaAndRaceIdentifier raceIdentifier = regattaAndRaceNamesPerFleet.get(selectedFleetName); - - if (raceIdentifier != null) { - selectRaceInList(raceIdentifier.getRegattaName(), raceIdentifier.getRaceName()); - } else { - trackedRacesListComposite.clearSelection(); - } - } else { - trackedRacesListComposite.clearSelection(); - } - } - })); - } - } - - private void selectRaceInList(String regattaName, String raceName) { - RegattaNameAndRaceName raceIdentifier = new RegattaNameAndRaceName(regattaName, raceName); - trackedRacesListComposite.selectRaceByIdentifier(raceIdentifier); - } - - private RaceColumnDTOAndFleetDTOWithNameBasedEquality getSelectedRaceColumnWithFleet() { - RaceColumnDTOAndFleetDTOWithNameBasedEquality raceInLeaderboardAndFleetName = raceColumnTableSelectionModel.getSelectedObject(); - return raceInLeaderboardAndFleetName; - } - - private void editRaceColumnOfLeaderboard(final RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnWithFleet) { - final String selectedLeaderboardName = getSelectedLeaderboardName(); - final boolean oldIsMedalRace = raceColumnWithFleet.getA().isMedalRace(); - final String oldRaceColumnName = raceColumnWithFleet.getA().getRaceColumnName(); - final Double oldExplicitFactor = raceColumnWithFleet.getA().getExplicitFactor(); - // use a set to avoid duplicates in the case of regatta leaderboards with multiple fleets per column - Set existingRacesWithoutThisRace = new HashSet(); - for (RaceColumnDTOAndFleetDTOWithNameBasedEquality pair : raceColumnAndFleetList.getList()) { - existingRacesWithoutThisRace.add(pair.getA()); - } - existingRacesWithoutThisRace.remove(raceColumnWithFleet.getA()); - final RaceColumnInLeaderboardDialog raceDialog = new RaceColumnInLeaderboardDialog(existingRacesWithoutThisRace, - raceColumnWithFleet.getA(), getSelectedLeaderboard().type.isRegattaLeaderboard(), stringMessages, new DialogCallback() { - @Override - public void cancel() { - } - - @Override - public void ok(final RaceColumnDescriptor result) { - boolean rename = !oldRaceColumnName.equals(result.getName()); - boolean updateIsMedalRace = oldIsMedalRace != result.isMedalRace(); - boolean updateFactor = oldExplicitFactor != result.getExplicitFactor(); - List> callbacks = new ArrayList>(); - final ParallelExecutionCallback renameLeaderboardColumnCallback = new ParallelExecutionCallback(); - if (rename) { - callbacks.add(renameLeaderboardColumnCallback); - } - final ParallelExecutionCallback updateIsMedalRaceCallback = new ParallelExecutionCallback(); - if (updateIsMedalRace) { - callbacks.add(updateIsMedalRaceCallback); - } - final ParallelExecutionCallback updateLeaderboardColumnFactorCallback = new ParallelExecutionCallback(); - if (updateFactor) { - callbacks.add(updateLeaderboardColumnFactorCallback); - } - new ParallelExecutionHolder(callbacks.toArray(new ParallelExecutionCallback[0])) { - @Override - public void handleSuccess() { - loadAndRefreshLeaderboard(selectedLeaderboardName, result.getName()); - } - @Override - public void handleFailure(Throwable t) { - errorReporter.reportError("Error trying to update data of race column " - + oldRaceColumnName + " in leaderboard " + selectedLeaderboardName + ": " - + t.getMessage()); - } - }; - if (rename) { - sailingService.renameLeaderboardColumn(selectedLeaderboardName, oldRaceColumnName, - result.getName(), renameLeaderboardColumnCallback); - } - if (updateIsMedalRace) { - sailingService.updateIsMedalRace(selectedLeaderboardName, result.getName(), - result.isMedalRace(), updateIsMedalRaceCallback); - } - if (updateFactor) { - sailingService.updateLeaderboardColumnFactor(selectedLeaderboardName, result.getName(), - result.getExplicitFactor(), updateLeaderboardColumnFactorCallback); - } - } - }); - raceDialog.show(); - } - - private void setIsMedalRace(String leaderboardName, final RaceColumnDTO raceInLeaderboard, - final boolean isMedalRace) { - sailingService.updateIsMedalRace(leaderboardName, raceInLeaderboard.getRaceColumnName(), isMedalRace, - new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError(stringMessages.errorUpdatingIsMedalRace(caught.getMessage())); - } - - @Override - public void onSuccess(Void result) { - getSelectedLeaderboard().setIsMedalRace(raceInLeaderboard.getRaceColumnName(), isMedalRace); - } - })); - } - - private void addRaceColumnsToLeaderboard() { - final String leaderboardName = getSelectedLeaderboardName(); - final List existingRaceColumns = new ArrayList(); - for (RaceColumnDTOAndFleetDTOWithNameBasedEquality pair : raceColumnAndFleetList.getList()) { - existingRaceColumns.add(pair.getA()); - } - final RaceColumnsInLeaderboardDialog raceDialog = new RaceColumnsInLeaderboardDialog(existingRaceColumns, - stringMessages, new DialogCallback>() { - @Override - public void cancel() { - } - - @Override - public void ok(final List result) { - updateRaceColumnsOfLeaderboard(leaderboardName, existingRaceColumns, result); - } - }); - raceDialog.ensureDebugId("RaceColumnsInLeaderboardDialog"); - raceDialog.show(); - } - - private void updateRaceColumnsOfLeaderboard(final String leaderboardName, List existingRaceColumns, List newRaceColumns) { - final List> raceColumnsToAdd = new ArrayList>(); - - for (RaceColumnDTO newRaceColumn : newRaceColumns) { - if (!existingRaceColumns.contains(newRaceColumn)) { - raceColumnsToAdd.add(new Pair(newRaceColumn.getName(), newRaceColumn.isMedalRace())); - } - } - - sailingService.addColumnsToLeaderboard(leaderboardName, raceColumnsToAdd, new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to add race columns to leaderboard " + leaderboardName - + ": " + caught.getMessage()); - } - - @Override - public void onSuccess(Void v) { - loadAndRefreshLeaderboard(leaderboardName, /* nameOfRaceColumnToSelect */ null); - } - })); - } - - private String getSelectedLeaderboardName() { - return getSelectedLeaderboard() != null ? getSelectedLeaderboard().name : null; - } - - private 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) { - raceColumnAndFleetList.getList().clear(); - for (RaceColumnDTO raceColumn : selectedLeaderboard.getRaceList()) { - for (FleetDTO fleet : raceColumn.getFleets()) { - raceColumnAndFleetList.getList().add(new RaceColumnDTOAndFleetDTOWithNameBasedEquality(raceColumn, fleet)); - } - } - selectedLeaderBoardPanel.setVisible(true); - selectedLeaderBoardPanel.setCaptionText("Details of leaderboard '" + selectedLeaderboard.name + "'"); - if (!selectedLeaderboard.type.isMetaLeaderboard()) { - trackedRacesCaptionPanel.setVisible(true); - } - addRaceColumnsButton.setVisible(!selectedLeaderboard.type.isRegattaLeaderboard()); - columnMoveUpButton.setVisible(!selectedLeaderboard.type.isRegattaLeaderboard()); - columnMoveDownButton.setVisible(!selectedLeaderboard.type.isRegattaLeaderboard()); - } else { - selectedLeaderBoardPanel.setVisible(false); - trackedRacesCaptionPanel.setVisible(false); - selectedRaceInLeaderboard = null; - } - } - - @Override - public void fillRegattas(List regattas) { - trackedRacesListComposite.fillRegattas(regattas); - - allRegattas.clear(); - allRegattas.addAll(regattas); - } - - @Override - public void changeTrackingRace(Iterable regattaAndRaceIdentifiers, boolean isTracked) { - for (RegattaAndRaceIdentifier regattaAndRaceIdentifier : regattaAndRaceIdentifiers) { - for (RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnAndFleetName : raceColumnAndFleetList.getList()) { - if (raceColumnAndFleetName.getA().getRaceColumnName().equals(regattaAndRaceIdentifier.getRaceName())) { - raceColumnAndFleetName.getA().setRaceIdentifier(raceColumnAndFleetName.getB(), - regattaAndRaceIdentifier); - } - } - raceColumnAndFleetList.refresh(); - } - } - - private void createFlexibleLeaderboard() { - sailingService.getEvents(new MarkedAsyncCallback>( - new AsyncCallback>() { - @Override - public void onSuccess(List result) { - createFlexibleLeaderboard(result); - } - - @Override - public void onFailure(Throwable caught) { - createFlexibleLeaderboard(new ArrayList()); - } - })); - } - - private void createFlexibleLeaderboard(List existingEvents) { - - AbstractLeaderboardDialog dialog = new FlexibleLeaderboardCreateDialog(Collections.unmodifiableCollection(availableLeaderboardList), - stringMessages, Collections.unmodifiableCollection(existingEvents), errorReporter, new DialogCallback() { - @Override - public void cancel() { - } - - @Override - public void ok(final LeaderboardDescriptor newLeaderboard) { - sailingService.createFlexibleLeaderboard(newLeaderboard.getName(), newLeaderboard.getDisplayName(), - newLeaderboard.getDiscardThresholds(), newLeaderboard.getScoringScheme(), newLeaderboard.getCourseAreaId(), - new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to create the new flexible leaderboard " + newLeaderboard.getName() - + ": " + t.getMessage()); - } - - @Override - public void onSuccess(StrippedLeaderboardDTO result) { - addLeaderboard(result); - } - })); - } - }); - dialog.ensureDebugId("FlexibleLeaderboardCreateDialog"); - dialog.show(); - } - - private void createRegattaLeaderboard() { - RegattaLeaderboardCreateDialog dialog = new RegattaLeaderboardCreateDialog(Collections.unmodifiableCollection(availableLeaderboardList), - Collections.unmodifiableCollection(allRegattas), stringMessages, errorReporter, new DialogCallback() { - @Override - public void cancel() { - } - - @Override - public void ok(final LeaderboardDescriptor newLeaderboard) { - RegattaIdentifier regattaIdentifier = new RegattaName(newLeaderboard.getRegattaName()); - sailingService.createRegattaLeaderboard(regattaIdentifier, newLeaderboard.getDisplayName(), newLeaderboard.getDiscardThresholds(), - new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to create the new regatta leaderboard " + newLeaderboard.getName() - + ": " + t.getMessage()); - } - - @Override - public void onSuccess(StrippedLeaderboardDTO result) { - addLeaderboard(result); - } - }); - } - }); - dialog.ensureDebugId("RegattaLeaderboardCreateDialog"); - dialog.show(); - } - - private void addLeaderboard(StrippedLeaderboardDTO result) { - leaderboardList.getList().add(result); - availableLeaderboardList.add(result); - leaderboardSelectionModel.clear(); - leaderboardSelectionModel.setSelected(result, true); - } - - private void updateLeaderboard(final String oldLeaderboardName, final LeaderboardDescriptor leaderboardToUpdate) { - sailingService.updateLeaderboard(oldLeaderboardName, leaderboardToUpdate.getName(), leaderboardToUpdate.getDisplayName(), - leaderboardToUpdate.getDiscardThresholds(), leaderboardToUpdate.getCourseAreaId(), - new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to update leaderboard " + oldLeaderboardName + ": " - + t.getMessage()); - } - - @Override - public void onSuccess(StrippedLeaderboardDTO updatedLeaderboard) { - int indexOfLeaderboard = 0; - for (int i = 0; i < leaderboardList.getList().size(); i++) { - StrippedLeaderboardDTO dao = leaderboardList.getList().get(i); - if (dao.name.equals(oldLeaderboardName)) { - indexOfLeaderboard = i; - break; - } - } - leaderboardList.getList().set(indexOfLeaderboard, updatedLeaderboard); - leaderboardList.refresh(); - } - })); - } - - private void removeLeaderboards(final Collection leaderboards) { - if (!leaderboards.isEmpty()) { - Set leaderboardNames = new HashSet(); - for (StrippedLeaderboardDTO leaderboard : leaderboards) { - leaderboardNames.add(leaderboard.name); - } - sailingService.removeLeaderboards(leaderboardNames, new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to remove the leaderboards:" + caught.getMessage()); - - - } - - @Override - public void onSuccess(Void result) { - for (StrippedLeaderboardDTO leaderboard : leaderboards) { - removeLeaderboardFromTable(leaderboard); - } - } - })); - } - } - - private void removeLeaderboard(final StrippedLeaderboardDTO leaderBoard) { - sailingService.removeLeaderboard(leaderBoard.name, new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to remove leaderboard " + leaderBoard.name + ": " - + caught.getMessage()); - } - - @Override - public void onSuccess(Void result) { - removeLeaderboardFromTable(leaderBoard); - } - })); - } - - private void removeLeaderboardFromTable(final StrippedLeaderboardDTO leaderBoard) { - leaderboardList.getList().remove(leaderBoard); - availableLeaderboardList.remove(leaderBoard); - leaderboardSelectionModel.setSelected(leaderBoard, false); - } - - @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 - .getRaceColumnName(), fleet.getName(), selectedRace, - new MarkedAsyncCallback( - new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to link tracked race " + selectedRace + " to race column named " - + selectedRaceInLeaderboard.getRaceColumnName() + " of leaderboard " - + getSelectedLeaderboardName() + ": " + t.getMessage()); - trackedRacesListComposite.clearSelection(); - } - - @Override - public void onSuccess(Boolean success) { - if (success) { - // TODO consider enabling the Unlink button - selectedRaceInLeaderboard.setRaceIdentifier(fleet, selectedRace); - raceColumnAndFleetList.refresh(); - } - } - })); - } - - @Override - public StrippedLeaderboardDTO getSelectedLeaderboard() { - return leaderboardSelectionModel.getSelectedSet().isEmpty() ? null : leaderboardSelectionModel.getSelectedSet().iterator().next(); - } +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +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.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.ColumnSortList; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.Window; +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.SingleSelectionModel; +import com.sap.sailing.domain.common.RegattaIdentifier; +import com.sap.sailing.domain.common.RegattaName; +import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.gwt.ui.adminconsole.DisablableCheckboxCell.IsEnabled; +import com.sap.sailing.gwt.ui.adminconsole.RaceColumnInLeaderboardDialog.RaceColumnDescriptor; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +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.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.URLEncoder; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint; +import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.RaceLogDTO; +import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.common.Util; +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, +TrackedRaceChangedListener, LeaderboardsDisplayer { + private final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); + + private final boolean showRaceDetails; + + private Button leaderboardRemoveButton; + + private Button addRaceColumnsButton; + + private Button columnMoveUpButton; + private Button columnMoveDownButton; + + public static class AnchorCell extends AbstractCell { + + @Override + public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) { + sb.append(safeHtml); + } + } + + interface AnchorTemplates extends SafeHtmlTemplates { + @SafeHtmlTemplates.Template("{1}") + SafeHtml cell(String url, String displayName); + } + + + public LeaderboardConfigPanel(final SailingServiceAsync sailingService, RegattaRefresher regattaRefresher, + final ErrorReporter errorReporter, StringMessages theStringConstants, final boolean showRaceDetails, + LeaderboardsRefresher leaderboardsRefresher) { + super(sailingService, regattaRefresher, leaderboardsRefresher, errorReporter, theStringConstants, + new SingleSelectionModel()); + this.showRaceDetails = showRaceDetails; + leaderboardTable.ensureDebugId("LeaderboardsCellTable"); + } + + @Override + protected void addLeaderboardConfigControls(Panel configPanel) { + filterLeaderboardPanel.getTextBox().ensureDebugId("LeaderboardsFilterTextBox"); + + leaderboardRemoveButton = new Button(stringMessages.remove()); + leaderboardRemoveButton.ensureDebugId("LeaderboardsRemoveButton"); + leaderboardRemoveButton.setEnabled(false); + leaderboardRemoveButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (Window.confirm(stringMessages.doYouReallyWantToRemoveLeaderboards())) { + removeLeaderboards(leaderboardSelectionModel.getSelectedSet()); + } + } + }); + configPanel.add(leaderboardRemoveButton); + } + + @Override + protected void addColumnsToLeaderboardTable(final CellTable leaderboardTable) { + ListHandler leaderboardColumnListHandler = new ListHandler( + leaderboardList.getList()); + + 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 : "")); + return ANCHORTEMPLATE.cell(link, object.name); + } + + }; + linkColumn.setSortable(true); + leaderboardColumnListHandler.setComparator(linkColumn, new Comparator() { + + @Override + public int compare(StrippedLeaderboardDTO o1, StrippedLeaderboardDTO o2) { + boolean ascending = isSortedAscending(); + if (o1.name.equals(o2.name)) { + return 0; + } + int val = -1; + val = (o1 != null && o2 != null && ascending) ? (o1.name.compareTo(o2.name)) : -(o2.name + .compareTo(o1.name)); + + return val; + } + + private boolean isSortedAscending() { + ColumnSortList sortList = leaderboardTable.getColumnSortList(); + return sortList.size() > 0 & sortList.get(0).isAscending(); + } + }); + + TextColumn leaderboardDisplayNameColumn = new TextColumn() { + @Override + public String getValue(StrippedLeaderboardDTO leaderboard) { + return leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : ""; + } + }; + + TextColumn discardingOptionsColumn = new TextColumn() { + @Override + public String getValue(StrippedLeaderboardDTO leaderboard) { + String result = ""; + if (leaderboard.discardThresholds != null) { + for (int discardThreshold : leaderboard.discardThresholds) { + result += discardThreshold + " "; + } + } + return result; + } + }; + + TextColumn leaderboardTypeColumn = new TextColumn() { + @Override + public String getValue(StrippedLeaderboardDTO leaderboard) { + String result = leaderboard.type.isRegattaLeaderboard() ? "Regatta" : "Flexible"; + if (leaderboard.type.isMetaLeaderboard()) { + result += " , Meta"; + } + return result; + } + }; + + TextColumn scoringSystemColumn = new TextColumn() { + @Override + public String getValue(StrippedLeaderboardDTO leaderboard) { + return leaderboard.scoringScheme == null ? "" : ScoringSchemeTypeFormatter.format(leaderboard.scoringScheme, stringMessages); + } + }; + + TextColumn courseAreaColumn = new TextColumn() { + @Override + public String getValue(StrippedLeaderboardDTO leaderboard) { + return leaderboard.defaultCourseAreaId == null ? "" : leaderboard.defaultCourseAreaName; + } + }; + + ImagesBarColumn leaderboardActionColumn = new ImagesBarColumn( + new LeaderboardConfigImagesBarCell(stringMessages)); + leaderboardActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, StrippedLeaderboardDTO leaderboardDTO, String value) { + if (LeaderboardConfigImagesBarCell.ACTION_REMOVE.equals(value)) { + if (Window.confirm(stringMessages.doYouReallyWantToRemoveLeaderboard(leaderboardDTO.name))) { + removeLeaderboard(leaderboardDTO); + } + } else if (LeaderboardConfigImagesBarCell.ACTION_EDIT.equals(value)) { + final String oldLeaderboardName = leaderboardDTO.name; + List otherExistingLeaderboard = new ArrayList(); + otherExistingLeaderboard.addAll(availableLeaderboardList); + otherExistingLeaderboard.remove(leaderboardDTO); + if (leaderboardDTO.type.isMetaLeaderboard()) { + Window.alert(stringMessages.metaLeaderboardCannotBeChanged()); + } else { + if (leaderboardDTO.type.isRegattaLeaderboard()) { + LeaderboardDescriptor descriptor = new LeaderboardDescriptor(leaderboardDTO.name, + leaderboardDTO.displayName, /* scoring scheme provided by regatta */ null, + leaderboardDTO.discardThresholds, leaderboardDTO.regattaName, + leaderboardDTO.defaultCourseAreaId); + AbstractLeaderboardDialog dialog = new RegattaLeaderboardEditDialog(Collections + .unmodifiableCollection(otherExistingLeaderboard), Collections.unmodifiableCollection(allRegattas), + descriptor, stringMessages, errorReporter, + new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(LeaderboardDescriptor result) { + updateLeaderboard(oldLeaderboardName, result); + } + }); + dialog.show(); + } else { + LeaderboardDescriptor descriptor = new LeaderboardDescriptor(leaderboardDTO.name, leaderboardDTO.displayName, leaderboardDTO.scoringScheme, leaderboardDTO.discardThresholds, leaderboardDTO.defaultCourseAreaId); + openUpdateFlexibleLeaderboardDialog(leaderboardDTO, otherExistingLeaderboard, leaderboardDTO.name, descriptor); + } + } + } 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); + } else if (LeaderboardConfigImagesBarCell.ACTION_EDIT_COMPETITORS.equals(value)) { + EditCompetitorsDialog editCompetitorsDialog = new EditCompetitorsDialog(sailingService, leaderboardDTO.name, stringMessages, + errorReporter, new DialogCallback>() { + @Override + public void cancel() { + } + + @Override + public void ok(final List result) { + } + }); + editCompetitorsDialog.show(); + + } 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); + } + } + }); + leaderboardTable.addColumn(linkColumn, stringMessages.name()); + leaderboardTable.addColumn(leaderboardDisplayNameColumn, stringMessages.displayName()); + leaderboardTable.addColumn(discardingOptionsColumn, stringMessages.discarding()); + leaderboardTable.addColumn(leaderboardTypeColumn, stringMessages.type()); + leaderboardTable.addColumn(scoringSystemColumn, stringMessages.scoringSystem()); + leaderboardTable.addColumn(courseAreaColumn, stringMessages.courseArea()); + leaderboardTable.addColumn(leaderboardActionColumn, stringMessages.actions()); + leaderboardTable.addColumnSortHandler(leaderboardColumnListHandler); + } + + protected void addLeaderboardCreateControls(Panel createPanel) { + Button createFlexibleLeaderboardBtn = new Button(stringMessages.createFlexibleLeaderboard() + "..."); + createFlexibleLeaderboardBtn.ensureDebugId("CreateFlexibleLeaderboardButton"); + createPanel.add(createFlexibleLeaderboardBtn); + createFlexibleLeaderboardBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent clickEvent) { + createFlexibleLeaderboard(); + } + }); + + Button createRegattaLeaderboardBtn = new Button(stringMessages.createRegattaLeaderboard() + "..."); + createRegattaLeaderboardBtn.ensureDebugId("CreateRegattaLeaderboardButton"); + createPanel.add(createRegattaLeaderboardBtn); + createRegattaLeaderboardBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent clickEvent) { + createRegattaLeaderboard(); + } + }); + } + + @Override + protected void addColumnsToRacesTable(CellTable racesTable) { + TextColumn explicitFactorColumn = new TextColumn() { + @Override + public String getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality object) { + return object.getA().getExplicitFactor() == null ? "" : object.getA().getExplicitFactor().toString(); + } + }; + + Column isMedalRaceCheckboxColumn = new Column( + new DisablableCheckboxCell(new IsEnabled() { + @Override + public boolean isEnabled() { + return getSelectedLeaderboard() != null && !getSelectedLeaderboard().type.isRegattaLeaderboard(); + + } + })) { + @Override + public Boolean getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality race) { + return race.getA().isMedalRace(); + } + }; + isMedalRaceCheckboxColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, RaceColumnDTOAndFleetDTOWithNameBasedEquality object, Boolean value) { + setIsMedalRace(getSelectedLeaderboard().name, object.getA(), value); + } + }); + isMedalRaceCheckboxColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); + + TextColumn isLinkedRaceColumn = new TextColumn() { + @Override + public String getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnAndFleetName) { + boolean isTrackedRace = raceColumnAndFleetName.getA().isTrackedRace(raceColumnAndFleetName.getB()); + return isTrackedRace ? stringMessages.yes() : stringMessages.no(); + } + }; + + ImagesBarColumn raceActionColumn = + new ImagesBarColumn( + new LeaderboardRaceConfigImagesBarCell(this, stringMessages)); + raceActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, RaceColumnDTOAndFleetDTOWithNameBasedEquality object, String value) { + if (LeaderboardRaceConfigImagesBarCell.ACTION_REMOVE.equals(value)) { + if (Window.confirm(stringMessages.reallyRemoveRace(object.getA().getRaceColumnName()))) { + removeRaceColumn(object.getA()); + } + } else if (LeaderboardRaceConfigImagesBarCell.ACTION_EDIT.equals(value)) { + editRaceColumnOfLeaderboard(object); + } else if (LeaderboardRaceConfigImagesBarCell.ACTION_UNLINK.equals(value)) { + unlinkRaceColumnFromTrackedRace(object.getA().getRaceColumnName(), object.getB()); + } else if (LeaderboardRaceConfigImagesBarCell.ACTION_REFRESH_RACELOG.equals(value)) { + refreshRaceLog(object.getA(), object.getB(), true); + } else if(LeaderboardRaceConfigImagesBarCell.ACTION_SET_STARTTIME.equals(value)) { + setStartTime(object.getA(), object.getB()); + } else if(LeaderboardRaceConfigImagesBarCell.ACTION_SHOW_RACELOG.equals(value)) { + showRaceLog(object.getA(), object.getB()); + } + } + }); + + racesTable.addColumn(isMedalRaceCheckboxColumn, stringMessages.medalRace()); + racesTable.addColumn(isLinkedRaceColumn, stringMessages.islinked()); + racesTable.addColumn(explicitFactorColumn, stringMessages.factor()); + racesTable.addColumn(raceActionColumn, stringMessages.actions()); + + racesTable.ensureDebugId("RacesCellTable"); + } + + @Override + protected void addSelectedLeaderboardRacesControls(Panel racesPanel) { + addRaceColumnsButton = new Button(stringMessages.actionAddRaces() + "..."); + addRaceColumnsButton.ensureDebugId("AddRacesButton"); + racesPanel.add(addRaceColumnsButton); + addRaceColumnsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (getSelectedLeaderboard().type.isRegattaLeaderboard()) { + Window.alert(stringMessages.cannotAddRacesToRegattaLeaderboardButOnlyToRegatta()); + } else { + addRaceColumnsToLeaderboard(); + } + } + }); + racesPanel.add(addRaceColumnsButton); + + columnMoveUpButton = new Button(stringMessages.columnMoveUp()); + racesPanel.add(columnMoveUpButton); + columnMoveUpButton.ensureDebugId("MoveRaceUpButton"); + columnMoveUpButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + moveSelectedRaceColumnUp(); + } + }); + racesPanel.add(columnMoveUpButton); + + columnMoveDownButton = new Button(stringMessages.columnMoveDown()); + racesPanel.add(columnMoveDownButton); + columnMoveDownButton.ensureDebugId("MoveRaceDownButton"); + columnMoveDownButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + moveSelectedRaceColumnDown(); + } + }); + racesPanel.add(columnMoveDownButton); + } + + protected void openUpdateFlexibleLeaderboardDialog(final StrippedLeaderboardDTO leaderboardDTO, final List otherExistingLeaderboard, + final String oldLeaderboardName, final LeaderboardDescriptor descriptor) { + sailingService.getEvents(new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onSuccess(List result) { + openUpdateFlexibleLeaderboardDialog(leaderboardDTO, otherExistingLeaderboard, oldLeaderboardName, + descriptor, result); + } + + @Override + public void onFailure(Throwable caught) { + openUpdateFlexibleLeaderboardDialog(leaderboardDTO, otherExistingLeaderboard, oldLeaderboardName, + descriptor, new ArrayList()); + } + })); + } + + protected void openUpdateFlexibleLeaderboardDialog(StrippedLeaderboardDTO leaderboardDTO, List otherExistingLeaderboard, + final String oldLeaderboardName, LeaderboardDescriptor descriptor, List existingEvents) { + FlexibleLeaderboardEditDialog dialog = new FlexibleLeaderboardEditDialog( + Collections.unmodifiableCollection(otherExistingLeaderboard), descriptor, stringMessages, + Collections.unmodifiableList(existingEvents), errorReporter, + new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(LeaderboardDescriptor result) { + updateLeaderboard(oldLeaderboardName, result); + } + }); + dialog.show(); + } + + /** + * Allow the user to combine the various URL parameters that exist for the {@link LeaderboardEntryPoint} and obtain the + * resulting URL in a link. The link's reference target is updated dynamically as the user adjusts the settings. Therefore, + * the link can be clicked, bookmarked or copied to the clipboard at any time. The OK / Cancel actions for the dialog shown + * are no-ops. + */ + private void openLeaderboardUrlConfigDialog(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages) { + LeaderboardEntryPoint.getUrlConfigurationDialog(leaderboard, stringMessages).show(); + } + + private void setStartTime(RaceColumnDTO raceColumnDTO, FleetDTO fleetDTO) { + new SetStartTimeDialog(sailingService, errorReporter, getSelectedLeaderboardName(), raceColumnDTO.getName(), + fleetDTO.getName(), stringMessages, new DialogCallback() { + @Override + public void ok(RaceLogSetStartTimeAndProcedureDTO editedObject) { + sailingService.setStartTimeAndProcedure(editedObject, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage()); + } + + @Override + public void onSuccess(Boolean result) { + if (!result) { + Window.alert(stringMessages.failedToSetNewStartTime()); + } + } + }); + } + + @Override + public void cancel() { } + }).show(); + } + + private void showRaceLog(final RaceColumnDTO raceColumnDTO, final FleetDTO fleetDTO) { + final String selectedLeaderboardName = getSelectedLeaderboardName(); + sailingService.getRaceLog(selectedLeaderboardName, raceColumnDTO, fleetDTO, + new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage(), true); + } + @Override + public void onSuccess(RaceLogDTO result) { + openRaceLogDialog(result); + } + })); + } + + private void openRaceLogDialog(RaceLogDTO raceLogDTO) { + RaceLogDialog dialog = new RaceLogDialog(raceLogDTO, stringMessages, new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(RaceLogDTO result) { + } + }); + dialog.show(); + } + + private void removeRaceColumn(final RaceColumnDTO raceColumnDTO) { + final String selectedLeaderboardName = getSelectedLeaderboardName(); + final String raceColumnString = raceColumnDTO.getRaceColumnName(); + sailingService.removeLeaderboardColumn(getSelectedLeaderboardName(), raceColumnString, + new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to remove leaderboard race column " + raceColumnDTO + + " in leaderboard " + getSelectedLeaderboardName() + ": " + t.getMessage()); + } + + @Override + public void onSuccess(Void arg0) { + loadAndRefreshLeaderboard(selectedLeaderboardName, /* raceColumnNameToSelect */ null); + } + })); + } + + /** + * The selected row is potentially only one of several fleet-based rows of the same RaceColumn. In this case, + * move all fleet-based rows of the same RaceColumn down. + */ + private void moveSelectedRaceColumnDown() { + final String selectedLeaderboardName = getSelectedLeaderboardName(); + final String selectedRaceColumnName = getSelectedRaceColumnWithFleet().getA().getRaceColumnName(); + sailingService.moveLeaderboardColumnDown(getSelectedLeaderboardName(), selectedRaceColumnName, + new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to move leaderboard race column " + + selectedRaceColumnName + " in leaderboard " + getSelectedLeaderboardName() + + " down: " + caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + loadAndRefreshLeaderboard(selectedLeaderboardName, selectedRaceColumnName); + } + })); + } + + /** + * The selected row is potentially only one of several fleet-based rows of the same RaceColumn. In this case, + * move all fleet-based rows of the same RaceColumn down. + */ + private void moveSelectedRaceColumnUp() { + final String selectedLeaderboardName = getSelectedLeaderboardName(); + final String selectedRaceColumnName = getSelectedRaceColumnWithFleet().getA().getRaceColumnName(); + sailingService.moveLeaderboardColumnUp(getSelectedLeaderboardName(), selectedRaceColumnName, + new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to move leaderboard race column " + + selectedRaceColumnName + " in leaderboard " + getSelectedLeaderboardName() + " up: " + + caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + loadAndRefreshLeaderboard(selectedLeaderboardName, selectedRaceColumnName); + } + })); + } + + @Override + protected void leaderboardRaceColumnSelectionChanged() { + selectedRaceInLeaderboard = getSelectedRaceColumnWithFleet(); + if (selectedRaceInLeaderboard != null) { + columnMoveUpButton.setEnabled(true); + columnMoveDownButton.setEnabled(true); + selectTrackedRaceInRaceList(); + } else { + columnMoveUpButton.setEnabled(false); + columnMoveDownButton.setEnabled(false); + trackedRacesListComposite.clearSelection(); + } + } + + private void editRaceColumnOfLeaderboard(final RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnWithFleet) { + final String selectedLeaderboardName = getSelectedLeaderboardName(); + final boolean oldIsMedalRace = raceColumnWithFleet.getA().isMedalRace(); + final String oldRaceColumnName = raceColumnWithFleet.getA().getRaceColumnName(); + final Double oldExplicitFactor = raceColumnWithFleet.getA().getExplicitFactor(); + // use a set to avoid duplicates in the case of regatta leaderboards with multiple fleets per column + Set existingRacesWithoutThisRace = new HashSet(); + for (RaceColumnDTOAndFleetDTOWithNameBasedEquality pair : raceColumnTable.getDataProvider().getList()) { + existingRacesWithoutThisRace.add(pair.getA()); + } + existingRacesWithoutThisRace.remove(raceColumnWithFleet.getA()); + final RaceColumnInLeaderboardDialog raceDialog = new RaceColumnInLeaderboardDialog(existingRacesWithoutThisRace, + raceColumnWithFleet.getA(), getSelectedLeaderboard().type.isRegattaLeaderboard(), stringMessages, new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(final RaceColumnDescriptor result) { + boolean rename = !oldRaceColumnName.equals(result.getName()); + boolean updateIsMedalRace = oldIsMedalRace != result.isMedalRace(); + boolean updateFactor = oldExplicitFactor != result.getExplicitFactor(); + List> callbacks = new ArrayList>(); + final ParallelExecutionCallback renameLeaderboardColumnCallback = new ParallelExecutionCallback(); + if (rename) { + callbacks.add(renameLeaderboardColumnCallback); + } + final ParallelExecutionCallback updateIsMedalRaceCallback = new ParallelExecutionCallback(); + if (updateIsMedalRace) { + callbacks.add(updateIsMedalRaceCallback); + } + final ParallelExecutionCallback updateLeaderboardColumnFactorCallback = new ParallelExecutionCallback(); + if (updateFactor) { + callbacks.add(updateLeaderboardColumnFactorCallback); + } + new ParallelExecutionHolder(callbacks.toArray(new ParallelExecutionCallback[0])) { + @Override + public void handleSuccess() { + loadAndRefreshLeaderboard(selectedLeaderboardName, result.getName()); + } + @Override + public void handleFailure(Throwable t) { + errorReporter.reportError("Error trying to update data of race column " + + oldRaceColumnName + " in leaderboard " + selectedLeaderboardName + ": " + + t.getMessage()); + } + }; + if (rename) { + sailingService.renameLeaderboardColumn(selectedLeaderboardName, oldRaceColumnName, + result.getName(), renameLeaderboardColumnCallback); + } + if (updateIsMedalRace) { + sailingService.updateIsMedalRace(selectedLeaderboardName, result.getName(), + result.isMedalRace(), updateIsMedalRaceCallback); + } + if (updateFactor) { + sailingService.updateLeaderboardColumnFactor(selectedLeaderboardName, result.getName(), + result.getExplicitFactor(), updateLeaderboardColumnFactorCallback); + } + } + }); + raceDialog.show(); + } + + private void setIsMedalRace(String leaderboardName, final RaceColumnDTO raceInLeaderboard, + final boolean isMedalRace) { + sailingService.updateIsMedalRace(leaderboardName, raceInLeaderboard.getRaceColumnName(), isMedalRace, + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorUpdatingIsMedalRace(caught.getMessage())); + } + + @Override + public void onSuccess(Void result) { + getSelectedLeaderboard().setIsMedalRace(raceInLeaderboard.getRaceColumnName(), isMedalRace); + } + }); + } + + private void addRaceColumnsToLeaderboard() { + final String leaderboardName = getSelectedLeaderboardName(); + final List existingRaceColumns = new ArrayList(); + for (RaceColumnDTOAndFleetDTOWithNameBasedEquality pair : raceColumnTable.getDataProvider().getList()) { + existingRaceColumns.add(pair.getA()); + } + final RaceColumnsInLeaderboardDialog raceDialog = new RaceColumnsInLeaderboardDialog(existingRaceColumns, + stringMessages, new DialogCallback>() { + @Override + public void cancel() { + } + + @Override + public void ok(final List result) { + updateRaceColumnsOfLeaderboard(leaderboardName, existingRaceColumns, result); + } + }); + raceDialog.ensureDebugId("RaceColumnsInLeaderboardDialog"); + raceDialog.show(); + } + + private void updateRaceColumnsOfLeaderboard(final String leaderboardName, List existingRaceColumns, List newRaceColumns) { + final List> raceColumnsToAdd = new ArrayList>(); + + for (RaceColumnDTO newRaceColumn : newRaceColumns) { + if (!existingRaceColumns.contains(newRaceColumn)) { + raceColumnsToAdd.add(new Util.Pair(newRaceColumn.getName(), newRaceColumn.isMedalRace())); + } + } + + sailingService.addColumnsToLeaderboard(leaderboardName, raceColumnsToAdd, new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to add race columns to leaderboard " + leaderboardName + + ": " + caught.getMessage()); + } + + @Override + public void onSuccess(Void v) { + loadAndRefreshLeaderboard(leaderboardName, /* nameOfRaceColumnToSelect */ null); + } + })); + } + + @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) { + raceColumnTable.getDataProvider().getList().clear(); + for (RaceColumnDTO raceColumn : selectedLeaderboard.getRaceList()) { + for (FleetDTO fleet : raceColumn.getFleets()) { + raceColumnTable.getDataProvider().getList().add(new RaceColumnDTOAndFleetDTOWithNameBasedEquality(raceColumn, fleet)); + } + } + selectedLeaderBoardPanel.setVisible(true); + selectedLeaderBoardPanel.setCaptionText("Details of leaderboard '" + selectedLeaderboard.name + "'"); + if (!selectedLeaderboard.type.isMetaLeaderboard()) { + trackedRacesCaptionPanel.setVisible(true); + } + addRaceColumnsButton.setVisible(!selectedLeaderboard.type.isRegattaLeaderboard()); + columnMoveUpButton.setVisible(!selectedLeaderboard.type.isRegattaLeaderboard()); + columnMoveDownButton.setVisible(!selectedLeaderboard.type.isRegattaLeaderboard()); + } else { + selectedLeaderBoardPanel.setVisible(false); + trackedRacesCaptionPanel.setVisible(false); + selectedRaceInLeaderboard = null; + } + } + + private void createFlexibleLeaderboard() { + sailingService.getEvents(new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onSuccess(List result) { + createFlexibleLeaderboard(result); + } + + @Override + public void onFailure(Throwable caught) { + createFlexibleLeaderboard(new ArrayList()); + } + })); + } + + private void createFlexibleLeaderboard(List existingEvents) { + + AbstractLeaderboardDialog dialog = new FlexibleLeaderboardCreateDialog(Collections.unmodifiableCollection(availableLeaderboardList), + stringMessages, Collections.unmodifiableCollection(existingEvents), errorReporter, new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(final LeaderboardDescriptor newLeaderboard) { + sailingService.createFlexibleLeaderboard(newLeaderboard.getName(), newLeaderboard.getDisplayName(), + newLeaderboard.getDiscardThresholds(), newLeaderboard.getScoringScheme(), newLeaderboard.getCourseAreaId(), + new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to create the new flexible leaderboard " + newLeaderboard.getName() + + ": " + t.getMessage()); + } + + @Override + public void onSuccess(StrippedLeaderboardDTO result) { + addLeaderboard(result); + } + })); + } + }); + dialog.ensureDebugId("FlexibleLeaderboardCreateDialog"); + dialog.show(); + } + + private void createRegattaLeaderboard() { + RegattaLeaderboardCreateDialog dialog = new RegattaLeaderboardCreateDialog(Collections.unmodifiableCollection(availableLeaderboardList), + Collections.unmodifiableCollection(allRegattas), stringMessages, errorReporter, new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(final LeaderboardDescriptor newLeaderboard) { + RegattaIdentifier regattaIdentifier = new RegattaName(newLeaderboard.getRegattaName()); + sailingService.createRegattaLeaderboard(regattaIdentifier, newLeaderboard.getDisplayName(), newLeaderboard.getDiscardThresholds(), + new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to create the new regatta leaderboard " + newLeaderboard.getName() + + ": " + t.getMessage()); + } + + @Override + public void onSuccess(StrippedLeaderboardDTO result) { + addLeaderboard(result); + } + }); + } + }); + dialog.ensureDebugId("RegattaLeaderboardCreateDialog"); + dialog.show(); + } + + private void addLeaderboard(StrippedLeaderboardDTO result) { + leaderboardList.getList().add(result); + availableLeaderboardList.add(result); + leaderboardSelectionModel.clear(); + leaderboardSelectionModel.setSelected(result, true); + } + + private void updateLeaderboard(final String oldLeaderboardName, final LeaderboardDescriptor leaderboardToUpdate) { + sailingService.updateLeaderboard(oldLeaderboardName, leaderboardToUpdate.getName(), leaderboardToUpdate.getDisplayName(), + leaderboardToUpdate.getDiscardThresholds(), leaderboardToUpdate.getCourseAreaId(), new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to update leaderboard " + oldLeaderboardName + ": " + + t.getMessage()); + } + + @Override + public void onSuccess(StrippedLeaderboardDTO updatedLeaderboard) { + int indexOfLeaderboard = 0; + for (int i = 0; i < leaderboardList.getList().size(); i++) { + StrippedLeaderboardDTO dao = leaderboardList.getList().get(i); + if (dao.name.equals(oldLeaderboardName)) { + indexOfLeaderboard = i; + break; + } + } + leaderboardList.getList().set(indexOfLeaderboard, updatedLeaderboard); + leaderboardList.refresh(); + } + }); + } + + private void removeLeaderboards(final Collection leaderboards) { + if (!leaderboards.isEmpty()) { + Set leaderboardNames = new HashSet(); + for (StrippedLeaderboardDTO leaderboard : leaderboards) { + leaderboardNames.add(leaderboard.name); + } + sailingService.removeLeaderboards(leaderboardNames, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to remove the leaderboards:" + caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + for (StrippedLeaderboardDTO leaderboard : leaderboards) { + removeLeaderboardFromTable(leaderboard); + } + getLeaderboardsRefresher().updateLeaderboards(availableLeaderboardList, LeaderboardConfigPanel.this); + } + }); + } + } + + private void removeLeaderboard(final StrippedLeaderboardDTO leaderBoard) { + sailingService.removeLeaderboard(leaderBoard.name, new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to remove leaderboard " + leaderBoard.name + ": " + + caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + removeLeaderboardFromTable(leaderBoard); + getLeaderboardsRefresher().updateLeaderboards(availableLeaderboardList, LeaderboardConfigPanel.this); + } + })); + } + + private void removeLeaderboardFromTable(final StrippedLeaderboardDTO leaderBoard) { + leaderboardList.getList().remove(leaderBoard); + availableLeaderboardList.remove(leaderBoard); + leaderboardSelectionModel.setSelected(leaderBoard, false); + } } \ No newline at end of file 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 ae71bb48158..7ad46e47426 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 @@ -38,21 +38,25 @@ import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell; import com.sap.sailing.gwt.ui.adminconsole.LeaderboardGroupDialog.LeaderboardGroupDescriptor; import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel; import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.LeaderboardGroupRefresher; +import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer; +import com.sap.sailing.gwt.ui.client.LeaderboardGroupsRefresher; +import com.sap.sailing.gwt.ui.client.LeaderboardsDisplayer; +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.URLEncoder; -import com.sap.sailing.gwt.ui.client.shared.panels.LabeledAbstractFilterablePanel; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; -public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements LeaderboardGroupRefresher { +public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements LeaderboardGroupsDisplayer, LeaderboardsDisplayer { interface AnchorTemplates extends SafeHtmlTemplates { @SafeHtmlTemplates.Template("{1}") @@ -95,13 +99,18 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private ArrayList availableLeaderboardGroups; private ArrayList availableLeaderboards; + private final LeaderboardGroupsRefresher leaderboardGroupsRefresher; + private final LeaderboardsRefresher leaderboardsRefresher; public LeaderboardGroupConfigPanel(SailingServiceAsync sailingService, RegattaRefresher regattaRefresher, + LeaderboardGroupsRefresher leaderboardGroupsRefresher, LeaderboardsRefresher leaderboardsRefresher, ErrorReporter errorReporter, StringMessages stringMessages) { super(sailingService, regattaRefresher, errorReporter, stringMessages); AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); availableLeaderboardGroups = new ArrayList(); availableLeaderboards = new ArrayList(); + this.leaderboardGroupsRefresher = leaderboardGroupsRefresher; + this.leaderboardsRefresher = leaderboardsRefresher; //Build GUI mainPanel = new VerticalPanel(); @@ -124,8 +133,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements splitPanel.add(createLeaderboardsGUI(tableRes)); //Load Data - fillLeaderboardGroups(); - loadLeaderboards(); + leaderboardGroupsRefresher.fillLeaderboardGroups(); + leaderboardsRefresher.fillLeaderboards(); } private Widget createSwitchLeaderboardsGUI() { @@ -159,7 +168,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements } private Widget createLeaderboardsGUI(Resources tableRes) { - CaptionPanel leaderboardsCaptionPanel = new CaptionPanel(stringMessages.leaderboards()); + CaptionPanel leaderboardsCaptionPanel = new CaptionPanel(stringMessages.leaderboardsExceptFromSelectedGroup()); leaderboardsCaptionPanel.setWidth("95%"); VerticalPanel leaderboardsPanel = new VerticalPanel(); @@ -397,7 +406,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements VerticalPanel leaderboardsGroupPanel = new VerticalPanel(); leaderboardGroupsCaptionPanel.add(leaderboardsGroupPanel); - //Create functional elements for the leaderboard groups + // Create functional elements for the leaderboard groups HorizontalPanel leaderboardGroupsFunctionPanel = new HorizontalPanel(); leaderboardGroupsFunctionPanel.setSpacing(5); leaderboardsGroupPanel.add(leaderboardGroupsFunctionPanel); @@ -448,7 +457,16 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements } }); leaderboardGroupsFunctionPanel.add(removeButton); - + Button refreshButton = new Button(stringMessages.refresh()); + refreshButton.ensureDebugId("RefreshLeaderboardGroupsButton"); + refreshButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + leaderboardGroupsRefresher.fillLeaderboardGroups(); + leaderboardsRefresher.fillLeaderboards(); + } + }); + leaderboardGroupsFunctionPanel.add(refreshButton); AnchorCell anchorCell = new AnchorCell(); Column groupNameColumn = new Column(anchorCell) { @Override @@ -540,43 +558,22 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements return leaderboardGroupsCaptionPanel; } - public void fillLeaderboardGroups() { - sailingService.getLeaderboardGroups(false /*withGeoLocationData*/, - new MarkedAsyncCallback>( - new AsyncCallback>() { - @Override - public void onSuccess(List groups) { - availableLeaderboardGroups.clear(); - if (groups != null) { - availableLeaderboardGroups.addAll(groups); - } - groupsProvider.getList().clear(); - groupsProvider.getList().addAll(availableLeaderboardGroups); - } - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to obtain list of leaderboard groups: " + t.getMessage()); - } - })); + @Override + public void fillLeaderboardGroups(Iterable groups) { + availableLeaderboardGroups.clear(); + if (groups != null) { + Util.addAll(groups, availableLeaderboardGroups); + } + groupsFilterablePanel.updateAll(availableLeaderboardGroups); } - private void loadLeaderboards() { - sailingService.getLeaderboards(new MarkedAsyncCallback>( - new AsyncCallback>() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to obtain list of leaderboards: " + t.getMessage()); - } - @Override - public void onSuccess(List leaderboards) { - availableLeaderboards.clear(); - if (leaderboards != null) { - availableLeaderboards.addAll(leaderboards); - } - leaderboardsProvider.getList().clear(); - leaderboardsProvider.getList().addAll(availableLeaderboards); - } - })); + @Override + public void fillLeaderboards(Iterable leaderboards) { + availableLeaderboards.clear(); + if (leaderboards != null) { + Util.addAll(leaderboards, availableLeaderboards); + } + leaderboardsFilterablePanel.updateAll(availableLeaderboards); } /** @@ -643,9 +640,10 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements @Override public void onSuccess(LeaderboardGroupDTO newGroup) { availableLeaderboardGroups.add(newGroup); - groupsProvider.getList().add(newGroup); + groupsFilterablePanel.updateAll(availableLeaderboardGroups); groupsSelectionModel.clear(); groupsSelectionModel.setSelected(newGroup, true); + leaderboardGroupsRefresher.updateLeaderboardGroups(availableLeaderboardGroups, LeaderboardGroupConfigPanel.this); } })); } @@ -682,6 +680,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements } } } + groupsFilterablePanel.updateAll(availableLeaderboardGroups); + leaderboardGroupsRefresher.updateLeaderboardGroups(availableLeaderboardGroups, LeaderboardGroupConfigPanel.this); groupsProvider.refresh(); } })); @@ -727,6 +727,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements public void onSuccess(Void result) { for (LeaderboardGroupDTO group : groups) { removeGroupFromTable(group); + leaderboardGroupsRefresher.updateLeaderboardGroups(availableLeaderboardGroups, LeaderboardGroupConfigPanel.this); } } })); @@ -746,13 +747,14 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements @Override public void onSuccess(Void v) { removeGroupFromTable(group); + leaderboardGroupsRefresher.updateLeaderboardGroups(availableLeaderboardGroups, LeaderboardGroupConfigPanel.this); } })); } private void removeGroupFromTable(final LeaderboardGroupDTO group) { availableLeaderboardGroups.remove(group); - groupsProvider.getList().remove(group); + groupsFilterablePanel.updateAll(availableLeaderboardGroups); groupsSelectionModel.setSelected(group, false); } @@ -773,12 +775,11 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements groupDetailsProvider.getList().clear(); groupDetailsProvider.getList().addAll(selectedGroup.leaderboards); - //Reload available leaderboards and remove leaderboards of the group from the list + // Tell leaderboards filter panel to use all available leaderboards except the leaderboards of the group from the list leaderboardsSelectionModel.clear(); - leaderboardsFilterablePanel.getTextBox().setText(""); - leaderboardsProvider.getList().clear(); - leaderboardsProvider.getList().addAll(availableLeaderboards); - leaderboardsProvider.getList().removeAll(selectedGroup.leaderboards); + ArrayList allLeaderboardsExceptThoseOfSelectedLeaderboardGroup = new ArrayList<>(availableLeaderboards); + allLeaderboardsExceptThoseOfSelectedLeaderboardGroup.removeAll(selectedGroup.leaderboards); + leaderboardsFilterablePanel.updateAll(allLeaderboardsExceptThoseOfSelectedLeaderboardGroup); } } @@ -891,5 +892,4 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private LeaderboardGroupDTO getSelectedGroup() { return groupsSelectionModel.getSelectedSet().isEmpty() ? null : groupsSelectionModel.getSelectedSet().iterator().next(); } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupCreateDialog.java index 39d3e582216..713af2506dd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupCreateDialog.java @@ -9,7 +9,7 @@ public class LeaderboardGroupCreateDialog extends LeaderboardGroupDialog { public LeaderboardGroupCreateDialog(Collection existingGroups, StringMessages stringMessages, DialogCallback callback) { - super(new LeaderboardGroupDTO(), stringMessages, + super(new LeaderboardGroupDTO(/* ID */ null), stringMessages, callback, existingGroups); nameEntryField = createTextBox(null, 50); nameEntryField.ensureDebugId("NameTextBox"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MarkEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MarkEditDialog.java new file mode 100644 index 00000000000..ca7d454bbb1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MarkEditDialog.java @@ -0,0 +1,113 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.MarkType; +import com.sap.sailing.domain.common.impl.RGBColor; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.MarkDTO; +import com.sap.sailing.gwt.ui.shared.racemap.Pattern; +import com.sap.sailing.gwt.ui.shared.racemap.Shape; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class MarkEditDialog extends DataEntryDialog { + private final MarkDTO markToEdit; //color, shape, pattern, type + private final TextBox name; + private final ListBox shape; + private final ListBox pattern; + private final ListBox type; + private final TextBox color; + private final StringMessages stringMessages; + + public MarkEditDialog(final StringMessages stringMessages, MarkDTO markToEdit, boolean isNewMark, + DialogCallback callback) { + super(isNewMark ? stringMessages.add(stringMessages.mark()) : stringMessages.edit(stringMessages.mark()), + isNewMark ? stringMessages.add(stringMessages.mark()) : stringMessages.edit(stringMessages.mark()), + stringMessages.ok(), stringMessages.cancel(), new DataEntryDialog.Validator() { + @Override + public String getErrorMessage(MarkDTO valueToValidate) { + String result = null; + if (valueToValidate.getName() == null || valueToValidate.getName().isEmpty()) { + result = stringMessages.pleaseEnterAName(); + } else if (valueToValidate.color != null && ! valueToValidate.color.isEmpty()) { + try { + new RGBColor(valueToValidate.color); + } catch (IllegalArgumentException iae) { + result = valueToValidate.color; + } + } + return result; + } + }, /* animationEnabled */true, callback); + this.stringMessages = stringMessages; + this.markToEdit = markToEdit; + + this.name = createTextBox(markToEdit.getName()); + this.shape = createAndSelectListBox(Shape.values(), markToEdit.shape, true); + this.type = createAndSelectListBox(MarkType.values(), markToEdit.type == null ? null : markToEdit.type.name(), false); + this.color = createTextBox(markToEdit.color); + this.pattern = createAndSelectListBox(Pattern.values(), markToEdit.pattern, true); + } + + private > ListBox createAndSelectListBox(T[] values, String toSelect, boolean emptySelectionPossible) { + ListBox result = createListBox(/* isMultipleSelect */ false); + int i=0; + List list = new ArrayList(); + for (T t : values) { + list.add(t.name()); + } + if (emptySelectionPossible) { + list.add(""); + } + Collections.sort(list); + for (String name : list) { + result.addItem(name); + if (name.equals(toSelect)) { + result.setSelectedIndex(i); + } + i++; + } + return result; + } + + + @Override + public void show() { + super.show(); + name.setFocus(true); + } + + @Override + protected MarkDTO getResult() { + MarkDTO result = new MarkDTO(markToEdit.getIdAsString(), name.getText()); + result.shape = shape.getItemText(shape.getSelectedIndex()); + result.type = MarkType.valueOf(type.getItemText(type.getSelectedIndex())); + result.color = color.getText(); + result.pattern = pattern.getItemText(pattern.getSelectedIndex()); + return result; + } + + @Override + protected Widget getAdditionalWidget() { + Grid result = new Grid(5, 2); + result.setWidget(0, 0, new Label(stringMessages.name())); + result.setWidget(0, 1, name); + result.setWidget(1, 0, new Label(stringMessages.shape())); + result.setWidget(1, 1, shape); + result.setWidget(2, 0, new Label(stringMessages.type())); + result.setWidget(2, 1, type); + result.setWidget(3, 0, new Label(stringMessages.color())); + result.setWidget(3, 1, color); + result.setWidget(4, 0, new Label(stringMessages.pattern())); + result.setWidget(4, 1, pattern); + return result; + } + +} 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 new file mode 100644 index 00000000000..badf06600c3 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MarkTableWrapper.java @@ -0,0 +1,110 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; + +import com.google.gwt.cell.client.SafeHtmlCell; +import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.view.client.SelectionModel; +import com.sap.sailing.domain.common.Color; +import com.sap.sailing.domain.common.impl.RGBColor; +import com.sap.sailing.gwt.ui.adminconsole.ColorColumn.ColorRetriever; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.MarkDTO; + +public class MarkTableWrapper> extends TableWrapper { + public MarkTableWrapper(S selectionModel, SailingServiceAsync sailingService, StringMessages stringMessages, + ErrorReporter errorReporter) { + super(sailingService, stringMessages, errorReporter, selectionModel); + + mainPanel.add(table); + + TextColumn markNameColumn = new TextColumn() { + @Override + public String getValue(MarkDTO markDTO) { + return markDTO.getName(); + } + }; + table.addColumn(markNameColumn, stringMessages.mark()); + + final SafeHtmlCell markPositionCell = new SafeHtmlCell(); + Column markPositionColumn = new Column(markPositionCell) { + @Override + public SafeHtml getValue(MarkDTO mark) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + if(mark.position != null) { + NumberFormat fmt = NumberFormat.getFormat("#.###"); + builder.appendEscaped(fmt.format(mark.position.latDeg)+", "+fmt.format(mark.position.lngDeg)); + } + return builder.toSafeHtml(); + } + }; + table.addColumn(markPositionColumn, stringMessages.position()); + + Column markColorColumn = new ColorColumn<>(new ColorRetriever() { + @Override + public Color getColor(MarkDTO t) { + return t.color != null && ! t.color.isEmpty() ? new RGBColor(t.color) : null; + } + }); + table.addColumn(markColorColumn, stringMessages.color()); + + TextColumn markShapeColumn = new TextColumn() { + @Override + public String getValue(MarkDTO markDTO) { + return markDTO.shape != null ? markDTO.shape : ""; + } + }; + table.addColumn(markShapeColumn, stringMessages.shape()); + + TextColumn markPatternColumn = new TextColumn() { + @Override + public String getValue(MarkDTO markDTO) { + return markDTO.pattern != null ? markDTO.pattern : ""; + } + }; + table.addColumn(markPatternColumn, stringMessages.pattern()); + + TextColumn markUUIDColumn = new TextColumn() { + @Override + public String getValue(MarkDTO markDTO) { + return markDTO.getIdAsString(); + } + }; + table.addColumn(markUUIDColumn, "UUID"); + } + + public void refresh(Collection marks) { + dataProvider.getList().clear(); + dataProvider.getList().addAll(marks); + dataProvider.flush(); + Collections.sort(dataProvider.getList(), new Comparator() { + @Override + public int compare(MarkDTO o1, MarkDTO o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + } + + public void refresh(String leaderboardName, String raceColumnName, String fleetName) { + sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + refresh(result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load marks: " + caught.getMessage()); + } + }); + } +} 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 7dddbeb9e95..5de53e1016d 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 @@ -24,11 +24,11 @@ import com.google.gwt.view.client.SelectionModel; import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.media.MediaUtil; -import com.sap.sailing.gwt.ui.client.media.NewMediaDialog; -import com.sap.sailing.gwt.ui.client.media.TimeFormatUtil; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.media.NewMediaDialog; +import com.sap.sailing.gwt.ui.client.media.TimeFormatUtil; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PositionEntryDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PositionEntryDialog.java new file mode 100644 index 00000000000..79401bc92bf --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PositionEntryDialog.java @@ -0,0 +1,64 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.user.client.ui.DoubleBox; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.dto.PositionDTO; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class PositionEntryDialog extends DataEntryDialog { + private final DoubleBox lat; + private final DoubleBox lng; + private final StringMessages stringMessages; + + private static final double ERROR_VAL = Double.MIN_VALUE; + + public PositionEntryDialog(String title, final StringMessages stringMessages, DialogCallback callback) { + super(title, title, stringMessages.save(), stringMessages.cancel(), + new DataEntryDialog.Validator() { + @Override + public String getErrorMessage(PositionDTO valueToValidate) { + if (valueToValidate.latDeg == ERROR_VAL) { + return stringMessages.pleaseEnterA(stringMessages.latitude()); + } + if (valueToValidate.latDeg > 90 || valueToValidate.latDeg < -90) { + return stringMessages.pleaseEnterAValidValueFor(stringMessages.latitude(), "-90.0 - 90.0"); + } + if (valueToValidate.lngDeg == ERROR_VAL) { + return stringMessages.pleaseEnterA(stringMessages.longitude()); + } + if (valueToValidate.lngDeg > 180 || valueToValidate.lngDeg < -180) { + return stringMessages.pleaseEnterAValidValueFor(stringMessages.latitude(), "-180.0 - 180.0"); + } + return null; + } + }, true, callback); + + this.stringMessages = stringMessages; + + lat = createDoubleBox(10); + lng = createDoubleBox(10); + } + + @Override + protected PositionDTO getResult() { + Double latDeg = lat.getValue(); + Double lngDeg = lng.getValue(); + return new PositionDTO(latDeg == null ? ERROR_VAL : latDeg, lngDeg == null ? ERROR_VAL : lngDeg); + + } + + @Override + protected Widget getAdditionalWidget() { + Grid grid = new Grid(2, 2); + grid.setWidget(0, 0, new Label(stringMessages.latitude() + " (" + stringMessages.degreesShort() + ")")); + grid.setWidget(0, 1, lat); + grid.setWidget(1, 0, new Label(stringMessages.longitude() + " (" + stringMessages.degreesShort() + ")")); + grid.setWidget(1, 1, lng); + + return grid; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java index fc47167b585..01e1e53c308 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java @@ -1,583 +1,76 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import com.google.gwt.cell.client.SafeHtmlCell; -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style.FontWeight; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.i18n.client.NumberFormat; -import com.google.gwt.safehtml.shared.SafeHtml; -import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -import com.google.gwt.user.cellview.client.CellTable; -import com.google.gwt.user.cellview.client.Column; -import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.Grid; -import com.google.gwt.user.client.ui.HasVerticalAlignment; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.Label; -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.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.PassingInstruction; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -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.ControlPointDTO; -import com.sap.sailing.gwt.ui.shared.GateDTO; -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.dialog.DataEntryDialog; -import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; - -/** - * A panel that has a race selection (inherited from {@link AbstractRaceManagementPanel}) and which adds a table - * for a selected race showing the race's waypoints together with the number of mark passings already received for that - * waypoint. Also, the control can be used to send course updates into the tracked race, mostly to simulate these types - * of events. Conceivably, this may in the future also become a way to set up and edit courses for a tracked race. - * - * @author Frank Mittag (C5163874), Axel Uhl (D043530) - */ -public class RaceCourseManagementPanel extends AbstractRaceManagementPanel { - /** - * Represents one mark assignment for a control point. Gates have multiple such records, one for each of their marks. - * - * @author Axel Uhl (D043530) - */ - private static class ControlPointAndOldAndNewMark { - private final ControlPointDTO controlPoint; - private final MarkDTO oldMark; - private MarkDTO newMark; - private PassingInstruction passingInstructions; - public ControlPointAndOldAndNewMark(ControlPointDTO controlPoint, PassingInstruction passingInstructions, MarkDTO oldMark) { - super(); - this.controlPoint = controlPoint; - this.passingInstructions = passingInstructions; - this.oldMark = oldMark; - this.newMark = oldMark; - } - public PassingInstruction getPassingInstructions() { - return passingInstructions; - } - public MarkDTO getNewMark() { - return newMark; - } - public void setNewMark(MarkDTO newMark) { - this.newMark = newMark; - } - public ControlPointDTO getControlPoint() { - return controlPoint; - } - public MarkDTO getOldMark() { - return oldMark; - } - } - - private class ControlPointCreationDialog extends DataEntryDialog { - private final CellTable marksTable; - private final MultiSelectionModel selectionModel; - - public ControlPointCreationDialog(final StringMessages stringMessages, AdminConsoleTableResources tableRes, - List marks, DialogCallback callback) { - super(stringMessages.controlPoint(), stringMessages.selectOneMarkOrTwoMarksForGate(), - stringMessages.ok(), stringMessages.cancel(), new Validator() { - @Override - public String getErrorMessage(ControlPointDTO valueToValidate) { - if (valueToValidate == null) { - return stringMessages.selectOneMarkOrTwoMarksForGate(); - } else { - return null; - } - } - - }, /* animationEnabled */ false, callback); - selectionModel = new MultiSelectionModel(); - selectionModel.addSelectionChangeHandler(new Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - validate(); - } - }); - marksTable = createAvailableMarksTable(stringMessages, tableRes, selectionModel); - ListDataProvider markDataProvider = new ListDataProvider(); - markDataProvider.getList().addAll(marks); - markDataProvider.addDataDisplay(marksTable); - } - - @Override - protected ControlPointDTO getResult() { - ControlPointDTO result = null; - Set selection = selectionModel.getSelectedSet(); - if (selection.size() == 1) { - result = selectionModel.getSelectedSet().iterator().next(); - } else if (selection.size() == 2) { - Iterator i = selectionModel.getSelectedSet().iterator(); - MarkDTO first = i.next(); - MarkDTO second = i.next(); - MarkDTO left; - MarkDTO right; - String gateName; - if (first.getName().matches("^.*"+REGEX_FOR_LEFT+".*$")) { - left = first; - right = second; - } else { - left = second; - right = first; - } - gateName = left.getName().replaceFirst(REGEX_FOR_LEFT, ""); - result = new GateDTO(/* generate UUID on the server */ null, gateName, left, right); - } - return result; - } - - @Override - protected Widget getAdditionalWidget() { - return marksTable; - } - } - - private static final String REGEX_FOR_LEFT = "( \\()?(([lL][eE][fF][tT])|(1))\\)?"; - - /** - * A table that lists the marks for which events have been received for the race selected. Note that this list may - * be longer than the list of marks actually used by the control points backing the course's waypoints because of - * the possibility of spare marks. - */ - private final CellTable marksTable; - private final ListDataProvider markDataProvider; - private final SingleSelectionModel markSelectionModel; - - /** - * A table that lists the product of Waypoint x ControlPoint x Mark plus a hint as to the number of mark passings. - * The (multi-)selection on this table can be used as either a selection of waypoints or a selection of control points - * or a selection of marks. - */ - private final CellTable controlPointsTable; - private final MultiSelectionModel controlPointsSelectionModel; - private final ListDataProvider controlPointDataProvider; - - /** - * When for a control point's mark a replacement mark is defined (see {@link #updateNewMark(Set, MarkDTO)}), - * the control point needs to be replaced before {@link #saveCourse(SailingServiceAsync, StringMessages) saving}. - * Those control points are added to this set. When the mark is reset to the original mark for all the control - * point's marks, the control point is removed from this set again. {@link #saveCourse(SailingServiceAsync, StringMessages)} - * then is responsible for creating replacement {@link ControlPointDTO}s before sending the new control point list to the - * server. - */ - private final Set controlPointsNeedingReplacement; - - private final HorizontalPanel courseActionsPanel; - - private final Handler markSelectionChangeHandler; - private final Button insertWaypointBefore; - private final Button insertWaypointAfter; - private final Button removeWaypointBtn; - private boolean ignoreWaypointAndOldAndNewMarkSelectionChange; - - public RaceCourseManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, - RegattaRefresher regattaRefresher, final StringMessages stringMessages) { - super(sailingService, errorReporter, regattaRefresher, stringMessages); - controlPointsNeedingReplacement = new HashSet(); - Grid grid = new Grid(2, 2); - grid.setCellPadding(5); - selectedRaceContentPanel.add(grid); - - Label currentRaceCourseLabel = new Label("Current race course"); - currentRaceCourseLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); - grid.setWidget(0, 0, currentRaceCourseLabel); - Label availableMarksLabel = new Label("Available marks"); - availableMarksLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); - grid.setWidget(0, 1, availableMarksLabel); - grid.getRowFormatter().setVerticalAlign(1, HasVerticalAlignment.ALIGN_TOP); - - final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); - controlPointsTable = new CellTable(/* pageSize */10000, tableRes); - grid.setWidget(1, 0, controlPointsTable); - controlPointsSelectionModel = new MultiSelectionModel(); - controlPointsTable.setSelectionModel(controlPointsSelectionModel); - controlPointsSelectionModel.addSelectionChangeHandler(new Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - handleControlPointSelectionChange(); - } - }); - TextColumn nameColumn = new TextColumn() { - @Override - public String getValue(ControlPointAndOldAndNewMark cpaoanm) { - return cpaoanm.getControlPoint().getName(); - } - }; - controlPointsTable.addColumn(nameColumn, stringMessages.controlPoint()); - TextColumn passingInstructionsColumn = new TextColumn() { - @Override - public String getValue(ControlPointAndOldAndNewMark cpaoanm) { - String result = ""; - if(cpaoanm.getPassingInstructions() != null) { - result = cpaoanm.getPassingInstructions().name(); - } - return result; - } - }; - controlPointsTable.addColumn(passingInstructionsColumn, "Passing instructions"); - TextColumn oldMarkColumn = new TextColumn() { - @Override - public String getValue(ControlPointAndOldAndNewMark cpaoanm) { - return "" + cpaoanm.getOldMark().getName(); - } - }; - controlPointsTable.addColumn(oldMarkColumn, stringMessages.mark()); - TextColumn newMarkColumn = new TextColumn(){ - - @Override - public String getValue(ControlPointAndOldAndNewMark cpaoanm) { - return "" + cpaoanm.getNewMark().getName(); - } - - }; - controlPointsTable.addColumn(newMarkColumn, stringMessages.newMark()); - controlPointDataProvider = new ListDataProvider(); - controlPointDataProvider.addDataDisplay(controlPointsTable); - - // race course marks table - markDataProvider = new ListDataProvider(); - markSelectionChangeHandler = new Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - if (!ignoreWaypointAndOldAndNewMarkSelectionChange) { - updateNewMark(controlPointsSelectionModel.getSelectedSet(), markSelectionModel.getSelectedObject()); - } - } - }; - markSelectionModel = new SingleSelectionModel(); - marksTable = createAvailableMarksTable(stringMessages, tableRes, markSelectionModel); - marksTable.getSelectionModel().addSelectionChangeHandler(markSelectionChangeHandler); - markDataProvider.addDataDisplay(marksTable); - grid.setWidget(1, 1, marksTable); - - courseActionsPanel = new HorizontalPanel(); - courseActionsPanel.setSpacing(10); - insertWaypointBefore = new Button(stringMessages.insertWaypointBeforeSelected()); - insertWaypointBefore.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - insertWaypoint(sailingService, stringMessages, tableRes, /* before */ true); - } - - }); - insertWaypointBefore.setEnabled(false); - courseActionsPanel.add(insertWaypointBefore); - insertWaypointAfter = new Button(stringMessages.insertWaypointAfterSelected()); - insertWaypointAfter.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - insertWaypoint(sailingService, stringMessages, tableRes, /* before */ false); - } - - }); - insertWaypointAfter.setEnabled(false); - courseActionsPanel.add(insertWaypointAfter); - removeWaypointBtn = new Button(stringMessages.remove()); - removeWaypointBtn.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - removeSelectedWaypoints(sailingService); - } - }); - removeWaypointBtn.setEnabled(false); - courseActionsPanel.add(removeWaypointBtn); - Button refreshBtn = new Button(stringMessages.refresh()); - refreshBtn.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - refreshSelectedRaceData (); - } - }); - courseActionsPanel.add(refreshBtn); - Button saveBtn = new Button(stringMessages.save()); - saveBtn.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - saveCourse(sailingService, stringMessages); - } - }); courseActionsPanel.add(saveBtn); - courseActionsPanel.setVisible(false); - this.selectedRaceContentPanel.add(courseActionsPanel); - } - - private void handleControlPointSelectionChange() { - ignoreWaypointAndOldAndNewMarkSelectionChange = true; - try { - markSelectionModel.setSelected(markSelectionModel.getSelectedObject(), false); - final int selectionSize = controlPointsSelectionModel.getSelectedSet().size(); - insertWaypointAfter.setEnabled(selectionSize==1); - insertWaypointBefore.setEnabled(selectionSize==1); - removeWaypointBtn.setEnabled(selectionSize>=1); - if (selectionSize == 1) { - MarkDTO newMark = controlPointsSelectionModel.getSelectedSet().iterator().next().getNewMark(); - if (newMark != null) { - for (MarkDTO markDTO : markDataProvider.getList()) { - if (markDTO.getName().equals(newMark.getName())) { - markSelectionModel.setSelected(markDTO, true); - } - } - } - } - } finally { - ignoreWaypointAndOldAndNewMarkSelectionChange = false; - } - } - - private void saveCourse(SailingServiceAsync sailingService, final StringMessages stringMessages) { - Set oldControlPointsFromTableAlreadyHandled = new HashSet(); - List> controlPoints = new ArrayList>(); - for (ControlPointAndOldAndNewMark cpaoanb : controlPointDataProvider.getList()) { - if (!oldControlPointsFromTableAlreadyHandled.contains(cpaoanb.getControlPoint())) { - oldControlPointsFromTableAlreadyHandled.add(cpaoanb.getControlPoint()); - ControlPointDTO controlPointToAdd; - if (controlPointsNeedingReplacement.contains(cpaoanb.getControlPoint())) { - if (cpaoanb.getControlPoint() instanceof GateDTO) { - controlPointToAdd = createGate((GateDTO) cpaoanb.getControlPoint()); - } else { - controlPointToAdd = cpaoanb.getNewMark(); - } - } else { - controlPointToAdd = cpaoanb.getControlPoint(); - } - controlPoints.add(new Pair(controlPointToAdd, cpaoanb.passingInstructions)); - } - } - sailingService.updateRaceCourse(singleSelectedRace, controlPoints, new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError(stringMessages.errorUpdatingRaceCourse(caught.getMessage())); - } - - @Override - public void onSuccess(Void result) { - Window.setStatus(stringMessages.successfullyUpdatedCourse()); - refreshSelectedRaceData(); - } - }); - } - - /** - * When a gate needs replacement, its entries in {@link #controlPointDataProvider} are looked up, and a new - * {@link GateDTO} is created having the same name as the old gate, but using the new marks as the gate's marks. - */ - private ControlPointDTO createGate(GateDTO oldGate) { - MarkDTO newLeft = null; - MarkDTO newRight = null; - for (ControlPointAndOldAndNewMark cpaoanb : controlPointDataProvider.getList()) { - if (cpaoanb.getControlPoint() == oldGate) { - MarkDTO newMark = cpaoanb.getNewMark(); - if (newRight != null || newMark.getName().matches("^.*"+REGEX_FOR_LEFT+".*$")) { - newLeft = newMark; - } else { - newRight = newMark; - } - } - } - assert newLeft != null && newRight != null; - // if old gate had null ID, the new gate will have a null ID too, causing the server to generate one - return new GateDTO(oldGate.getIdAsString(), oldGate.getName(), newLeft, newRight); - } - - private CellTable createAvailableMarksTable(final StringMessages stringMessages, AdminConsoleTableResources tableRes, - SelectionModel selectionModel) { - CellTable result = new CellTable(/* pageSize */10000, tableRes); - result.setSelectionModel(selectionModel); - TextColumn markNameColumn = new TextColumn() { - @Override - public String getValue(MarkDTO markDTO) { - return markDTO.getName(); - } - }; - result.addColumn(markNameColumn, stringMessages.mark()); - - final SafeHtmlCell markPositionCell = new SafeHtmlCell(); - Column markPositionColumn = new Column(markPositionCell) { - @Override - public SafeHtml getValue(MarkDTO mark) { - SafeHtmlBuilder builder = new SafeHtmlBuilder(); - if(mark.position != null) { - NumberFormat fmt = NumberFormat.getFormat("#.###"); - builder.appendEscaped(fmt.format(mark.position.latDeg)+", "+fmt.format(mark.position.lngDeg)); - } - return builder.toSafeHtml(); - } - }; - result.addColumn(markPositionColumn, stringMessages.position()); - - TextColumn markColorColumn = new TextColumn() { - @Override - public String getValue(MarkDTO markDTO) { - return markDTO.color != null ? markDTO.color : ""; - } - }; - result.addColumn(markColorColumn, stringMessages.color()); - - TextColumn markShapeColumn = new TextColumn() { - @Override - public String getValue(MarkDTO markDTO) { - return markDTO.shape != null ? markDTO.shape : ""; - } - }; - result.addColumn(markShapeColumn, stringMessages.shape()); - - TextColumn markPatternColumn = new TextColumn() { - @Override - public String getValue(MarkDTO markDTO) { - return markDTO.pattern != null ? markDTO.pattern : ""; - } - }; - result.addColumn(markPatternColumn, stringMessages.pattern()); - - TextColumn markUUIDColumn = new TextColumn() { - @Override - public String getValue(MarkDTO markDTO) { - return markDTO.getIdAsString(); - } - }; - result.addColumn(markUUIDColumn, "UUID"); - - return result; - } - - private void updateNewMark(Set selectedWaypointsAndOldAndNewMarks, MarkDTO selectedNewMark) { - if (selectedWaypointsAndOldAndNewMarks != null) { - for (ControlPointAndOldAndNewMark w : selectedWaypointsAndOldAndNewMarks) { - if (selectedNewMark == null) { - w.setNewMark(w.getOldMark()); - } else { - w.setNewMark(selectedNewMark); - } - if (w.getOldMark().getName().equals(w.getNewMark().getName())) { - checkIfAllMarksOfControlPointAreUnchangedAndIfSoRememberThis(w.getControlPoint()); - } else { - controlPointsNeedingReplacement.add(w.getControlPoint()); - } - final int indexOf = controlPointDataProvider.getList().indexOf(w); - if (indexOf != -1) { - controlPointDataProvider.getList().set(indexOf, w); - } - } - } - } - - private void checkIfAllMarksOfControlPointAreUnchangedAndIfSoRememberThis(ControlPointDTO controlPoint) { - boolean allMarksUnchanged = true; - for (ControlPointAndOldAndNewMark cpaoanb : controlPointDataProvider.getList()) { - if (cpaoanb.getControlPoint() == controlPoint) { - if (!cpaoanb.getOldMark().getName().equals(cpaoanb.getNewMark().getName())) { - allMarksUnchanged = false; - break; - } - } - } - if (allMarksUnchanged) { - controlPointsNeedingReplacement.remove(controlPoint); - } - } - - private void insertWaypoint(final SailingServiceAsync sailingService, StringMessages stringMessages, - AdminConsoleTableResources tableRes, final boolean beforeSelection) { - new ControlPointCreationDialog(stringMessages, tableRes, markDataProvider.getList(), new DialogCallback() { - @Override - public void cancel() { - // dialog cancelled, do nothing - } - - @Override - public void ok(ControlPointDTO result) { - Set selectedElements = controlPointsSelectionModel.getSelectedSet(); - if (!selectedElements.isEmpty()) { - ControlPointAndOldAndNewMark selectedElement = selectedElements.iterator().next(); - int insertPos = controlPointDataProvider.getList().indexOf(selectedElement) + (beforeSelection?0:1); - for (MarkDTO markDTO : result.getMarks()) { - controlPointDataProvider.getList().add(insertPos++, new ControlPointAndOldAndNewMark(result, /* passing instructions */ null, markDTO)); - } - } - } - }).show(); - } - - @Override - void refreshSelectedRaceData() { - if (singleSelectedRace != null && selectedRaceDTO != null) { - courseActionsPanel.setVisible(true); - // TODO bug 1351: never use System.currentTimeMillis() on the client when trying to compare anything with "server time"; this one is not so urgent as it is reached only in the AdminConsole and we expect administrators to have proper client-side time settings - sailingService.getRaceCourse(singleSelectedRace, new Date(), new AsyncCallback() { - @Override - public void onSuccess(RaceCourseDTO raceCourseDTO) { - updateCourseAndMarksInfo(raceCourseDTO); - } - - @Override - public void onFailure(Throwable caught) { - RaceCourseManagementPanel.this.errorReporter.reportError( - RaceCourseManagementPanel.this.stringMessages.errorTryingToObtainTheMarksOfTheRace( - caught.getMessage())); - } - }); - } else { - courseActionsPanel.setVisible(false); - } - } - - private void updateCourseAndMarksInfo(RaceCourseDTO raceCourseDTO) { - List waypointsAndOldAndNewMarks = new ArrayList(); - for (WaypointDTO waypointDTO : raceCourseDTO.waypoints) { - ControlPointDTO controlPointDTO = waypointDTO.controlPoint; - for (MarkDTO mark : controlPointDTO.getMarks()) { - ControlPointAndOldAndNewMark waypointAndOldAndNewMark = new ControlPointAndOldAndNewMark(controlPointDTO, waypointDTO.passingInstructions, mark); - waypointsAndOldAndNewMarks.add(waypointAndOldAndNewMark); - } - } - controlPointDataProvider.getList().clear(); - controlPointDataProvider.getList().addAll(waypointsAndOldAndNewMarks); - controlPointsNeedingReplacement.clear(); - markDataProvider.getList().clear(); - markDataProvider.getList().addAll(raceCourseDTO.getMarks()); - Collections.sort(markDataProvider.getList(), new Comparator() { - @Override - public int compare(MarkDTO o1, MarkDTO o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - for (ControlPointAndOldAndNewMark w : controlPointsSelectionModel.getSelectedSet()) { - controlPointsSelectionModel.setSelected(w, false); - } - } - - private void removeSelectedWaypoints(final SailingServiceAsync sailingService) { - final Set selectedControlPoints = new HashSet(); - for (ControlPointAndOldAndNewMark cpaoanb : controlPointsSelectionModel.getSelectedSet()) { - selectedControlPoints.add(cpaoanb.getControlPoint()); - } - for (Iterator i=controlPointDataProvider.getList().iterator(); i.hasNext(); ) { - ControlPointAndOldAndNewMark next = i.next(); - if (selectedControlPoints.contains(next.getControlPoint())) { - i.remove(); - } - } - handleControlPointSelectionChange(); - } -} +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Date; + +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +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.RaceCourseDTO; + +/** + * A panel that has a race selection (inherited from {@link AbstractRaceManagementPanel}) and which adds a table + * for a selected race showing the race's waypoints together with the number of mark passings already received for that + * waypoint. Also, the control can be used to send course updates into the tracked race, mostly to simulate these types + * of events. Conceivably, this may in the future also become a way to set up and edit courses for a tracked race. + * + * @author Frank Mittag (C5163874), Axel Uhl (D043530) + */ +public class RaceCourseManagementPanel extends AbstractRaceManagementPanel { + private final CourseManagementWidget courseManagementWidget; + + public RaceCourseManagementPanel(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, + RegattaRefresher regattaRefresher, final StringMessages stringMessages) { + super(sailingService, errorReporter, regattaRefresher, stringMessages); + + courseManagementWidget = new CourseManagementWidget(sailingService, errorReporter, stringMessages) { + @Override + protected void save() { + sailingService.updateRaceCourse(singleSelectedRace, createWaypointPairs(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorUpdatingRaceCourse(caught.getMessage())); + } + + @Override + public void onSuccess(Void result) { + Window.setStatus(stringMessages.successfullyUpdatedCourse()); + refreshSelectedRaceData(); + } + }); + } + + @Override + public void refresh() { + if (singleSelectedRace != null && selectedRaceDTO != null) { + mainPanel.setVisible(true); + // TODO bug 1351: never use System.currentTimeMillis() on the client when trying to compare anything with "server time"; this one is not so urgent as it is reached only in the AdminConsole and we expect administrators to have proper client-side time settings + sailingService.getRaceCourse(singleSelectedRace, new Date(), new AsyncCallback() { + @Override + public void onSuccess(RaceCourseDTO raceCourseDTO) { + updateWaypointsAndControlPoints(raceCourseDTO); + marks.refresh(raceCourseDTO.getMarks()); + } + + @Override + public void onFailure(Throwable caught) { + RaceCourseManagementPanel.this.errorReporter.reportError( + RaceCourseManagementPanel.this.stringMessages.errorTryingToObtainTheMarksOfTheRace( + caught.getMessage())); + } + }); + } else { + mainPanel.setVisible(false); + } + } + }; + this.selectedRaceContentPanel.add(courseManagementWidget); + } + + @Override + void refreshSelectedRaceData() { + courseManagementWidget.refresh(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogDialog.java index 8ff0780c90b..1dc3a475a81 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogDialog.java @@ -16,6 +16,7 @@ import com.google.gwt.view.client.SelectionModel; import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.impl.NaturalComparator; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.RaceLogDTO; import com.sap.sailing.gwt.ui.shared.RaceLogEventDTO; import com.sap.sse.gwt.client.dialog.DataEntryDialog; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java new file mode 100644 index 00000000000..fd6729abd63 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java @@ -0,0 +1,185 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gwt.core.client.Callback; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CaptionPanel; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.VerticalPanel; +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.ErrorReporter; +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.dialog.DataEntryDialog; + +public class RaceLogTrackingCompetitorRegistrationsDialog extends RaceLogTrackingDialog { + private CompetitorTableWrapper> allCompetitorsTable; + private CompetitorTableWrapper> registeredCompetitorsTable; + private final boolean filterByLeaderBoardInitially = false; + private final Callback competitorsRegistered; + + public RaceLogTrackingCompetitorRegistrationsDialog(final SailingServiceAsync sailingService, final StringMessages stringMessages, + final ErrorReporter errorReporter, final String leaderboardName, final String raceColumnName, final String fleetName, boolean editable, + Callback competitorsRegistered) { + super(sailingService, stringMessages, errorReporter, leaderboardName, raceColumnName, fleetName, editable); + this.competitorsRegistered = competitorsRegistered; + + refreshCompetitors(); + } + + @Override + protected void addButtons(Panel buttonPanel) { + Button addCompetitorButton = new Button(stringMessages.add(stringMessages.competitor())); + addCompetitorButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + openAddCompetitorDialog(); + } + }); + buttonPanel.add(addCompetitorButton); + + super.addButtons(buttonPanel); + } + + private void move(CompetitorTableWrapper from, CompetitorTableWrapper to, Collection toMove) { + if (toMove.isEmpty()) { + return; + } + List newFromList = new ArrayList<>(); + Util.addAll(from.getFilterField().getAll(), newFromList); + newFromList.removeAll(toMove); + from.getFilterField().updateAll(newFromList); + List newToList = new ArrayList<>(); + Util.addAll(to.getFilterField().getAll(), newToList); + newToList.addAll(toMove); + to.getFilterField().updateAll(newToList); + } + + private void moveSelected(CompetitorTableWrapper> from, + CompetitorTableWrapper> to) { + move(from, to, from.getSelectionModel().getSelectedSet()); + } + + @Override + protected void addMainContent(Panel mainPanel) { + super.addMainContent(mainPanel); + + HorizontalPanel panel = new HorizontalPanel(); + mainPanel.add(panel); + CaptionPanel allCompetitorsPanel = new CaptionPanel(stringMessages.competitorPool()); + CaptionPanel registeredCompetitorsPanel = new CaptionPanel(stringMessages.registeredCompetitors()); + allCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, + new MultiSelectionModel()); + registeredCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, + new MultiSelectionModel()); + allCompetitorsPanel.add(allCompetitorsTable); + registeredCompetitorsPanel.add(registeredCompetitorsTable); + VerticalPanel movePanel = new VerticalPanel(); + Button registerBtn = new Button("<"); + Button unregisterBtn = new Button(">"); + registerBtn.setEnabled(editable); + unregisterBtn.setEnabled(editable); + movePanel.add(registerBtn); + movePanel.add(unregisterBtn); + registerBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + moveSelected(allCompetitorsTable, registeredCompetitorsTable); + } + }); + unregisterBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + moveSelected(registeredCompetitorsTable, allCompetitorsTable); + } + }); + panel.add(registeredCompetitorsPanel); + panel.add(movePanel); + panel.setCellVerticalAlignment(movePanel, HasVerticalAlignment.ALIGN_MIDDLE); + panel.add(allCompetitorsPanel); + } + + @Override + protected void save() { + final Set registeredCompetitors = new HashSet<>(); + Util.addAll(registeredCompetitorsTable.getAllCompetitors(), registeredCompetitors); + sailingService.setCompetitorRegistrations(leaderboardName, raceColumnName, fleetName, + registeredCompetitors, new AsyncCallback() { + @Override + public void onSuccess(Void result) { + hide(); + competitorsRegistered.onSuccess(! registeredCompetitors.isEmpty()); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not save competitor registrations: " + caught.getMessage()); + competitorsRegistered.onFailure(caught); + } + }); + } + + private void openAddCompetitorDialog() { + new CompetitorEditDialog(stringMessages, new CompetitorDTOImpl(), new DataEntryDialog.DialogCallback() { + @Override + public void ok(CompetitorDTO competitor) { + sailingService.addOrUpdateCompetitor(competitor, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to add competitor: "+caught.getMessage()); + } + + @Override + public void onSuccess(CompetitorDTO updatedCompetitor) { + allCompetitorsTable.getFilterField().add(updatedCompetitor); + } + }); + } + + @Override + public void cancel() { + } + }).show(); + } + + private void refreshCompetitors() { + registeredCompetitorsTable.getDataProvider().getList().clear(); + + allCompetitorsTable.refreshCompetitorList(filterByLeaderBoardInitially ? leaderboardName : null, + true, new Callback, Throwable>() { + @Override + public void onSuccess(Iterable result) { + center(); + + //add competitors that are already registered + sailingService.getCompetitorRegistrations(leaderboardName, raceColumnName, fleetName, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + move(allCompetitorsTable, registeredCompetitorsTable, result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load already registered competitors: " + caught.getMessage()); + } + }); + } + + @Override + public void onFailure(Throwable reason) {} + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCourseDefinitionDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCourseDefinitionDialog.java new file mode 100644 index 00000000000..ca1e07ac26e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCourseDefinitionDialog.java @@ -0,0 +1,168 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Collection; + +import com.google.gwt.cell.client.FieldUpdater; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Panel; +import com.sap.sailing.domain.common.dto.PositionDTO; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +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.sailing.gwt.ui.shared.RaceCourseDTO; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class RaceLogTrackingCourseDefinitionDialog extends RaceLogTrackingDialog { + private class RaceLogCourseManagementWidget extends CourseManagementWidget { + public RaceLogCourseManagementWidget(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, + final StringMessages stringMessages) { + super(sailingService, errorReporter, stringMessages); + + Button addMark = new Button(stringMessages.add(stringMessages.mark())); + addMark.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + new MarkEditDialog(stringMessages, new MarkDTO(null, null), true, new DataEntryDialog.DialogCallback() { + @Override + public void ok(MarkDTO mark) { + sailingService.addMarkToRaceLog(leaderboardName, raceColumnName, fleetName, mark, new AsyncCallback() { + @Override + public void onSuccess(Void result) { + courseManagementWidget.refresh(); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not add mark: " + caught.getMessage()); + } + }); + } + + @Override + public void cancel() {} + }).show(); + } + }); + marksBtnsPanel.add(addMark); + + + Button cancel = new Button(stringMessages.cancel()); + cancel.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + hide(); + } + }); + buttonsPanel.add(cancel); + + ImagesBarColumn actionColumn = + new ImagesBarColumn( + new RaceLogTrackingCourseDefinitionDialogMarksImagesBarCell(stringMessages)); + actionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, final MarkDTO markDTO, String value) { + if (RaceLogTrackingCourseDefinitionDialogMarksImagesBarCell.ACTION_PING.equals(value)) { + new PositionEntryDialog(stringMessages.pingPosition(stringMessages.mark()), + stringMessages, new DataEntryDialog.DialogCallback() { + + @Override + public void ok(PositionDTO position) { + sailingService.pingMarkViaRaceLogTracking(leaderboardName, raceColumnName, fleetName, + markDTO, position, new AsyncCallback() { + + @Override + public void onSuccess(Void result) { + refresh(); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not ping mark: " + caught.getMessage()); + } + }); + } + + @Override + public void cancel() {} + }).show(); + } + } + }); + marks.getTable().addColumn(actionColumn); + } + + @Override + protected void save() { + sailingService.addCourseDefinitionToRaceLog(leaderboardName, raceColumnName, fleetName, + createWaypointPairs(), new AsyncCallback() { + @Override + public void onSuccess(Void result) { + hide(); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could note save course: " + caught.getMessage()); + } + }); + } + + @Override + public void refresh() { + sailingService.getLastCourseDefinitionInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback() { + @Override + public void onSuccess(RaceCourseDTO result) { + updateWaypointsAndControlPoints(result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load course: " + caught.getMessage()); + } + }); + + sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + marks.refresh(result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load marks: " + caught.getMessage()); + } + }); + } + }; + + private CourseManagementWidget courseManagementWidget; + + public RaceLogTrackingCourseDefinitionDialog(final SailingServiceAsync sailingService, final StringMessages stringMessages, + final ErrorReporter errorReporter, final String leaderboardName, final String raceColumnName, final String fleetName) { + super(sailingService, stringMessages, errorReporter, leaderboardName, raceColumnName, fleetName); + + courseManagementWidget.refresh(); + } + + @Override + protected void addMainContent(Panel mainPanel) { + super.addMainContent(mainPanel); + + courseManagementWidget = new RaceLogCourseManagementWidget(sailingService, errorReporter, stringMessages); + mainPanel.add(courseManagementWidget); + } + + @Override + protected void addButtons(Panel buttonPanel) { + //reuse buttons of widget + } + + @Override + protected void save() { + //won't be called, as we are reusing widget buttons + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCourseDefinitionDialogMarksImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCourseDefinitionDialogMarksImagesBarCell.java new file mode 100644 index 00000000000..a33ffe1c9f9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCourseDefinitionDialogMarksImagesBarCell.java @@ -0,0 +1,26 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; + +import com.google.gwt.core.client.GWT; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; + +public class RaceLogTrackingCourseDefinitionDialogMarksImagesBarCell extends ImagesBarCell { + public static final String ACTION_PING = "ACTION_PING"; + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + private final StringMessages stringMessages; + + public RaceLogTrackingCourseDefinitionDialogMarksImagesBarCell(StringMessages stringMessages) { + super(); + this.stringMessages = stringMessages; + } + + @Override + protected Iterable getImageSpecs() { + ArrayList result = new ArrayList(); + result.add(new ImageSpec(ACTION_PING, stringMessages.pingPosition(stringMessages.mark()), makeImagePrototype(resources.ping()))); + + return result; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsDialog.java new file mode 100644 index 00000000000..c06731dc8c0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsDialog.java @@ -0,0 +1,320 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import org.moxieapps.gwt.highcharts.client.Axis; +import org.moxieapps.gwt.highcharts.client.AxisTitle; +import org.moxieapps.gwt.highcharts.client.Chart; +import org.moxieapps.gwt.highcharts.client.Global; +import org.moxieapps.gwt.highcharts.client.Highcharts; +import org.moxieapps.gwt.highcharts.client.Legend; +import org.moxieapps.gwt.highcharts.client.Point; +import org.moxieapps.gwt.highcharts.client.Series; +import org.moxieapps.gwt.highcharts.client.ToolTip; +import org.moxieapps.gwt.highcharts.client.ToolTipData; +import org.moxieapps.gwt.highcharts.client.ToolTipFormatter; +import org.moxieapps.gwt.highcharts.client.events.PointMouseOverEvent; +import org.moxieapps.gwt.highcharts.client.events.PointMouseOverEventHandler; +import org.moxieapps.gwt.highcharts.client.plotOptions.SeriesPlotOptions; + +import com.google.gwt.cell.client.FieldUpdater; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.view.client.CellPreviewEvent; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +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.sailing.gwt.ui.shared.MarkDTO; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class RaceLogTrackingDeviceMappingsDialog extends RaceLogTrackingDialog { + public static final double PERCENTAGE_OF_TIMESPAN_TO_EXTEND_OPEN_ENDS = 0.1; + public static final String FIELD_INDEX = "index"; + public static final int CHART_WIDTH = 500; + public static final String SERIES_COLOR = "#fcb913"; + + private List allMappings = new ArrayList(); + + private DeviceMappingTableWrapper deviceMappingTable; + + private Point[] data; + + private Date latest; + private Date earliest; + + private Chart chart; + + public RaceLogTrackingDeviceMappingsDialog(final SailingServiceAsync sailingService, final StringMessages stringMessages, + final ErrorReporter errorReporter, final String leaderboardName, + final String raceColumnName, final String fleetName) { + super(sailingService, stringMessages, errorReporter, leaderboardName, raceColumnName, fleetName, false); + + deviceMappingTable = new DeviceMappingTableWrapper(sailingService, stringMessages, errorReporter); + deviceMappingTable.getTable().addCellPreviewHandler(new CellPreviewEvent.Handler() { + @Override + public void onCellPreview(CellPreviewEvent event) { + int i = allMappings.indexOf(event.getValue()); + chart.getSeries()[0].getPoints()[i].select(true, false); + } + }); + ImagesBarColumn actionCol = + new ImagesBarColumn( + new RaceLogTrackingDeviceMappingsImagesBarCell(stringMessages)); + actionCol.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, final DeviceMappingDTO dto, String value) { + if (RaceLogTrackingDeviceMappingsImagesBarCell.ACTION_CLOSE.equals(value)) { + new SetTimePointDialog(stringMessages, stringMessages.setClosingTimePoint(), new DataEntryDialog.DialogCallback() { + @Override + public void ok(Date editedObject) { + sailingService.closeOpenEndedDeviceMapping(leaderboardName, raceColumnName, fleetName, + dto, editedObject, new AsyncCallback() { + @Override + public void onSuccess(Void result) { + refresh(); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not close open ended mapping: " + caught.getMessage()); + } + }); + } + + @Override + public void cancel() {} + }).show(); + } else if (RaceLogTrackingDeviceMappingsImagesBarCell.ACTION_REMOVE.equals(value)) { + sailingService.revokeRaceLogEvents(leaderboardName, raceColumnName, fleetName, dto.originalRaceLogEventIds, + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not remove mappings: " + caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + refresh(); + } + + }); + } + } + }); + deviceMappingTable.getTable().addColumn(actionCol, stringMessages.actions()); + + HorizontalPanel panel = new HorizontalPanel(); + mainPanel.add(panel); + + chart = new Chart() + .setType(Series.Type.COLUMN_RANGE) + .setChartTitleText(stringMessages.deviceMappings()) + .setLegend(new Legend().setEnabled(false)) + .setInverted(true); + + chart.setSeriesPlotOptions(new SeriesPlotOptions().setPointMouseOverEventHandler(new PointMouseOverEventHandler() { + @Override + public boolean onMouseOver(PointMouseOverEvent pointMouseOverEvent) { + int i = (int) pointMouseOverEvent.getXAsLong(); + deviceMappingTable.getSelectionModel().setSelected(allMappings.get(i), true); + chart.getSeries()[0].getPoints()[i].select(true, false); + return true; + } + }).setColor(SERIES_COLOR)); + + Highcharts.setOptions( + new Highcharts.Options().setGlobal( + new Global() + .setUseUTC(false) + )); + + chart.getXAxis().setOption("labels/enabled", false) + .setGridLineWidth(0) + .setMinorGridLineWidth(0); + + chart.getYAxis() + .setAxisTitle(new AxisTitle().setText(stringMessages.time())) + .setType(Axis.Type.DATE_TIME) + .setGridLineWidth(0) + .setMinorGridLineWidth(0); + + chart.setWidth(CHART_WIDTH + "px"); + chart.setHeight("400px"); + + chart.setToolTip(new ToolTip().setFormatter(new ToolTipFormatter() { + @Override + public String format(ToolTipData toolTipData) { + int index = (int) toolTipData.getXAsLong(); + DeviceMappingDTO mapping = allMappings.get(index); + String itemType = mapping.mappedTo instanceof MarkDTO ? stringMessages.mark() : stringMessages.competitor(); + + return "" + stringMessages.device() + ": " + mapping.deviceIdentifier.deviceType + " - " + mapping.deviceIdentifier.deviceId + "
" + + "" + stringMessages.mappedTo() + ": " + itemType + " - " + mapping.mappedTo + "
" + + "" + stringMessages.from() + ": " + DateAndTimeFormatterUtil.formatDateAndTime(mapping.from) + "
" + + "" + stringMessages.to() + ": " + DateAndTimeFormatterUtil.formatDateAndTime(mapping.to); + } + })); + + + panel.add(chart); + panel.add(deviceMappingTable); + + refresh(); + center(); + } + + @Override + protected void addButtons(Panel buttonPanel) { + Button addCompetitorButton = new Button(stringMessages.add()); + addCompetitorButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + addMapping(); + } + }); + buttonPanel.add(addCompetitorButton); + + Button importBtn = new Button(stringMessages.importFixes()); + importBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + importFixes(); + } + }); + buttonPanel.add(importBtn); + + super.addButtons(buttonPanel); + } + + @Override + protected void save() { + // TODO Auto-generated method stub + + } + + private void updateChart() { + data = new Point[allMappings.size()]; + long earliestMillis = earliest.getTime(); + long latestMillis = latest.getTime(); + long range = latestMillis - earliestMillis; + long extension = (long) (range * PERCENTAGE_OF_TIMESPAN_TO_EXTEND_OPEN_ENDS); + long yMin = earliestMillis - extension; + long yMax = latestMillis + extension; + + int i = 0; + for (DeviceMappingDTO mapping : allMappings) { + JSONObject userData = new JSONObject(); + userData.put(FIELD_INDEX, userData); + + long from = mapping.from == null ? yMin - range : mapping.from.getTime(); + long to = mapping.to == null ? yMax + range : mapping.to.getTime(); + + data[i] = new Point(i, from, to); + + i++; + } + + chart.removeAllSeries(false); + + chart.addSeries(chart.createSeries() + .setName(stringMessages.deviceMappings()) + .setPoints(data)); + + chart.getYAxis().setExtremes(yMin, yMax); + } + + private void updateExtremes(DeviceMappingDTO mapping) { + if (mapping.from != null && earliest.after(mapping.from)) earliest = mapping.from; + if (mapping.to != null && latest.before(mapping.to)) latest = mapping.to; + if (mapping.to != null && earliest.after(mapping.to)) earliest = mapping.to; + if (mapping.from != null && latest.before(mapping.from)) latest = mapping.from; + } + + private void refresh() { + earliest = new Date(Long.MAX_VALUE); + latest = new Date(Long.MIN_VALUE); + + sailingService.getDeviceMappingsFromRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback>() { + @Override + public void onSuccess(List result) { + for (DeviceMappingDTO mapping : result) { + updateExtremes(mapping); + } + allMappings = result; + + updateChart(); + deviceMappingTable.refresh(allMappings); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load mappings for marks: " + caught.getMessage()); + } + }); + } + + private void showAddMappingDialog(DeviceMappingDTO mapping) { + new AddDeviceMappingDialog(sailingService, errorReporter, stringMessages, + leaderboardName, raceColumnName, fleetName, new DataEntryDialog.DialogCallback() { + @Override + public void ok(final DeviceMappingDTO mapping) { + sailingService.addDeviceMappingToRaceLog(leaderboardName, raceColumnName, fleetName, mapping, new AsyncCallback() { + @Override + public void onSuccess(Void result) { + refresh(); + } + + @Override + public void onFailure(Throwable caught) { + showAddMappingDialog(mapping); + errorReporter.reportError("Could not add mapping: " + caught.getMessage()); + } + }); + } + + @Override + public void cancel() {} + }, mapping).show(); + } + + private void addMapping() { + showAddMappingDialog(null); + } + + private void importFixes() { + new ImportFixesAndAddMappingsDialog(sailingService, errorReporter, stringMessages, + leaderboardName, raceColumnName, fleetName, new DataEntryDialog.DialogCallback>() { + + @Override + public void ok(Collection editedObject) { + for (DeviceMappingDTO mapping : editedObject) { + sailingService.addDeviceMappingToRaceLog(leaderboardName, raceColumnName, fleetName, mapping, + new AsyncCallback() { + @Override + public void onSuccess(Void result) { + refresh(); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage()); + } + }); + } + } + + @Override + public void cancel() {} + }).show(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java new file mode 100644 index 00000000000..d31582aa965 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java @@ -0,0 +1,35 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; + +import com.google.gwt.core.client.GWT; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; + +public class RaceLogTrackingDeviceMappingsImagesBarCell extends ImagesBarCell { + public static final String ACTION_CLOSE = "CLOSE"; + public static final String ACTION_REMOVE = "REMOVE"; + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + private final StringMessages stringMessages; + + public RaceLogTrackingDeviceMappingsImagesBarCell(StringMessages stringMessages) { + super(); + this.stringMessages = stringMessages; + } + + @Override + protected Iterable getImageSpecs() { + ArrayList result = new ArrayList(); + DeviceMappingDTO mapping = (DeviceMappingDTO) getContext().getKey(); + + result.add(new ImageSpec(ACTION_REMOVE, stringMessages.remove(), makeImagePrototype(resources.removeIcon()))); + + if (mapping.from == null || mapping.to == null) { + result.add(new ImageSpec(ACTION_CLOSE, stringMessages.closeTimeRange(), + makeImagePrototype(resources.closeTimeRange()))); + } + + return result; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDialog.java new file mode 100644 index 00000000000..aaa8baddae4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDialog.java @@ -0,0 +1,77 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +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.DialogBox; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public abstract class RaceLogTrackingDialog extends DialogBox { + protected final SailingServiceAsync sailingService; + protected final StringMessages stringMessages; + protected final ErrorReporter errorReporter; + protected final String leaderboardName; + protected final String raceColumnName; + protected final String fleetName; + protected Button saveButton; + protected final boolean editable; + protected final VerticalPanel mainPanel; + + public RaceLogTrackingDialog(SailingServiceAsync sailingService, StringMessages stringMessages, + ErrorReporter errorReporter, String leaderboardName, String raceColumnName, String fleetName) { + this(sailingService, stringMessages, errorReporter, leaderboardName, raceColumnName, fleetName, true); + } + + public RaceLogTrackingDialog(SailingServiceAsync sailingService, StringMessages stringMessages, + ErrorReporter errorReporter, String leaderboardName, String raceColumnName, String fleetName, boolean editable) { + this.sailingService = sailingService; + this.stringMessages = stringMessages; + this.errorReporter = errorReporter; + this.leaderboardName = leaderboardName; + this.raceColumnName = raceColumnName; + this.fleetName = fleetName; + this.editable = editable; + + mainPanel = new VerticalPanel(); + setWidget(mainPanel); + + addMainContent(mainPanel); + center(); + } + + protected void addMainContent(Panel mainPanel) { + HorizontalPanel buttonPanel = new HorizontalPanel(); + mainPanel.add(buttonPanel); + + addButtons(buttonPanel); + } + + protected void addButtons(Panel buttonPanel) { + Button cancel = new Button(stringMessages.cancel()); + cancel.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + hide(); + } + }); + buttonPanel.add(cancel); + if (editable) { + saveButton = new Button(stringMessages.save()); + saveButton.setTitle(stringMessages.canOnlyBeEditedBeforeStartingTracking()); + saveButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + save(); + } + }); + buttonPanel.add(saveButton); + } + } + + protected abstract void save(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementImagesBarCell.java new file mode 100644 index 00000000000..56b0d917dcc --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementImagesBarCell.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.text.shared.SafeHtmlRenderer; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; + +public class RaceLogTrackingEventManagementImagesBarCell extends ImagesBarCell { + public static final String ACTION_DENOTE_FOR_RACELOG_TRACKING = "ACTION_DENOTE_FOR_RACELOG_TRACKING"; + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + private final StringMessages stringMessages; + + public RaceLogTrackingEventManagementImagesBarCell(StringMessages stringMessages) { + super(); + this.stringMessages = stringMessages; + } + + public RaceLogTrackingEventManagementImagesBarCell(SafeHtmlRenderer renderer, StringMessages stringConstants) { + super(); + this.stringMessages = stringConstants; + } + + @Override + protected Iterable getImageSpecs() { + ArrayList result = new ArrayList(); + + result.add(new ImageSpec(ACTION_DENOTE_FOR_RACELOG_TRACKING, stringMessages.denoteForRaceLogTracking(), + makeImagePrototype(resources.denoteForRaceLogTracking()))); + + return result; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementPanel.java new file mode 100644 index 00000000000..c8ced0d8b97 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementPanel.java @@ -0,0 +1,387 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gwt.cell.client.FieldUpdater; +import com.google.gwt.core.client.Callback; +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.ColumnSortEvent.ListHandler; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CaptionPanel; +import com.google.gwt.user.client.ui.Panel; +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.FleetDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.LeaderboardsDisplayer; +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.shared.RaceLogSetStartTimeAndProcedureDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.common.Util; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; +import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; + +/** + * Allows the user to start and stop tracking of races using the RaceLog-tracking connector. + */ +public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConfigPanel implements LeaderboardsDisplayer { + private Button startTrackingButton; + private TrackFileImportDeviceIdentifierTableWrapper deviceIdentifierTable; + + public RaceLogTrackingEventManagementPanel(SailingServiceAsync sailingService, + RegattaRefresher regattaRefresher, LeaderboardsRefresher leaderboardsRefresher, + ErrorReporter errorReporter, StringMessages stringMessages) { + super(sailingService, regattaRefresher, leaderboardsRefresher, errorReporter, + stringMessages, new MultiSelectionModel()); + + //add upload panel + CaptionPanel importPanel = new CaptionPanel(stringMessages.importFixes()); + VerticalPanel importContent = new VerticalPanel(); + mainPanel.add(importPanel); + deviceIdentifierTable = new TrackFileImportDeviceIdentifierTableWrapper(sailingService, stringMessages, errorReporter); + TrackFileImportWidget importWidget = new TrackFileImportWidget(deviceIdentifierTable, stringMessages, + sailingService, errorReporter); + importPanel.add(importContent); + importContent.add(importWidget); + importContent.add(deviceIdentifierTable); + } + + @Override + protected void addColumnsToLeaderboardTable(CellTable leaderboardTable) { + ListHandler leaderboardColumnListHandler = new ListHandler( + leaderboardList.getList()); + + TextColumn leaderboardNameColumn = new TextColumn() { + @Override + public String getValue(StrippedLeaderboardDTO leaderboard) { + return leaderboard.name; + } + }; + + TextColumn leaderboardDisplayNameColumn = new TextColumn() { + @Override + public String getValue(StrippedLeaderboardDTO leaderboard) { + return leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : ""; + } + }; + + ImagesBarColumn leaderboardActionColumn = + new ImagesBarColumn( + new RaceLogTrackingEventManagementImagesBarCell(stringMessages)); + leaderboardActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, StrippedLeaderboardDTO leaderboardDTO, String value) { + if (RaceLogTrackingEventManagementImagesBarCell.ACTION_DENOTE_FOR_RACELOG_TRACKING.equals(value)) { + denoteForRaceLogTracking(leaderboardDTO); + } + } + }); + + leaderboardTable.addColumn(leaderboardNameColumn, stringMessages.name()); + leaderboardTable.addColumn(leaderboardDisplayNameColumn, stringMessages.displayName()); + leaderboardTable.addColumn(leaderboardActionColumn, stringMessages.actions()); + leaderboardTable.addColumnSortHandler(leaderboardColumnListHandler); + } + + private RaceLogTrackingState getTrackingState(RaceColumnDTOAndFleetDTOWithNameBasedEquality race) { + return race.getA().getRaceLogTrackingInfo(race.getB()).raceLogTrackingState; + } + + private boolean doesTrackerExist(RaceColumnDTOAndFleetDTOWithNameBasedEquality race) { + return race.getA().getRaceLogTrackingInfo(race.getB()).raceLogTrackerExists; + } + + private boolean doCompetitorResgistrationsExist(RaceColumnDTOAndFleetDTOWithNameBasedEquality race) { + return race.getA().getRaceLogTrackingInfo(race.getB()).competitorRegistrationsExists; + } + + @Override + protected void addColumnsToRacesTable(CellTable racesTable) { + TextColumn raceLogTrackingStateColumn = new TextColumn() { + @Override + public String getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnAndFleetName) { + RaceLogTrackingState state = getTrackingState(raceColumnAndFleetName); + return state.name(); + } + }; + + TextColumn trackerStateColumn = new TextColumn() { + @Override + public String getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnAndFleetName) { + return doesTrackerExist(raceColumnAndFleetName) ? "Active" : "None"; + } + }; + + ImagesBarColumn raceActionColumn = + new ImagesBarColumn( + new RaceLogTrackingEventManagementRaceImagesBarCell(stringMessages)); + raceActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, final RaceColumnDTOAndFleetDTOWithNameBasedEquality object, String value) { + final String leaderboardName = getSelectedLeaderboardName(); + final String raceColumnName = object.getA().getName(); + final String fleetName = object.getB().getName(); + boolean editable = ! (doesTrackerExist(object) && + getTrackingState(object) == RaceLogTrackingState.TRACKING); + if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_DENOTE_FOR_RACELOG_TRACKING.equals(value)) { + denoteForRaceLogTracking(object.getA(), object.getB()); + } else if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_REMOVE_DENOTATION.equals(value)) { + removeDenotation(object.getA(), object.getB()); + } else if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_COMPETITOR_REGISTRATIONS.equals(value)) { + new RaceLogTrackingCompetitorRegistrationsDialog(sailingService, stringMessages, errorReporter, + getSelectedLeaderboardName(), object.getA().getName(), object.getB().getName(), editable, + new Callback() { + @Override + public void onSuccess(Boolean result) { + object.getA().getRaceLogTrackingInfo(object.getB()).competitorRegistrationsExists = result; + } + + @Override + public void onFailure(Throwable reason) {} + }).show(); + } else if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_DEFINE_COURSE.equals(value)) { + new RaceLogTrackingCourseDefinitionDialog(sailingService, stringMessages, errorReporter, leaderboardName, raceColumnName, fleetName).show(); + } else if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_MAP_DEVICES.equals(value)) { + new RaceLogTrackingDeviceMappingsDialog(sailingService, stringMessages, errorReporter, leaderboardName, raceColumnName, fleetName).show(); + } else if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_COPY.equals(value)) { + List races = + new ArrayList<>(raceColumnTable.getDataProvider().getList()); + races.remove(object); + new SelectRacesDialog(sailingService, errorReporter, stringMessages, races, + new DialogCallback>() { + @Override + public void ok(Set editedObject) { + Set> toRaceLogs = new HashSet<>(); + for (RaceColumnDTOAndFleetDTOWithNameBasedEquality race : editedObject) { + toRaceLogs.add(toTriple(leaderboardName, race)); + } + sailingService.copyCourseAndCompetitorsToOtherRaceLogs( + toTriple(leaderboardName, object), toRaceLogs, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not copy course and competitors: " + caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + loadAndRefreshLeaderboard(leaderboardName, object.getA().getName()); + } + }); + + } + + @Override + public void cancel() {} + }).show(); + } else if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_SET_START_TIME.equals(value)) { + setStartTime(getSelectedRaceColumnWithFleet().getA(), getSelectedRaceColumnWithFleet().getB()); + } + } + }); + + racesTable.addColumn(raceLogTrackingStateColumn, stringMessages.raceStatusColumn()); + racesTable.addColumn(trackerStateColumn, stringMessages.trackerStatus()); + racesTable.addColumn(raceActionColumn, stringMessages.actions()); + racesTable.setWidth("600px"); + } + + private Util.Triple toTriple(String leaderboardName, + RaceColumnDTOAndFleetDTOWithNameBasedEquality race) { + return new Util.Triple(leaderboardName, race.getA().getName(), race.getB().getName()); + } + + @Override + protected void addLeaderboardConfigControls(Panel configPanel) {} + + @Override + protected void addLeaderboardCreateControls(Panel createPanel) {} + + @Override + protected void addSelectedLeaderboardRacesControls(Panel racesPanel) { + startTrackingButton = new Button(stringMessages.startTracking()); + startTrackingButton.setEnabled(false); + racesPanel.add(startTrackingButton); + startTrackingButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + startTracking(raceColumnTableSelectionModel.getSelectedSet()); + } + }); + + raceColumnTableSelectionModel.addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + enableStartTrackingButtonIfAppropriateRacesSelected(); + } + }); + } + + private void enableStartTrackingButtonIfAppropriateRacesSelected() { + boolean enable = raceColumnTableSelectionModel.getSelectedSet().size() > 0; + for (RaceColumnDTOAndFleetDTOWithNameBasedEquality race : raceColumnTableSelectionModel.getSelectedSet()) { + if (! getTrackingState(race).isForTracking() || doesTrackerExist(race)) { + enable = false; + } + } + startTrackingButton.setEnabled(enable); + } + + @Override + protected void leaderboardRaceColumnSelectionChanged() { + RaceColumnDTOAndFleetDTOWithNameBasedEquality selectedRaceInLeaderboard = getSelectedRaceColumnWithFleet(); + if (selectedRaceInLeaderboard != null) { + selectTrackedRaceInRaceList(); + } else { + trackedRacesListComposite.clearSelection(); + } + enableStartTrackingButtonIfAppropriateRacesSelected(); + } + + @Override + protected void leaderboardSelectionChanged() { + StrippedLeaderboardDTO selectedLeaderboard = getSelectedLeaderboard(); + if (leaderboardSelectionModel.getSelectedSet().size() == 1 && selectedLeaderboard != null) { + raceColumnTable.getDataProvider().getList().clear(); + for (RaceColumnDTO raceColumn : selectedLeaderboard.getRaceList()) { + for (FleetDTO fleet : raceColumn.getFleets()) { + raceColumnTable.getDataProvider().getList().add(new RaceColumnDTOAndFleetDTOWithNameBasedEquality(raceColumn, fleet)); + } + } + selectedLeaderBoardPanel.setVisible(true); + selectedLeaderBoardPanel.setCaptionText("Details of leaderboard '" + selectedLeaderboard.name + "'"); + if (!selectedLeaderboard.type.isMetaLeaderboard()) { + trackedRacesCaptionPanel.setVisible(true); + } + } else { + selectedLeaderBoardPanel.setVisible(false); + trackedRacesCaptionPanel.setVisible(false); + } + raceColumnTableSelectionModel.clear(); + } + + private void denoteForRaceLogTracking(final StrippedLeaderboardDTO leaderboard) { + sailingService.denoteForRaceLogTracking(leaderboard.name, new AsyncCallback() { + @Override + public void onSuccess(Void result) { + loadAndRefreshLeaderboard(leaderboard.name, null); + raceColumnTableSelectionModel.clear(); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not denote for RaceLog tracking: " + caught.getMessage()); + } + }); + } + + private void denoteForRaceLogTracking(final RaceColumnDTO raceColumn, final FleetDTO fleet) { + final StrippedLeaderboardDTO leaderboard = getSelectedLeaderboard(); + sailingService.denoteForRaceLogTracking(leaderboard.name, raceColumn.getName(), fleet.getName(), new AsyncCallback() { + @Override + public void onSuccess(Void result) { + loadAndRefreshLeaderboard(leaderboard.name, raceColumn.getName()); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not denote for RaceLog tracking: " + caught.getMessage()); + } + }); + } + + private void removeDenotation(final RaceColumnDTO raceColumn, final FleetDTO fleet) { + final StrippedLeaderboardDTO leaderboard = getSelectedLeaderboard(); + sailingService.removeDenotationForRaceLogTracking(leaderboard.name, raceColumn.getName(), fleet.getName(), new AsyncCallback() { + @Override + public void onSuccess(Void result) { + loadAndRefreshLeaderboard(leaderboard.name, raceColumn.getName()); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not remove denotation: " + caught.getMessage()); + } + }); + } + + private void startTracking(Set races) { + final StrippedLeaderboardDTO leaderboard = getSelectedLeaderboard(); + + //prompt user if competitor registrations are missing for same races + String namesOfRacesMissingRegistrations = ""; + for (RaceColumnDTOAndFleetDTOWithNameBasedEquality race : races) { + if (! doCompetitorResgistrationsExist(race)) { + namesOfRacesMissingRegistrations += race.getA().getName() + "/" + race.getB().getName() + " "; + } + } + if (! namesOfRacesMissingRegistrations.isEmpty()) { + boolean proceed = Window.confirm(stringMessages.competitorRegistrationsMissingProceed( + namesOfRacesMissingRegistrations)); + if (! proceed) { + return; + } + } + + for (RaceColumnDTOAndFleetDTOWithNameBasedEquality race : races) { + final RaceColumnDTO raceColumn = race.getA(); + final FleetDTO fleet = race.getB(); + + sailingService.startRaceLogTracking(leaderboard.name, raceColumn.getName(), fleet.getName(), + new AsyncCallback() { + @Override + public void onSuccess(Void result) { + loadAndRefreshLeaderboard(leaderboard.name, raceColumn.getName()); + trackedRacesListComposite.regattaRefresher.fillRegattas(); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Failed to start tracking " + raceColumn.getName() + " - " + + fleet.getName() + ": " + caught.getMessage()); + } + }); + } + } + + private void setStartTime(RaceColumnDTO raceColumnDTO, FleetDTO fleetDTO) { + new SetStartTimeDialog(sailingService, errorReporter, getSelectedLeaderboardName(), raceColumnDTO.getName(), + fleetDTO.getName(), stringMessages, new DataEntryDialog.DialogCallback() { + @Override + public void ok(RaceLogSetStartTimeAndProcedureDTO editedObject) { + sailingService.setStartTimeAndProcedure(editedObject, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage()); + } + + @Override + public void onSuccess(Boolean result) { + if (!result) { + Window.alert(stringMessages.failedToSetNewStartTime()); + } else { + trackedRacesListComposite.regattaRefresher.fillRegattas(); + } + } + }); + } + + @Override + public void cancel() { } + }).show(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementRaceImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementRaceImagesBarCell.java new file mode 100644 index 00000000000..52459167fc4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementRaceImagesBarCell.java @@ -0,0 +1,45 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.sap.sailing.gwt.ui.adminconsole.AbstractLeaderboardConfigPanel.RaceColumnDTOAndFleetDTOWithNameBasedEquality; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; + +public class RaceLogTrackingEventManagementRaceImagesBarCell extends ImagesBarCell { + public final static String ACTION_DENOTE_FOR_RACELOG_TRACKING = "ACTION_DENOTE_FOR_RACELOG_TRACKING"; + public final static String ACTION_REMOVE_DENOTATION = "ACTION_REMOVE_DENOTATION"; + public final static String ACTION_COMPETITOR_REGISTRATIONS = "ACTION_COMPETITOR_REGISTRATIONS"; + public final static String ACTION_DEFINE_COURSE = "ACTION_DEFINE_COURSE"; + public final static String ACTION_MAP_DEVICES = "ACTION_MAP_DEVICES"; + public final static String ACTION_COPY = "ACTION_COPY"; + public final static String ACTION_SET_START_TIME = "ACTION_SET_START_TIME"; + + private final StringMessages stringMessages; + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + + public RaceLogTrackingEventManagementRaceImagesBarCell(StringMessages stringConstants) { + super(); + this.stringMessages = stringConstants; + } + + @Override + protected Iterable getImageSpecs() { + List result = new ArrayList(); + RaceColumnDTOAndFleetDTOWithNameBasedEquality object = (RaceColumnDTOAndFleetDTOWithNameBasedEquality) getContext().getKey(); + if (! object.getA().getRaceLogTrackingInfo(object.getB()).raceLogTrackingState.isForTracking()) { + result.add(new ImageSpec(ACTION_DENOTE_FOR_RACELOG_TRACKING, stringMessages.denoteForRaceLogTracking(), makeImagePrototype(resources.denoteForRaceLogTracking()))); + } else { + result.add(new ImageSpec(ACTION_REMOVE_DENOTATION, stringMessages.removeDenotation(), makeImagePrototype(resources.unDenoteForRaceLogTracking()))); + result.add(new ImageSpec(ACTION_COMPETITOR_REGISTRATIONS, stringMessages.competitorRegistrations(), makeImagePrototype(resources.competitorRegistrations()))); + result.add(new ImageSpec(ACTION_DEFINE_COURSE, stringMessages.defineCourse(), makeImagePrototype(resources.defineCourse()))); + result.add(new ImageSpec(ACTION_MAP_DEVICES, stringMessages.mapDevices(), makeImagePrototype(resources.mapDevices()))); + result.add(new ImageSpec(ACTION_SET_START_TIME, stringMessages.setStartTime(), makeImagePrototype(resources.clockIcon()))); + result.add(new ImageSpec(ACTION_COPY, stringMessages.copyCourseAndCompetitors(), makeImagePrototype(resources.copy()))); + } + + return result; + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..865a14fbed0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceTableWrapper.java @@ -0,0 +1,64 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.core.client.GWT; +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; +import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorTemplates; +import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class RaceTableWrapper> +extends TableWrapper { + private final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); + private String selectedLeaderboardName; + + public RaceTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, + ErrorReporter errorReporter, S selectionModel) { + super(sailingService, stringMessages, errorReporter, selectionModel); + + Column raceNameColumn = + new Column(new AnchorCell()) { + @Override + public SafeHtml getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality raceInLeaderboardDTOAndFleetName) { + if (raceInLeaderboardDTOAndFleetName.getA().getRaceIdentifier(raceInLeaderboardDTOAndFleetName.getB()) != null) { + RegattaNameAndRaceName raceIdentifier = (RegattaNameAndRaceName) raceInLeaderboardDTOAndFleetName + .getA().getRaceIdentifier(raceInLeaderboardDTOAndFleetName.getB()); + Map params = new HashMap<>(); + params.put("leaderboardName", selectedLeaderboardName); + params.put("regattaName", raceIdentifier.getRegattaName()); + params.put("raceName", raceIdentifier.getRaceName()); + params.put("canReplayDuringLiveRaces", "true"); + String link = EntryPointLinkFactory.createRaceBoardLink(params); + return ANCHORTEMPLATE.cell(link, raceInLeaderboardDTOAndFleetName.getA().getRaceColumnName()); + } else { + return SafeHtmlUtils.fromString(raceInLeaderboardDTOAndFleetName.getA().getRaceColumnName()); + } + } + }; + + TextColumn fleetNameColumn = new TextColumn() { + @Override + public String getValue(RaceColumnDTOAndFleetDTOWithNameBasedEquality object) { + return object.getB().getName(); + } + }; + + table.addColumn(raceNameColumn, stringMessages.race()); + table.addColumn(fleetNameColumn, stringMessages.fleet()); + } + + public void setSelectedLeaderboardName(String name) { + this.selectedLeaderboardName = name; + } +} 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 index ff781a2dc3e..bb1203bee4d 100755 --- 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 @@ -20,12 +20,12 @@ 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.RegattaDisplayer; 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 RegattaDisplayer, RaceSelectionChangeListener { +public class RacesListBoxPanel extends FormPanel implements RegattasDisplayer, RaceSelectionChangeListener { private final List raceList; private final ListBox raceListBox; private final RegattaRefresher regattaRefresher; 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 835b823902e..2d32c597eec 100644 --- 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 @@ -28,19 +28,19 @@ import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.impl.NaturalComparator; import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.RegattaDisplayer; 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.panels.LabeledAbstractFilterablePanel; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; -public class RegattaListComposite extends Composite implements RegattaDisplayer { +public class RegattaListComposite extends Composite implements RegattasDisplayer { private final SelectionModel regattaSelectionModel; private final CellTable regattaTable; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaStructureManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaStructureManagementPanel.java index 6209c61820b..e2286d24d44 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaStructureManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaStructureManagementPanel.java @@ -21,11 +21,11 @@ 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.ErrorReporter; -import com.sap.sailing.gwt.ui.client.RegattaDisplayer; 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; import com.sap.sailing.gwt.ui.shared.EventDTO; @@ -40,7 +40,7 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; * @author Frank Mittag (C5163974) * */ -public class RegattaStructureManagementPanel extends SimplePanel implements RegattaDisplayer, RegattaSelectionChangeListener { +public class RegattaStructureManagementPanel extends SimplePanel implements RegattasDisplayer, RegattaSelectionChangeListener { private final SailingServiceAsync sailingService; private final ErrorReporter errorReporter; private final StringMessages stringMessages; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RemoteSailingServerInstancesManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RemoteSailingServerInstancesManagementPanel.java new file mode 100644 index 00000000000..7871c675d8b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RemoteSailingServerInstancesManagementPanel.java @@ -0,0 +1,205 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gwt.cell.client.SafeHtmlCell; +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.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Label; +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.ErrorReporter; +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.dialog.DataEntryDialog.DialogCallback; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; + +public class RemoteSailingServerInstancesManagementPanel extends FlowPanel { + private final SailingServiceAsync sailingService; + private final ErrorReporter errorReporter; + private final StringMessages stringMessages; + + private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); + private final MultiSelectionModel serverSelectionModel; + private LabeledAbstractFilterablePanel filteredServerTable; + + public RemoteSailingServerInstancesManagementPanel(SailingServiceAsync sailingService, ErrorReporter errorReporter, + StringMessages stringMessages) { + this.sailingService = sailingService; + this.errorReporter = errorReporter; + this.stringMessages = stringMessages; + + TextColumn serverNameColumn = new TextColumn() { + @Override + public String getValue(RemoteSailingServerReferenceDTO server) { + return server.getName() != null ? server.getName() : ""; + } + }; + TextColumn serverUrlColumn = new TextColumn() { + @Override + public String getValue(RemoteSailingServerReferenceDTO server) { + return server.getUrl() != null ? server.getUrl() : ""; + } + }; + final SafeHtmlCell eventsCell = new SafeHtmlCell(); + Column eventsOrErrorColumn = new Column(eventsCell) { + @Override + public SafeHtml getValue(RemoteSailingServerReferenceDTO server) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + final Iterable events = server.getEvents(); + if (events != null) { + for (EventBaseDTO event : events) { + builder.appendEscaped(event.getName()); + builder.appendHtmlConstant("
"); + } + } else { + builder.appendEscaped(RemoteSailingServerInstancesManagementPanel.this.stringMessages + .errorAddingSailingServer(server.getLastErrorMessage())); + } + return builder.toSafeHtml(); + } + }; + CellTable serverTable = new CellTable(10000, tableRes); + serverTable.addColumn(serverNameColumn, stringMessages.name()); + serverTable.addColumn(serverUrlColumn, stringMessages.url()); + serverTable.addColumn(eventsOrErrorColumn, stringMessages.events()); + + serverTable.setEmptyTableWidget(new Label(stringMessages.noSailingServerInstancesYet())); + + serverSelectionModel = new MultiSelectionModel(); + serverTable.setSelectionModel(serverSelectionModel); + + ListDataProvider serverDataProvider = new ListDataProvider(); + serverDataProvider.addDataDisplay(serverTable); + + Button addButton = new Button(stringMessages.add()); + Button removeButton = new Button(stringMessages.remove()); + Button refreshButton = new Button(stringMessages.refresh()); + addButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + addRemoteSailingServerReference(); + } + }); + removeButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + removeSelectedSailingServers(); + } + }); + refreshButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + refreshSailingServerList(); + } + }); + VerticalPanel vp = new VerticalPanel(); + HorizontalPanel providerSelectionPanel = new HorizontalPanel(); + providerSelectionPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); + vp.add(providerSelectionPanel); + + filteredServerTable = new LabeledAbstractFilterablePanel( + new Label(stringMessages.registeredSailingServerInstances()), Collections.emptyList(), serverTable, serverDataProvider) { + @Override + public List getSearchableStrings(RemoteSailingServerReferenceDTO t) { + List strings = new ArrayList(); + strings.add(t.getName()); + strings.add(t.getUrl()); + if (t.getEvents() != null) { + for (EventBaseDTO e : t.getEvents()) { + strings.add(e.getName()); + } + } + return strings; + } + }; + providerSelectionPanel.add(filteredServerTable); + vp.add(serverTable); + HorizontalPanel buttonPanel = new HorizontalPanel(); + buttonPanel.add(addButton); + buttonPanel.add(removeButton); + buttonPanel.add(refreshButton); + vp.add(buttonPanel); + + add(vp); + refreshSailingServerList(); + } + + private void refreshSailingServerList() { + sailingService.getRemoteSailingServerReferences(new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorRefreshingSailingServers(caught.getMessage())); + } + + @Override + public void onSuccess(List result) { + filteredServerTable.updateAll(result); + } + }); + } + + private void removeSelectedSailingServers() { + Set toRemove = new HashSet(); + for (RemoteSailingServerReferenceDTO selectedServer: serverSelectionModel.getSelectedSet()) { + toRemove.add(selectedServer.getName()); + } + + sailingService.removeSailingServers(toRemove, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorRemovingSailingServers(caught.getMessage())); + } + + @Override + public void onSuccess(Void result) { + refreshSailingServerList(); + Window.setStatus(stringMessages.successfullyUpdatedSailingServers()); + } + }); + } + + private void addRemoteSailingServerReference() { + SailingServerCreateOrEditDialog dialog = new SailingServerCreateOrEditDialog(filteredServerTable.getAll(), stringMessages, new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(final RemoteSailingServerReferenceDTO server) { + sailingService.addRemoteSailingServerReference(server, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorAddingSailingServer(caught.getMessage())); + } + + @Override + public void onSuccess(RemoteSailingServerReferenceDTO result) { + filteredServerTable.add(result); + Window.setStatus(stringMessages.successfullyUpdatedSailingServers()); + } + }); + } + }); + dialog.show(); + } +} 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 ded0c7aad27..00226b4eb71 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 @@ -14,13 +14,13 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.ReplicaDTO; import com.sap.sailing.gwt.ui.shared.ReplicationMasterDTO; import com.sap.sailing.gwt.ui.shared.ReplicationStateDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.controls.IntegerBox; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -145,9 +145,9 @@ public class ReplicationPanel extends FlowPanel { private void addReplication() { AddReplicationDialog dialog = new AddReplicationDialog(null, - new DialogCallback, Integer, Integer>>() { + new DialogCallback, Integer, Integer>>() { @Override - public void ok(final Triple, Integer, Integer> masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber) { + public void ok(final Util.Triple, Integer, Integer> masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber) { registeredMasters.removeRow(0); registeredMasters.insertRow(0); registeredMasters.setWidget(0, 0, new Label(stringMessages.loading())); @@ -275,15 +275,15 @@ public class ReplicationPanel extends FlowPanel { * @author Axel Uhl (d043530) * */ - private class AddReplicationDialog extends DataEntryDialog, Integer, Integer>> { + private class AddReplicationDialog extends DataEntryDialog, Integer, Integer>> { private final TextBox hostnameEntryField; private final TextBox exchangeHostnameEntryField; private final TextBox exchangenameEntryField; private final IntegerBox messagingPortField; private final IntegerBox servletPortField; - public AddReplicationDialog(final Validator, Integer, Integer>> validator, - final DialogCallback, Integer, Integer>> callback) { + public AddReplicationDialog(final Validator, Integer, Integer>> validator, + final DialogCallback, Integer, Integer>> callback) { super(stringMessages.connect(), stringMessages.enterMaster(), stringMessages.ok(), stringMessages.cancel(), validator, callback); hostnameEntryField = createTextBox("localhost"); @@ -329,9 +329,9 @@ public class ReplicationPanel extends FlowPanel { } @Override - protected Triple, Integer, Integer> getResult() { - return new Triple, Integer, Integer>( - new Triple(exchangeHostnameEntryField.getText(), hostnameEntryField.getText(), exchangenameEntryField.getText()), + protected Util.Triple, Integer, Integer> getResult() { + return new Util.Triple, Integer, Integer>( + new Util.Triple(exchangeHostnameEntryField.getText(), hostnameEntryField.getText(), exchangenameEntryField.getText()), messagingPortField.getValue(), servletPortField.getValue()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingEventManagementPanel.java deleted file mode 100644 index 8a9ae7a445d..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingEventManagementPanel.java +++ /dev/null @@ -1,488 +0,0 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.UUID; - -import com.google.gwt.cell.client.AbstractCell; -import com.google.gwt.cell.client.FieldUpdater; -import com.google.gwt.cell.client.SafeHtmlCell; -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.client.SafeHtmlTemplates; -import com.google.gwt.safehtml.shared.SafeHtml; -import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -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; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.HorizontalPanel; -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.impl.NaturalComparator; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.EventRefresher; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.URLEncoder; -import com.sap.sailing.gwt.ui.client.shared.panels.LabeledAbstractFilterablePanel; -import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; -import com.sap.sailing.gwt.ui.shared.EventDTO; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; -import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; - -/** - * Allows administrators to manage data of a sailing event. This is a temporary panel because the managed event - * is not connected with any regatta and it used only to manage of basic attributes of the an event. - * @author Frank Mittag (C5163974) - * - */ -public class SailingEventManagementPanel extends SimplePanel implements EventRefresher { - private final SailingServiceAsync sailingService; - private final ErrorReporter errorReporter; - private final StringMessages stringMessages; - - private CellTable eventTable; - private MultiSelectionModel eventSelectionModel; - private ListDataProvider eventProvider; - private List allEvents; - private LabeledAbstractFilterablePanel filterTextbox; - private Button removeEventsButton; - private TextColumn startEndDateColumn; - - public static class AnchorCell extends AbstractCell { - @Override - public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) { - sb.append(safeHtml); - } - } - - interface AnchorTemplates extends SafeHtmlTemplates { - @SafeHtmlTemplates.Template("{1}") - SafeHtml cell(String url, String displayName); - } - - private static AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); - - private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); - - public SailingEventManagementPanel(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, - final StringMessages stringMessages) { - this.sailingService = sailingService; - this.stringMessages = stringMessages; - this.errorReporter = errorReporter; - - VerticalPanel mainPanel = new VerticalPanel(); - this.setWidget(mainPanel); - mainPanel.setWidth("100%"); - - HorizontalPanel eventsPanel = new HorizontalPanel(); - eventsPanel.setSpacing(5); - mainPanel.add(eventsPanel); - - Button refreshButton = new Button(stringMessages.refresh()); - refreshButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - fillEvents(); - } - }); - eventsPanel.add(refreshButton); - - Button createEventBtn = new Button(stringMessages.actionAddEvent()); - createEventBtn.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - openCreateEventDialog(); - } - }); - eventsPanel.add(createEventBtn); - - removeEventsButton = new Button(stringMessages.remove()); - removeEventsButton.setEnabled(false); - removeEventsButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (Window.confirm(stringMessages.doYouReallyWantToRemoveEvents())) { - removeEvents(eventSelectionModel.getSelectedSet()); - } - } - }); - eventsPanel.add(removeEventsButton); - - // sailing events table - AnchorCell anchorCell = new AnchorCell(); - Column eventNameColumn = new Column(anchorCell) { - @Override - public SafeHtml getValue(EventDTO event) { - String debugParam = Window.Location.getParameter("gwt.codesvr"); - String link = URLEncoder.encode("/gwt/RegattaOverview.html?event=" + event.id - + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); - return ANCHORTEMPLATE.cell(link, event.getName()); - } - }; - - TextColumn venueNameColumn = new TextColumn() { - @Override - public String getValue(EventDTO event) { - return event.venue != null ? event.venue.getName() : ""; - } - }; - - startEndDateColumn = new TextColumn() { - @Override - public String getValue(EventDTO event) { - return DateAndTimeFormatterUtil.formatDateRange(event.startDate, event.endDate); - } - }; - - TextColumn isPublicColumn = new TextColumn() { - @Override - public String getValue(EventDTO event) { - return event.isPublic ? stringMessages.yes() : stringMessages.no(); - } - }; - - final SafeHtmlCell courseAreasCell = new SafeHtmlCell(); - Column courseAreasColumn = new Column(courseAreasCell) { - @Override - public SafeHtml getValue(EventDTO event) { - SafeHtmlBuilder builder = new SafeHtmlBuilder(); - int courseAreasCount = event.venue.getCourseAreas().size(); - int i = 1; - for (CourseAreaDTO courseArea : event.venue.getCourseAreas()) { - builder.appendEscaped(courseArea.getName()); - if (i < courseAreasCount) { - builder.appendHtmlConstant("
"); - } - i++; - } - return builder.toSafeHtml(); - } - }; - - final SafeHtmlCell associatedRegattasCell = new SafeHtmlCell(); - Column associatedRegattasColumn = new Column(associatedRegattasCell) { - @Override - public SafeHtml getValue(EventDTO event) { - SafeHtmlBuilder builder = new SafeHtmlBuilder(); - int regattaCount = event.regattas.size(); - int i = 1; - for (RegattaDTO regatta : event.regattas) { - builder.appendEscaped(regatta.getName()); - if (i < regattaCount) { - builder.appendHtmlConstant("
"); - } - i++; - } - return builder.toSafeHtml(); - } - }; - - ImagesBarColumn eventActionColumn = new ImagesBarColumn( - new EventConfigImagesBarCell(stringMessages)); - eventActionColumn.setFieldUpdater(new FieldUpdater() { - @Override - public void update(int index, EventDTO event, String value) { - if (EventConfigImagesBarCell.ACTION_REMOVE.equals(value)) { - if (Window.confirm(stringMessages.doYouReallyWantToRemoveEvent(event.getName()))) { - removeEvent(event); - } - } else if (EventConfigImagesBarCell.ACTION_EDIT.equals(value)) { - openEditEventDialog(event); - } - } - }); - - eventNameColumn.setSortable(true); - venueNameColumn.setSortable(true); - isPublicColumn.setSortable(true); - startEndDateColumn.setSortable(true); - courseAreasColumn.setSortable(true); - - eventTable = new CellTable(10000, tableRes); - eventTable.addColumn(eventNameColumn, stringMessages.event()); - eventTable.addColumn(venueNameColumn, stringMessages.venue()); - eventTable.addColumn(startEndDateColumn, stringMessages.from() + "/" + stringMessages.to()); - eventTable.addColumn(isPublicColumn, stringMessages.isPublic()); - eventTable.addColumn(courseAreasColumn, stringMessages.courseAreas()); - eventTable.addColumn(associatedRegattasColumn, stringMessages.regattas()); - eventTable.addColumn(eventActionColumn, stringMessages.actions()); - - eventSelectionModel = new MultiSelectionModel(); - eventSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - removeEventsButton.setEnabled(!eventSelectionModel.getSelectedSet().isEmpty()); - } - }); - eventTable.setSelectionModel(eventSelectionModel); - - eventProvider = new ListDataProvider(); - eventProvider.addDataDisplay(eventTable); - mainPanel.add(eventTable); - allEvents = new ArrayList(); - eventTable.addColumnSortHandler(getEventTableColumnSortHandler(eventProvider.getList(), eventNameColumn, - venueNameColumn, startEndDateColumn, isPublicColumn, courseAreasColumn)); - filterTextbox = new LabeledAbstractFilterablePanel(new Label("Filter events by name: "), allEvents, - eventTable, eventProvider) { - - @Override - public Iterable getSearchableStrings(EventDTO t) { - List result = new ArrayList(); - result.add(t.getName()); - result.add(t.venue.getName()); - for (CourseAreaDTO c : t.venue.getCourseAreas()) { - result.add(c.getName()); - } - return result; - } - }; - eventsPanel.add(filterTextbox); - fillEvents(); - } - - private ListHandler getEventTableColumnSortHandler(List eventRecords, - Column eventNameColumn, TextColumn venueNameColumn, - TextColumn startEndDateColumn, TextColumn isPublicColumn, - Column courseAreasColumn) { - ListHandler result = new ListHandler(eventRecords); - result.setComparator(eventNameColumn, new Comparator() { - @Override - public int compare(EventDTO e1, EventDTO e2) { - return new NaturalComparator().compare(e1.getName(), e2.getName()); - } - }); - result.setComparator(venueNameColumn, new Comparator() { - @Override - public int compare(EventDTO e1, EventDTO e2) { - return new NaturalComparator().compare(e1.venue.getName(), e2.venue.getName()); - } - }); - result.setComparator(startEndDateColumn, new Comparator() { - @Override - public int compare(EventDTO e1, EventDTO e2) { - int result; - if(e1.startDate != null && e2.startDate != null) { - result = e2.startDate.compareTo(e1.startDate); - } else if(e1.startDate == null && e2.startDate != null) { - result = 1; - } else if(e1.startDate != null && e2.startDate == null) { - result = -1; - } else { - result = 0; - } - return result; - } - }); - result.setComparator(isPublicColumn, new Comparator() { - @Override - public int compare(EventDTO e1, EventDTO e2) { - return e1.isPublic == e2.isPublic ? 0 : e1.isPublic ? 1 : -1; - } - }); - result.setComparator(courseAreasColumn, new Comparator() { - @Override - public int compare(EventDTO e1, EventDTO e2) { - return new NaturalComparator().compare(Arrays.toString(e1.venue.getCourseAreas().toArray()), - Arrays.toString(e2.venue.getCourseAreas().toArray())); - } - }); - - return result; - - } - - private void removeEvents(Collection events) { - if (!events.isEmpty()) { - Collection eventIds = new HashSet(); - for (EventDTO event : events) { - eventIds.add(event.id); - } - sailingService.removeEvents(eventIds, new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to remove the events:" + caught.getMessage()); - } - @Override - public void onSuccess(Void result) { - fillEvents(); - } - }); - } - } - - private void removeEvent(final EventDTO event) { - sailingService.removeEvent(event.id, new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to remove event " + event.getName() + ": " + caught.getMessage()); - } - - @Override - public void onSuccess(Void result) { - fillEvents(); - } - }); - } - - private void openCreateEventDialog() { - List existingEvents = new ArrayList(eventProvider.getList()); - EventCreateDialog dialog = new EventCreateDialog(Collections.unmodifiableCollection(existingEvents), stringMessages, - new DialogCallback() { - @Override - public void cancel() { - } - - @Override - public void ok(EventDTO newEvent) { - createNewEvent(newEvent); - } - }); - dialog.show(); - } - - private void openEditEventDialog(final EventDTO selectedEvent) { - List existingEvents = new ArrayList(eventProvider.getList()); - existingEvents.remove(selectedEvent); - EventEditDialog dialog = new EventEditDialog(selectedEvent, Collections.unmodifiableCollection(existingEvents), stringMessages, - new DialogCallback() { - @Override - public void cancel() { - } - - @Override - public void ok(EventDTO updatedEvent) { - updateEvent(selectedEvent, updatedEvent); - } - }); - dialog.show(); - } - - private void updateEvent(final EventDTO oldEvent, final EventDTO updatedEvent) { - List regattaNames = new ArrayList(); - List courseAreasToAdd = getCourseAreasToAdd(oldEvent, updatedEvent); - - sailingService.updateEvent(oldEvent.id, oldEvent.getName(), updatedEvent.startDate, updatedEvent.endDate, updatedEvent.venue, - updatedEvent.isPublic, regattaNames, new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to update sailing event" + oldEvent.getName() + ": " + t.getMessage()); - } - - @Override - public void onSuccess(Void result) { - fillEvents(); - if (!oldEvent.getName().equals(updatedEvent.getName())) { - sailingService.renameEvent(oldEvent.id, updatedEvent.getName(), new AsyncCallback() { - @Override - public void onSuccess(Void result) { - fillEvents(); - } - - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to rename sailing event " + oldEvent.getName() + ": " + t.getMessage()); - - } - }); - } - } - }); - - if (courseAreasToAdd.size() > 0) { - for (CourseAreaDTO courseArea : courseAreasToAdd) { - sailingService.createCourseArea(oldEvent.id, courseArea.getName(), new AsyncCallback() { - - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to add course area to sailing event " + oldEvent.getName() + ": " + t.getMessage()); - } - - @Override - public void onSuccess(CourseAreaDTO result) { - fillEvents(); - } - - }); - } - } - } - - private List getCourseAreasToAdd(final EventDTO oldEvent, final EventDTO updatedEvent) { - List courseAreasToAdd = new ArrayList(); - List courseAreaNamesToAdd = new ArrayList(); - - List newCourseAreaNames = new ArrayList(); - for (CourseAreaDTO courseArea : updatedEvent.venue.getCourseAreas()) { - newCourseAreaNames.add(courseArea.getName()); - } - - List oldCourseAreaNames = new ArrayList(); - for (CourseAreaDTO courseArea : oldEvent.venue.getCourseAreas()) { - oldCourseAreaNames.add(courseArea.getName()); - } - - for (String newCourseAreaName : newCourseAreaNames) { - if (!oldCourseAreaNames.contains(newCourseAreaName)) - courseAreaNamesToAdd.add(newCourseAreaName); - } - - for (CourseAreaDTO courseArea : updatedEvent.venue.getCourseAreas()) { - if (courseAreaNamesToAdd.contains(courseArea.getName())) { - courseAreasToAdd.add(courseArea); - } - } - - return courseAreasToAdd; - } - - private void createNewEvent(final EventDTO newEvent) { - List courseAreaNames = new ArrayList(); - for (CourseAreaDTO courseAreaDTO : newEvent.venue.getCourseAreas()) { - courseAreaNames.add(courseAreaDTO.getName()); - } - sailingService.createEvent(newEvent.getName(), newEvent.startDate, newEvent.endDate, newEvent.venue.getName(), - newEvent.isPublic, courseAreaNames, new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to create new event" + newEvent.getName() + ": " + t.getMessage()); - } - - @Override - public void onSuccess(EventDTO newEvent) { - fillEvents(); - } - }); - } - - public void fillEvents() { - sailingService.getEvents(new AsyncCallback>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Remote Procedure Call getEvents() - Failure: " + caught.getMessage()); - } - - @Override - public void onSuccess(List result) { - allEvents.clear(); - allEvents.addAll(result); - eventTable.getColumnSortList().push(startEndDateColumn); - filterTextbox.updateAll(allEvents); - } - }); - } -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingServerCreateOrEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingServerCreateOrEditDialog.java new file mode 100644 index 00000000000..fe41a120324 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingServerCreateOrEditDialog.java @@ -0,0 +1,102 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.RemoteSailingServerReferenceDTO; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class SailingServerCreateOrEditDialog extends DataEntryDialog { + private StringMessages stringConstants; + private TextBox nameTextBox; + private TextBox urlTextBox; + + private static class SailingServerValidator implements Validator { + private StringMessages stringConstants; + private Iterable existingSailingServers; + + public SailingServerValidator(StringMessages stringConstants, Iterable existingSailingServers) { + this.stringConstants = stringConstants; + this.existingSailingServers = existingSailingServers; + } + + @Override + public String getErrorMessage(RemoteSailingServerReferenceDTO serverToValidate) { + String errorMessage = null; + boolean nameNotEmpty = serverToValidate.getName() != null && serverToValidate.getName().length() > 0; + boolean urlNotEmpty = serverToValidate.getUrl() != null && serverToValidate.getUrl().length() > 0; + boolean unique = true; + for (RemoteSailingServerReferenceDTO event : existingSailingServers) { + if (event.getName().equals(serverToValidate.getName())) { + unique = false; + break; + } + } + if (!nameNotEmpty) { + errorMessage = stringConstants.pleaseEnterAName(); + } else if (!urlNotEmpty) { + errorMessage = stringConstants.pleaseEnterNonEmptyUrl(); + } else if (!unique) { + errorMessage = stringConstants.eventWithThisNameAlreadyExists(); + } + return errorMessage; + } + } + + public SailingServerCreateOrEditDialog(Iterable existingSailingServers, + StringMessages stringConstants, DialogCallback callback) { + this(existingSailingServers, null, false, stringConstants, callback); + } + + public SailingServerCreateOrEditDialog(Iterable existingSailingServers, RemoteSailingServerReferenceDTO serverToEdit, StringMessages stringConstants, + DialogCallback callback) { + this(existingSailingServers, serverToEdit, true, stringConstants, callback); + } + + private SailingServerCreateOrEditDialog(Iterable existingSailingServers, RemoteSailingServerReferenceDTO serverToEdit, boolean isEditMode, StringMessages stringConstants, + DialogCallback callback) { + super("Sailing Server", null, stringConstants.ok(), stringConstants.cancel(), + new SailingServerValidator(stringConstants, existingSailingServers), callback); + this.stringConstants = stringConstants; + + nameTextBox = createTextBox(isEditMode ? serverToEdit.getName() : null); + nameTextBox.setVisibleLength(50); + urlTextBox = createTextBox(isEditMode ? serverToEdit.getUrl() : null); + urlTextBox.setVisibleLength(100); + nameTextBox.setEnabled(!isEditMode); + } + + @Override + protected RemoteSailingServerReferenceDTO getResult() { + return new RemoteSailingServerReferenceDTO(nameTextBox.getText(), urlTextBox.getText()); + } + + @Override + protected Widget getAdditionalWidget() { + final VerticalPanel panel = new VerticalPanel(); + Widget additionalWidget = super.getAdditionalWidget(); + if (additionalWidget != null) { + panel.add(additionalWidget); + } + + Grid formGrid = new Grid(2, 2); + panel.add(formGrid); + + formGrid.setWidget(0, 0, new Label(stringConstants.name() + ":")); + formGrid.setWidget(0, 1, nameTextBox); + formGrid.setWidget(1, 0, new Label(stringConstants.url() + ":")); + formGrid.setWidget(1, 1, urlTextBox); + + return panel; + } + + @Override + public void show() { + super.show(); + nameTextBox.setFocus(true); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SelectRacesDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SelectRacesDialog.java new file mode 100644 index 00000000000..ee49e8cee09 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SelectRacesDialog.java @@ -0,0 +1,58 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Collection; +import java.util.Set; + +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.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class SelectRacesDialog extends DataEntryDialog> { + private final RaceTableWrapper> + racesTable; + + public SelectRacesDialog(SailingServiceAsync sailingService, ErrorReporter errorReporter, final StringMessages stringMessages, + Collection races, + DialogCallback> callback) { + super(stringMessages.selectRaces(), stringMessages.selectRaces(), stringMessages.ok(), stringMessages.cancel(), + new Validator>() { + @Override + public String getErrorMessage(Set valueToValidate) { + if (valueToValidate.isEmpty()) { + return stringMessages.selectAtLeastOne(); + } + return null; + } + + }, true, callback); + + racesTable = new RaceTableWrapper>( + sailingService, stringMessages, errorReporter, new MultiSelectionModel()); + + racesTable.getDataProvider().getList().addAll(races); + + racesTable.getSelectionModel().addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + validate(); + } + }); + } + + @Override + protected Widget getAdditionalWidget() { + return racesTable.getTable(); + } + + @Override + protected Set getResult() { + return racesTable.getSelectionModel().getSelectedSet(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java index 3dfb749179e..a3bb7970e8a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java @@ -247,7 +247,7 @@ public class SeriesEditDialog extends DataEntryDialog { private final Label addRacesHintLabel; public RaceNamesEditorUi(RegattaDTO regatta, StringMessages stringMessages, ImageResource removeImage, String seriesName) { - super(stringMessages, removeImage, Collections.emptyList()); + super(stringMessages, removeImage, /* suggest values */ Collections.emptyList()); this.seriesName = seriesName; this.regatta = regatta; @@ -344,7 +344,7 @@ public class SeriesEditDialog extends DataEntryDialog { HorizontalPanel addRacesPanel = new HorizontalPanel(); addRacesPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); addRacesPanel.setSpacing(5); - addRacesPanel.add(new Label("Add races")); + addRacesPanel.add(new Label(stringMessages.addRaces())); for(int i = 1; i <= 50; i++) { addRacesFromListBox.addItem("" + i); @@ -366,9 +366,9 @@ public class SeriesEditDialog extends DataEntryDialog { }); addRacesPanel.add(addRacesFromListBox); - addRacesPanel.add(new Label("to")); + addRacesPanel.add(new Label(stringMessages.to())); addRacesPanel.add(addRacesToListBox); - addRacesPanel.add(new Label("with name prefix")); + addRacesPanel.add(new Label(stringMessages.withNamePrefix())); raceNamePrefixTextBox.setWidth("20px"); if ("Default".equals(seriesName)) { @@ -390,16 +390,14 @@ public class SeriesEditDialog extends DataEntryDialog { @Override public void onClick(ClickEvent event) { SeriesDTO selectedSeries = getSelectedSeries(); - if(selectedSeries != null) { + if (selectedSeries != null) { List raceNamesToAdd = resolveRaceNamesToAdd(); - for(String raceToAdd: raceNamesToAdd) { + for (String raceToAdd : raceNamesToAdd) { addValue(raceToAdd); - } + } validate(); -// updateFromToListboxesSelection(); -// updateHintLabel(); } else { - Window.alert("Please select a series first."); + Window.alert(stringMessages.pleaseSelectASeriesFirst()); } } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java index 7b52b28bfbc..4c1c99253a9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java @@ -23,7 +23,6 @@ import com.google.gwt.user.client.ui.PushButton; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -157,10 +156,10 @@ public class SetStartTimeDialog extends DataEntryDialog>() { + service.getStartTimeAndProcedure(leaderboardName, raceColumnName, fleetName, new AsyncCallback>() { @Override - public void onSuccess(Util.Triple result) { + public void onSuccess(com.sap.sse.common.Util.Triple result) { if (result == null) { currentStartTimeLabel.setText(stringMessages.notAvailable()); currentPassIdBox.setText(stringMessages.notAvailable()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetTimePointDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetTimePointDialog.java new file mode 100644 index 00000000000..a0415d4f82f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetTimePointDialog.java @@ -0,0 +1,70 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Date; + +import com.github.gwtbootstrap.datetimepicker.client.ui.base.HasViewMode.ViewMode; +import com.google.gwt.event.logical.shared.AttachEvent; +import com.google.gwt.event.logical.shared.AttachEvent.Handler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class SetTimePointDialog extends DataEntryDialog { + private final BetterDateTimeBox time; + private final StringMessages stringMessages; + + public SetTimePointDialog(final StringMessages stringMessages, String title, DialogCallback callback) { + super(title, title, stringMessages.ok(), stringMessages.cancel(), new DataEntryDialog.Validator() { + @Override + public String getErrorMessage(Date valueToValidate) { + if (valueToValidate == null) return stringMessages.pleaseEnterA(stringMessages.time()); + return null; + } + }, callback); + + this.stringMessages = stringMessages; + + time = createTimeBox(); + } + + private BetterDateTimeBox createTimeBox() { + final BetterDateTimeBox timeBox = new BetterDateTimeBox(); + timeBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + validate(); + } + }); + timeBox.addAttachHandler(new Handler() { + @Override + public void onAttachOrDetach(AttachEvent event) { + if (event.isAttached()) { + addAutoHidePartner(timeBox.getPicker()); + } + } + }); + timeBox.setAutoClose(true); + timeBox.setStartView(ViewMode.HOUR); + timeBox.setFormat("dd/mm/yyyy hh:ii"); + return timeBox; + } + + @Override + protected Widget getAdditionalWidget() { + Grid grid = new Grid(1, 2); + grid.setWidget(0, 0, new Label(stringMessages.time())); + grid.setWidget(0, 1, time); + return grid; + } + + @Override + protected Date getResult() { + return time.getValue(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SuggestedCourseAreaNames.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SuggestedCourseAreaNames.java new file mode 100755 index 00000000000..2edd66db8db --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SuggestedCourseAreaNames.java @@ -0,0 +1,10 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Arrays; +import java.util.List; + +public interface SuggestedCourseAreaNames { + static final List suggestedCourseAreaNames = Arrays.asList("Alpha", "Bravo", "Charly", "Delta", "Echo", + "Foxtrott", "Golf", "Hotel", "India", "Juliett", "Stadium", "Offshore"); + +} 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 5b92481b772..497a670c3c2 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 @@ -39,11 +39,11 @@ 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.panels.LabeledAbstractFilterablePanel; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingEventRecordDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; /** * Allows the user to start and stop tracking of races using the SwissTiming connector. In particular, @@ -102,7 +102,6 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane fillConfigurations(); jsonUrlBox = new TextBox(); - jsonUrlBox.setText("test"); // by using 'test' as the JSON URL you will get some useful content from a static json file jsonUrlBox.getElement().getStyle().setWidth(50, Unit.EM); connectionsGrid.setWidget(0, 0, new Label(stringMessages.swissTimingEvents() + ":")); @@ -135,6 +134,20 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane } }; + TextColumn regattaNameColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.regattaName; + } + }; + + TextColumn seriesNameColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.seriesName; + } + }; + TextColumn raceIdColumn = new TextColumn() { @Override public String getValue(SwissTimingRaceRecordDTO object) { @@ -156,15 +169,12 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane } }; - // AXEL: DON'T DELETE -// TextColumn stateColumn = new TextColumn() { -// @Override -// public String getValue(SwissTimingRaceRecordDTO object) { -// String result = object.hasCourse ? "C✓ " : ""; -// result += object.hasStartlist ? "S✓" : ""; -// return result; -// } -// }; + TextColumn raceStatusColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.raceStatus != null ? object.raceStatus : ""; + } + }; TextColumn raceStartTimeColumn = new TextColumn() { @Override @@ -229,11 +239,15 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane boatClassColumn.setSortable(true); raceIdColumn.setSortable(true); genderColumn.setSortable(true); + raceStatusColumn.setSortable(true); AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); raceTable = new CellTable(/* pageSize */ 10000, tableRes); + raceTable.addColumn(regattaNameColumn, stringConstants.regatta()); + raceTable.addColumn(seriesNameColumn, stringConstants.series()); raceTable.addColumn(raceNameColumn, stringConstants.name()); - raceTable.addColumn(raceIdColumn, stringConstants.id()); + //raceTable.addColumn(raceIdColumn, stringConstants.id()); + raceTable.addColumn(raceStatusColumn, stringConstants.status()); raceTable.addColumn(boatClassColumn, stringConstants.boatClass()); raceTable.addColumn(genderColumn, "Gender"); raceTable.addColumn(raceStartTimeColumn, stringConstants.startTime()); @@ -244,8 +258,8 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane raceList = new ListDataProvider(); raceList.addDataDisplay(raceTable); - Handler columnSortHandler = getRaceTableColumnSortHandler(raceList.getList(), raceNameColumn, raceStartTimeColumn, - raceIdColumn, boatClassColumn, genderColumn); + Handler columnSortHandler = getRaceTableColumnSortHandler(raceList.getList(), regattaNameColumn, seriesNameColumn, + raceNameColumn, raceStartTimeColumn, raceIdColumn, boatClassColumn, genderColumn, raceStatusColumn); raceTable.addColumnSortHandler(columnSortHandler); trackedRacesPanel.add(trackedRacesListComposite); @@ -254,10 +268,16 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane availableSwissTimingRaces, raceTable, raceList) { @Override public Iterable getSearchableStrings(SwissTimingRaceRecordDTO t) { - List strings = new ArrayList(); - strings.add(t.raceId); + List strings = new ArrayList<>(); + strings.add(t.regattaName); + strings.add(t.seriesName); strings.add(t.raceName); + strings.add(t.raceStatus); strings.add(t.boatClass); + strings.add(t.gender); + if (t.raceStartTime != null) { + strings.add(dateFormatter.render(t.raceStartTime)); + } return strings; } }; @@ -277,11 +297,24 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane } private ListHandler getRaceTableColumnSortHandler(List raceRecords, - Column nameColumn, Column trackingStartColumn, + Column regattaNameColumn, Column seriesNameColumn, + Column nameColumn, Column trackingStartColumn, Column raceIdColumn, Column boatClassColumn, - final Column genderColumn) { + Column genderColumn, Column statusColumn) { ListHandler result = new ListHandler(raceRecords); + result.setComparator(regattaNameColumn, new Comparator() { + @Override + public int compare(SwissTimingRaceRecordDTO o1, SwissTimingRaceRecordDTO o2) { + return new NaturalComparator().compare(o1.regattaName, o2.regattaName); + } + }); + result.setComparator(seriesNameColumn, new Comparator() { + @Override + public int compare(SwissTimingRaceRecordDTO o1, SwissTimingRaceRecordDTO o2) { + return new NaturalComparator().compare(o1.seriesName, o2.seriesName); + } + }); result.setComparator(nameColumn, new Comparator() { @Override public int compare(SwissTimingRaceRecordDTO o1, SwissTimingRaceRecordDTO o2) { @@ -314,6 +347,12 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane return o1.gender == null ? -1 : o2.gender == null ? 1 : new NaturalComparator(false).compare(o1.gender, o2.gender); } }); + result.setComparator(statusColumn, new Comparator() { + @Override + public int compare(SwissTimingRaceRecordDTO o1, SwissTimingRaceRecordDTO o2) { + return new NaturalComparator().compare(o1.raceStatus, o2.raceStatus); + } + }); return result; } @@ -360,6 +399,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane raceList.getList().addAll(availableSwissTimingRaces); filterablePanelEvents.getTextBox().setText(null); + filterablePanelEvents.updateAll(result.races); // store a successful configuration in the database for later retrieval final String hostname = result.trackingDataHost; @@ -427,6 +467,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane if (stConfig != null) { hostnameTextbox.setValue(stConfig.getHostname()); portIntegerbox.setValue(stConfig.getPort()); + jsonUrlBox.setValue(stConfig.getJsonURL()); } } } 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 3f4586581b9..f68d8cea3af 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 @@ -40,10 +40,10 @@ 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.panels.LabeledAbstractFilterablePanel; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; /** * Allows the user to start and stop tracking of events, regattas and races using the SwissTiming connector. @@ -315,6 +315,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane raceList.getList().clear(); raceList.getList().addAll(availableSwissTimingRaces); filterablePanelEvents.getTextBox().setText(null); + filterablePanelEvents.updateAll(races); // store a successful configuration in the database for later retrieval sailingService.storeSwissTimingArchiveConfiguration(swissTimingJsonUrl, new AsyncCallback() { 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 new file mode 100644 index 00000000000..893021f914a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java @@ -0,0 +1,65 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Collection; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.client.ui.IsWidget; +import com.google.gwt.user.client.ui.Panel; +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.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; + +/** + * Implementing classes still have to add the table to the main panel. + */ +public abstract class TableWrapper> implements IsWidget { + protected final CellTable table; + protected final S selectionModel; + protected final ListDataProvider dataProvider; + protected Panel mainPanel; + protected final SailingServiceAsync sailingService; + protected final ErrorReporter errorReporter; + + private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); + + @Override + public Widget asWidget() { + return mainPanel; + } + + public TableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter, + S selectionModel) { + this.sailingService = sailingService; + this.errorReporter = errorReporter; + this.selectionModel = selectionModel; + this.dataProvider = new ListDataProvider(); + mainPanel = new VerticalPanel(); + table = new CellTable(10000, tableRes); + dataProvider.addDataDisplay(table); + table.setSelectionModel(selectionModel); + mainPanel.add(table); + } + + public CellTable getTable() { + return table; + } + + public S getSelectionModel() { + return selectionModel; + } + + public ListDataProvider getDataProvider() { + return dataProvider; + } + + public void refresh(Collection newItems) { + dataProvider.getList().clear(); + dataProvider.getList().addAll(newItems); + dataProvider.flush(); + } +} \ No newline at end of file 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 f084a74fc59..f5f771ba1fa 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 @@ -36,18 +36,17 @@ 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.domain.common.impl.NaturalComparator; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.client.ErrorReporter; 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.panels.LabeledAbstractFilterablePanel; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO; import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; /** * Allows the user to start and stop tracking of events, regattas and races using the TracTrac connector. In particular, @@ -466,8 +465,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { final String tractracUsername = tractracUsernameTextBox.getValue(); final String tractracPassword = tractracPasswordTextBox.getValue(); - sailingService.listTracTracRacesInEvent(jsonURL, listHiddenRaces, new MarkedAsyncCallback>>( - new AsyncCallback>>() { + sailingService.listTracTracRacesInEvent(jsonURL, listHiddenRaces, new MarkedAsyncCallback>>( + new AsyncCallback>>() { @Override public void onFailure(Throwable caught) { loadingMessageLabel.setText(""); @@ -475,19 +474,16 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { } @Override - public void onSuccess(final Pair> result) { + public void onSuccess(final com.sap.sse.common.Util.Pair> result) { loadingMessageLabel.setText("Building resultset and saving configuration..."); TracTracEventManagementPanel.this.availableTracTracRaces.clear(); - final String eventName = result.getA(); final List eventRaces = result.getB(); - if (eventRaces != null) { TracTracEventManagementPanel.this.availableTracTracRaces.addAll(eventRaces); + racesFilterablePanel.updateAll(availableTracTracRaces); } - List races = TracTracEventManagementPanel.this.raceList.getList(); - races.clear(); races.addAll(TracTracEventManagementPanel.this.availableTracTracRaces); 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 new file mode 100644 index 00000000000..0407b55ad08 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportDeviceIdentifierTableWrapper.java @@ -0,0 +1,68 @@ +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.ErrorReporter; +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; + +public class TrackFileImportDeviceIdentifierTableWrapper extends TableWrapper> { + + public TrackFileImportDeviceIdentifierTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, + ErrorReporter errorReporter) { + super(sailingService, stringMessages, errorReporter, new SingleSelectionModel()); + + TextColumn uuidColumn = new TextColumn() { + @Override + public String getValue(TrackFileImportDeviceIdentifierDTO object) { + return object.uuidAsString; + } + }; + + TextColumn fileNameColumn = new TextColumn() { + @Override + public String getValue(TrackFileImportDeviceIdentifierDTO object) { + return object.fileName; + } + }; + + TextColumn trackNameColumn = new TextColumn() { + @Override + public String getValue(TrackFileImportDeviceIdentifierDTO object) { + return object.trackName; + } + }; + + TextColumn fromColumn = new TextColumn() { + @Override + public String getValue(TrackFileImportDeviceIdentifierDTO object) { + return DateAndTimeFormatterUtil.formatDateAndTime(object.from); + } + }; + + TextColumn toColumn = new TextColumn() { + @Override + public String getValue(TrackFileImportDeviceIdentifierDTO object) { + return DateAndTimeFormatterUtil.formatDateAndTime(object.to); + } + }; + + TextColumn numberOfFixesColumn = new TextColumn() { + @Override + public String getValue(TrackFileImportDeviceIdentifierDTO object) { + return "" + object.numFixes; + } + }; + + table.addColumn(uuidColumn, "UUID"); + table.addColumn(fileNameColumn, "Filename"); + table.addColumn(trackNameColumn, "Trackname"); + table.addColumn(fromColumn, stringMessages.from()); + table.addColumn(toColumn, stringMessages.to()); + table.addColumn(numberOfFixesColumn, "# fixes"); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportWidget.java new file mode 100644 index 00000000000..a9b59236bb0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportWidget.java @@ -0,0 +1,102 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FileUpload; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent; +import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.IsWidget; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; + +public class TrackFileImportWidget implements IsWidget { + private final Panel mainPanel = new VerticalPanel(); + + public TrackFileImportWidget(final TrackFileImportDeviceIdentifierTableWrapper table, StringMessages stringMessages, + final SailingServiceAsync sailingService, final ErrorReporter errorReporter) { + this(new SubmitCompleteHandler() { + @Override + public void onSubmitComplete(SubmitCompleteEvent event) { + table.getDataProvider().getList().clear(); + + String[] uuids = event.getResults().split("\\n"); + sailingService.getTrackFileImportDeviceIds(Arrays.asList(uuids), + new AsyncCallback>() { + + @Override + public void onSuccess(List result) { + table.getDataProvider().getList().addAll(result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load TrackFileImportDeviceIds: " + caught.getMessage()); + } + }); + + } + }, stringMessages, sailingService, errorReporter); + } + + public TrackFileImportWidget(final SubmitCompleteHandler submitCompleteHandler, StringMessages stringMessages, + SailingServiceAsync sailingService, final ErrorReporter errorReporter) { + final FormPanel formPanel = new FormPanel(); + mainPanel.add(formPanel); + + HorizontalPanel inFormPanel = new HorizontalPanel(); + formPanel.add(inFormPanel); + formPanel.setAction("/sailingserver/trackfiles/import"); + formPanel.setEncoding(FormPanel.ENCODING_MULTIPART); + formPanel.setMethod(FormPanel.METHOD_POST); + formPanel.addSubmitCompleteHandler(submitCompleteHandler); + FileUpload fileUpload = new FileUpload(); + fileUpload.setName("file"); + inFormPanel.add(fileUpload); + + final ListBox preferredImporter = new ListBox(); + preferredImporter.setName("preferredImporter"); + inFormPanel.add(preferredImporter); + preferredImporter.addItem(""); + sailingService.getGPSFixImporterTypes(new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + for (String type : result) { + preferredImporter.addItem(type); + } + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load importer types: " + caught.getMessage()); + } + }); + + Button btnUpload = new Button(stringMessages.importFixes()); + inFormPanel.add(btnUpload); + btnUpload.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent arg0) { + formPanel.submit(); + } + }); + } + @Override + public Widget asWidget() { + return mainPanel; + } + +} 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 87246cca965..dd14590c951 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 @@ -4,9 +4,10 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import com.google.gwt.user.client.rpc.AsyncCallback; + import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.HorizontalPanel; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -14,8 +15,8 @@ import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; -import com.sap.sailing.gwt.ui.client.RegattaDisplayer; 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.components.SettingsDialog; @@ -23,7 +24,7 @@ import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; /** - * Shows the currently tracked events/races in a table. Updated if subscribed as an {@link RegattaDisplayer}, e.g., with + * Shows the currently tracked events/races in a table. Updated if subscribed as an {@link RegattasDisplayer}, e.g., with * the {@link AdminConsoleEntryPoint}. */ public class TrackedRacesListComposite extends AbstractTrackedRacesListComposite { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesSettingsDialogComponent.java index 2f28415cfe2..e2c09837b0f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesSettingsDialogComponent.java @@ -5,8 +5,8 @@ import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.LongBox; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; 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 new file mode 100644 index 00000000000..ba08d5e51c5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointCreationDialog.java @@ -0,0 +1,104 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.List; + +import com.google.gwt.user.client.ui.Grid; +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.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.ErrorReporter; +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.WaypointDTO; +import com.sap.sse.common.Util; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class WaypointCreationDialog extends DataEntryDialog { + private final ControlPointTableWrapper> controlPointsWrapper; + private final ListBox passingInstructions; + private final StringMessages stringMessages; + + public WaypointCreationDialog(SailingServiceAsync sailingService, ErrorReporter errorReporter, + final StringMessages stringMessages, AdminConsoleTableResources tableRes, + List controlPoints, DialogCallback callback) { + super(stringMessages.waypoint(), stringMessages.waypoint(), + stringMessages.ok(), stringMessages.cancel(), new DataEntryDialog.Validator() { + @Override + public String getErrorMessage(WaypointDTO valueToValidate) { + if (valueToValidate.controlPoint == null) { + return stringMessages.pleaseSelectAControlPoint(); + } + return null; + } + + }, /* animationEnabled */ false, callback); + + this.stringMessages = stringMessages; + + controlPointsWrapper = new ControlPointTableWrapper>( + new SingleSelectionModel(), sailingService, stringMessages, errorReporter); + controlPointsWrapper.getDataProvider().getList().addAll(controlPoints); + + passingInstructions = createListBox(false); + updatePassingInstructions(); + + controlPointsWrapper.getSelectionModel().addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + updatePassingInstructions(); + validate(); + } + }); + } + + private void updatePassingInstructions() { + passingInstructions.clear(); + + int numMarks = controlPointsWrapper.getSelectionModel().getSelectedObject() != null ? + Util.size(controlPointsWrapper.getSelectionModel().getSelectedObject().getMarks()) : 0; + + int i = 0; + passingInstructions.insertItem(PassingInstruction.None.name(), i++); + for (PassingInstruction pi : PassingInstruction.relevantValues()) { + for (int numApplicableMarks : pi.applicability) { + if (numApplicableMarks == numMarks) { + passingInstructions.insertItem(pi.name(), i++); + } + } + } + } + + @Override + protected WaypointDTO getResult() { + PassingInstruction pi = passingInstructions.getSelectedIndex() > 0 ? + PassingInstruction.valueOf(passingInstructions.getItemText(passingInstructions.getSelectedIndex())) + : PassingInstruction.None; + + ControlPointDTO controlPoint = controlPointsWrapper.getSelectionModel().getSelectedObject(); + String name = null; + if (controlPoint != null) { + name = controlPoint.getName(); + } + return new WaypointDTO(name, controlPoint, pi); + } + + @Override + protected Widget getAdditionalWidget() { + Grid grid = new Grid(2,1); + + grid.setWidget(0, 0, controlPointsWrapper); + + HorizontalPanel passingInstructionsRow = new HorizontalPanel(); + grid.setWidget(1, 0, passingInstructionsRow); + passingInstructionsRow.add(new Label(stringMessages.passingInstructions() + ":")); + passingInstructionsRow.add(passingInstructions); + + return grid; + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..ea04002dfe2 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointTableWrapper.java @@ -0,0 +1,51 @@ +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.ErrorReporter; +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.sailing.gwt.ui.shared.WaypointDTO; + +public class WaypointTableWrapper> extends TableWrapper { + public WaypointTableWrapper(S selectionModel, SailingServiceAsync sailingService, final StringMessages stringMessages, + ErrorReporter errorReporter) { + super(sailingService, stringMessages, errorReporter, selectionModel); + + mainPanel.add(table); + + TextColumn nameColumn = new TextColumn() { + @Override + public String getValue(WaypointDTO d) { + return d.controlPoint.getName(); + } + }; + table.addColumn(nameColumn, stringMessages.controlPoint() + " " + stringMessages.name()); + + TextColumn typeColumn = new TextColumn() { + @Override + public String getValue(WaypointDTO w) { + ControlPointDTO d = w.controlPoint; + if (d instanceof GateDTO) { + return "2x " + stringMessages.mark(); + } else if (d instanceof MarkDTO) { + return stringMessages.mark(); + } else { + return stringMessages.unknown(); + } + } + }; + table.addColumn(typeColumn, stringMessages.type()); + + TextColumn passingInstructionsColumn = new TextColumn() { + @Override + public String getValue(WaypointDTO waypoint) { + return waypoint.passingInstructions != null ? waypoint.passingInstructions.name() : ""; + } + }; + table.addColumn(passingInstructionsColumn, stringMessages.passingInstructions()); + } +} 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 e4108e2d379..3264791d6d6 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 @@ -55,10 +55,11 @@ import com.sap.sailing.gwt.ui.client.ErrorReporter; 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.RegattaDisplayer; 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.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.WindDTO; @@ -75,7 +76,7 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; * @author Axel Uhl (d043530) * */ -public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower, RaceSelectionChangeListener { +public class WindPanel extends FormPanel implements RegattasDisplayer, WindShower, RaceSelectionChangeListener { private static final String EXPEDITON_IMPORT_PARAMETER_RACES = "races"; @@ -571,7 +572,8 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower private void showWindFixesList(RegattaAndRaceIdentifier selectedRace, RaceDTO raceDTO) { List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.COMBINED.name()); - sailingService.getAveragedWindInfo(selectedRace, raceDTO.startOfRace, 30000L, 100, windSourceTypeNames, new AsyncCallback() { + sailingService.getAveragedWindInfo(selectedRace, raceDTO.startOfRace, 30000L, 100, windSourceTypeNames, + /* onlyUpToNewestEvent==true means to only use data "based on facts" */ true, new AsyncCallback() { @Override public void onFailure(Throwable caught) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java index 7a0c0eb941a..c8fe4770bc6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java @@ -7,7 +7,6 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.resources.client.TextResource; import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.HTML; @@ -27,21 +26,6 @@ public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, W protected UserAgentDetails userAgent; protected Label persistentAlertLabel; -// /** -// * Creates a remote service proxy to talk to the server-side SailingService. -// */ -// protected final SailingServiceAsync sailingService = GWT.create(SailingService.class); -// -// /** -// * Creates a remote service proxy to talk to the server-side MediaService. -// */ -// protected final MediaServiceAsync mediaService = GWT.create(MediaService.class); -// -// /** -// * Creates a remote service proxy to talk to the server-side UsermanagementService. -// */ -// protected final UserManagementServiceAsync userManagementService = GWT.create(UserManagementService.class); - /** * The message displayed to the user when the server cannot be reached or * returns an error. @@ -71,17 +55,6 @@ public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, W userAgent = new UserAgentDetails(Window.Navigator.getUserAgent()); persistentAlertLabel = new Label(""); persistentAlertLabel.setStyleName("global-alert-message"); - -// registerASyncService((ServiceDefTarget) sailingService, "sailing"); -// registerASyncService((ServiceDefTarget) mediaService, "media"); -// registerASyncService((ServiceDefTarget) userManagementService, "usermanagement"); - } - - protected void registerASyncService(ServiceDefTarget serviceToRegister, String servicePath) { - String moduleBaseURL = GWT.getModuleBaseURL(); - String baseURL = moduleBaseURL.substring(0, moduleBaseURL.lastIndexOf('/', moduleBaseURL.length()-2)+1); - - serviceToRegister.setServiceEntryPoint(baseURL + servicePath); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractRegattaPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractRegattaPanel.java index 4b2e742fb54..5d0bb4fa956 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractRegattaPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractRegattaPanel.java @@ -9,7 +9,7 @@ import com.google.gwt.user.client.ui.FormPanel; import com.sap.sailing.gwt.ui.shared.RegattaDTO; //TODO: Do not inherit from FormPanel since the provided functionality is never used! -public abstract class AbstractRegattaPanel extends FormPanel implements RegattaDisplayer { +public abstract class AbstractRegattaPanel extends FormPanel implements RegattasDisplayer { protected final SailingServiceAsync sailingService; protected DateTimeFormatRenderer dateFormatter = new DateTimeFormatRenderer(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT)); protected DateTimeFormatRenderer timeFormatter = new DateTimeFormatRenderer(DateTimeFormat.getFormat(PredefinedFormat.TIME_LONG)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DebugIdHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DebugIdHelper.java new file mode 100644 index 00000000000..8bc293b508e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DebugIdHelper.java @@ -0,0 +1,45 @@ +package com.sap.sailing.gwt.ui.client; + +/** + *

Helper class for generating debug identifiers.

+ * + * @author + * Riccardo Nimser + */ +public class DebugIdHelper { + /** + *

Creates a debug identifier for the given enum value. The identifier is the camel cased name of the enum value + * as declared in its enum declaration. The name is split into single words using the underscore.

+ * + * @param value + * The enum value for which the identfier should be created. + * @return + * A debug identifier for the given enum value. + */ + public static > String createDebugId(T value) { + return createDebugId(value.name(), "_"); + } + + /** + *

Creates a debug identifier for the given name. The name is splited into single words using all whitespace + * characters.

+ * + * @param value + * The name for which the identifier should be created. + * @return + * A debug identifier for the given enum value. + */ + public static String createDebugId(String name) { + return createDebugId(name, "\\s"); + } + + private static String createDebugId(String name, String splitRegex) { + StringBuilder builder = new StringBuilder(); + + for (String token : name.split(splitRegex)) { + builder.append(token.substring(0, 1).toUpperCase()); + builder.append(token.substring(1, token.length()).toLowerCase()); + } + return builder.toString(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java index 481828356ac..8f04b1f0212 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java @@ -53,8 +53,10 @@ public class DetailTypeFormatter { return stringMessages.gapToLeaderInSeconds(); case RACE_DISTANCE_TO_LEADER_IN_METERS: return stringMessages.windwardDistanceToLeaderInMeters(); - case RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS: - return stringMessages.averageCrossTrackErrorInMeters(); + case RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS: + return stringMessages.averageAbsoluteCrossTrackErrorInMeters(); + case RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS: + return stringMessages.averageSignedCrossTrackErrorInMeters(); case START_TACK: return stringMessages.startTack(); case WINDWARD_DISTANCE_TO_OVERALL_LEADER: @@ -79,8 +81,10 @@ public class DetailTypeFormatter { return stringMessages.currentLeg(); case TIME_TRAVELED: return stringMessages.time(); - case AVERAGE_CROSS_TRACK_ERROR_IN_METERS: - return stringMessages.averageCrossTrackErrorInMeters(); + case AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS: + return stringMessages.averageAbsoluteCrossTrackErrorInMeters(); + case AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS: + return stringMessages.averageSignedCrossTrackErrorInMeters(); case MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS: return stringMessages.maximumSpeedOverGroundInKnots(); case TOTAL_DISTANCE_TRAVELED: @@ -153,8 +157,10 @@ public class DetailTypeFormatter { case AVERAGE_TACK_LOSS_IN_METERS: case AVERAGE_JIBE_LOSS_IN_METERS: case AVERAGE_MANEUVER_LOSS_IN_METERS: - case AVERAGE_CROSS_TRACK_ERROR_IN_METERS: - case RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS: + case AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS: + case AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS: + case RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS: + case RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS: case DISTANCE_TO_START_AT_RACE_START: case TOTAL_DISTANCE_TRAVELED: case DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS: @@ -213,12 +219,16 @@ public class DetailTypeFormatter { */ public static String getTooltip(DetailType detailType) { switch (detailType) { - case AVERAGE_CROSS_TRACK_ERROR_IN_METERS: - return stringMessages.averageCrossTrackErrorInMetersTooltip(); + case AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS: + return stringMessages.averageAbsoluteCrossTrackErrorInMetersTooltip(); + case AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS: + return stringMessages.averageSignedCrossTrackErrorInMetersTooltip(); case GAP_CHANGE_SINCE_LEG_START_IN_SECONDS: return stringMessages.gapChangeSinceLegStartInSecondsTooltip(); - case RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS: - return stringMessages.raceAverageCrossTrackErrorInMetersTooltip(); + case RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS: + return stringMessages.raceAverageAbsoluteCrossTrackErrorInMetersTooltip(); + case RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS: + return stringMessages.raceAverageSignedCrossTrackErrorInMetersTooltip(); case SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED: return stringMessages.sideToWhichMarkAtLegStartWasRoundedTooltip(); case VELOCITY_MADE_GOOD_IN_KNOTS: diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventRefresher.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventsRefresher.java similarity index 61% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventRefresher.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventsRefresher.java index 5d074fd828a..2000472a956 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventRefresher.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventsRefresher.java @@ -1,7 +1,5 @@ package com.sap.sailing.gwt.ui.client; -public interface EventRefresher { - +public interface EventsRefresher { void fillEvents(); - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardGroupRefresher.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardGroupRefresher.java deleted file mode 100644 index 10a757b45fc..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardGroupRefresher.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -public interface LeaderboardGroupRefresher { - - void fillLeaderboardGroups(); - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardGroupsDisplayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardGroupsDisplayer.java new file mode 100755 index 00000000000..815c724a13a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardGroupsDisplayer.java @@ -0,0 +1,7 @@ +package com.sap.sailing.gwt.ui.client; + +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; + +public interface LeaderboardGroupsDisplayer { + void fillLeaderboardGroups(Iterable leaderboardGroups); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardGroupsRefresher.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardGroupsRefresher.java new file mode 100755 index 00000000000..552c148d435 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardGroupsRefresher.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.ui.client; + +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; + +public interface LeaderboardGroupsRefresher { + /** + * Fetch all leaderboard groups from the server and distribute to all {@link LeaderboardGroupsDisplayer}s registered. + */ + void fillLeaderboardGroups(); + + /** + * Update all {@link LeaderboardGroupsDisplayer}s registered with the updated collection of leaderboard groups. + * Calling this method is useful for notifying all other {@link LeaderboardGroupsDisplayer}s after one area of the + * UI has actively changed the leaderboard groups collection and has already updated its sequence of + * {@link LeaderboardGroupDTO}s. This does not necessarily create consistency with the leaderboard groups known by + * the server at that time, but at least all areas of the UI that have registered as + * {@link LeaderboardGroupsDisplayer} will show an equal sequence of leaderboard groups. + * + * @param origin + * will not receive a call to its {@link LeaderboardGroupsDisplayer#fillLeaderboardGroups(Iterable)}, assuming that + * the update originated in origin and hence no notification is required. + */ + void updateLeaderboardGroups(Iterable updatedLeaderboardGroups, LeaderboardGroupsDisplayer origin); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardsDisplayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardsDisplayer.java new file mode 100755 index 00000000000..22f739c0858 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardsDisplayer.java @@ -0,0 +1,7 @@ +package com.sap.sailing.gwt.ui.client; + +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; + +public interface LeaderboardsDisplayer { + void fillLeaderboards(Iterable leaderboards); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardsRefresher.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardsRefresher.java new file mode 100755 index 00000000000..d0f9cd86018 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardsRefresher.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.ui.client; + +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; + +public interface LeaderboardsRefresher { + /** + * Fetch all leaderboards from the server as {@link StrippedLeaderboardDTO} objects and distribute to all + * {@link LeaderboardsDisplayer}s registered. + */ + void fillLeaderboards(); + + /** + * Update all {@link LeaderboardsDisplayer}s registered with the updated collection of leaderboards. Calling this + * method is useful for notifying all other {@link LeaderboardsDisplayer}s after one area of the UI has actively + * changed the leaderboard collection and has already updated its sequence of {@link StrippedLeaderboardDTO}s. + * This does not necessarily create consistency with the leaderboards known by the server at that time, but at + * least all areas of the UI that have registered as {@link LeaderboardsDisplayer} will show an equal sequence + * of leaderboards. + * + * @param origin will not receive a call to its {@link LeaderboardsDisplayer#fillLeaderboards(Iterable)}, assuming + * that the update originated in origin and hence no notification is required. + */ + void updateLeaderboards(Iterable updatedLeaderboards, LeaderboardsDisplayer origin); +} 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 index 42315b4407e..556bbf02538 100755 --- 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 @@ -8,8 +8,8 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; public class RaceSelectionModel implements RaceSelectionProvider { private final List selection; 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 751ea204167..a1d879b6e76 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 @@ -5,11 +5,11 @@ import java.util.List; import java.util.Map; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.gwt.ui.adminconsole.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; +import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.MarkPassingTimesDTO; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; @@ -170,7 +170,7 @@ public class RaceTimePanel extends TimePanel implements R * was null before, or extended to a later point in time. */ private void updateMinMax(RaceTimesInfoDTO newRaceTimesInfo) { - Pair raceMinMax = RaceTimesCalculationUtil.caluclateRaceMinMax(timer, newRaceTimesInfo); + Util.Pair raceMinMax = RaceTimesCalculationUtil.caluclateRaceMinMax(timer, newRaceTimesInfo); Date min = raceMinMax.getA(); Date max = raceMinMax.getB(); @@ -193,7 +193,7 @@ public class RaceTimePanel extends TimePanel implements R * {@link RaceTimesInfoDTO#timePointOfNewestEvent timePointOfNewestEvent} received from the tracking infrastructure * so far and puts the timer into {@link PlayStates#Playing play mode}. */ - private boolean initTimerPosition(RaceTimesInfoDTO newRaceTimesInfo) { + private void initTimerPosition(RaceTimesInfoDTO newRaceTimesInfo) { // initialize timer position switch (timer.getPlayMode()) { case Live: @@ -209,7 +209,6 @@ public class RaceTimePanel extends TimePanel implements R } break; } - return timer.getTime() != null; } private void updateLegMarkers(RaceTimesInfoDTO newRaceTimesInfo) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimesCalculationUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimesCalculationUtil.java index 23a928e9e0d..79d2e640c21 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimesCalculationUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimesCalculationUtil.java @@ -2,13 +2,13 @@ package com.sap.sailing.gwt.ui.client; import java.util.Date; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.player.Timer; public class RaceTimesCalculationUtil { - public static Pair caluclateRaceMinMax(Timer timer, RaceTimesInfoDTO raceTimesInfo) { + public static Util.Pair caluclateRaceMinMax(Timer timer, RaceTimesInfoDTO raceTimesInfo) { Date min = null; Date max = null; @@ -55,7 +55,7 @@ public class RaceTimesCalculationUtil { break; } - return new Pair(min, max); + return new Util.Pair(min, max); } private static long calculateRaceExtensionTime(Date startTime, Date endTime) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaDisplayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattasDisplayer.java similarity index 78% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaDisplayer.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattasDisplayer.java index d64a267a767..318b01eeee0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaDisplayer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattasDisplayer.java @@ -4,6 +4,6 @@ import java.util.List; import com.sap.sailing.gwt.ui.shared.RegattaDTO; -public interface RegattaDisplayer { +public interface RegattasDisplayer { void fillRegattas(List result); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RequiresDataInitialization.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RequiresDataInitialization.java index 29f25034849..5f69d064a8e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RequiresDataInitialization.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RequiresDataInitialization.java @@ -7,7 +7,7 @@ package com.sap.sailing.gwt.ui.client; */ public interface RequiresDataInitialization { - public void initializeData(); + public void initializeData(boolean showMapControls); public boolean isDataInitialized(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java new file mode 100755 index 00000000000..33bcbb9cee4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java @@ -0,0 +1,13 @@ +package com.sap.sailing.gwt.ui.client; + +import com.sap.sse.gwt.client.mvp.ClientFactory; + +/** + * A client factory that can deliver the sailing-specific services. + * + * @author Axel Uhl (D043530) + * + */ +public interface SailingClientFactory extends ClientFactory { + SailingServiceAsync getSailingService(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactoryImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactoryImpl.java new file mode 100755 index 00000000000..34887593a33 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactoryImpl.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.ui.client; + +import com.google.gwt.core.client.GWT; +import com.sap.sse.gwt.client.mvp.ClientFactoryImpl; +import com.sap.sse.gwt.client.mvp.TopLevelView; + +public abstract class SailingClientFactoryImpl extends ClientFactoryImpl implements SailingClientFactory { + private final SailingServiceAsync sailingService; + + public SailingClientFactoryImpl(TopLevelView root) { + super(root); + sailingService = GWT.create(SailingServiceAsync.class); + } + + @Override + public SailingServiceAsync getSailingService() { + return sailingService; + } + +} 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 index 00b7dfd321b..7092dd5e6ba 100644 --- 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 @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.client; +import java.io.Serializable; import java.util.Collection; import java.util.Date; import java.util.List; @@ -8,6 +9,8 @@ import java.util.Set; import java.util.UUID; import com.google.gwt.user.client.rpc.RemoteService; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.common.DataImportProgress; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.LeaderboardType; @@ -24,13 +27,22 @@ import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherTyp import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.IncrementalOrFullLeaderboardDTO; +import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; +import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.Revokable; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; +import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; @@ -40,10 +52,14 @@ import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; +import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; +import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceLogDTO; @@ -52,6 +68,7 @@ import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.RemoteSailingServerReferenceDTO; import com.sap.sailing.gwt.ui.shared.ReplicationStateDTO; import com.sap.sailing.gwt.ui.shared.ScoreCorrectionProviderDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; @@ -62,9 +79,13 @@ import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO; import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO; import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO; +import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; import com.sap.sailing.gwt.ui.shared.VenueDTO; import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.search.KeywordQuery; /** * The client side stub for the RPC service. Usually, when a null date is passed to @@ -75,9 +96,11 @@ public interface SailingService extends RemoteService { List getRegattas(); - List getEvents(); + List getEvents() throws Exception; - Pair> listTracTracRacesInEvent(String eventJsonURL, boolean listHiddenRaces) throws Exception; + List getPublicEventsOfAllSailingServers() throws Exception; + + Util.Pair> listTracTracRacesInEvent(String eventJsonURL, boolean listHiddenRaces) throws Exception; void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable rrs, String liveURI, String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination, boolean simulateWithStartTimeNow, String tracTracUsername, String tracTracPassword) throws Exception; @@ -101,10 +124,10 @@ public interface SailingService extends RemoteService { void setWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO wind); WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, - int numberOfFixes, Collection windSourceTypeNames) throws NoWindException; + int numberOfFixes, Collection windSourceTypeNames, boolean onlyUpToNewestEvent) throws NoWindException; WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, Date to, long resolutionInMilliseconds, - Collection windSourceTypeNames); + Collection windSourceTypeNames, boolean onlyUpToNewestEvent); WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, int numberOfFixes, double latDeg, double lngDeg, Collection windSources) throws NoWindException; @@ -125,7 +148,8 @@ public interface SailingService extends RemoteService { public List getLeaderboardNames(); IncrementalOrFullLeaderboardDTO getLeaderboardByName(String leaderboardName, Date date, - Collection namesOfRaceColumnsForWhichToLoadLegDetails, String previousLeaderboardId) throws Exception; + Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + String previousLeaderboardId) throws Exception; List getLeaderboards(); @@ -196,10 +220,10 @@ public interface SailingService extends RemoteService { * @return the new net points in {@link Pair#getA()} and the new total points in {@link Pair#getB()} for time point * date after the max points reason has been updated to maxPointsReasonAsString. */ - Triple updateLeaderboardMaxPointsReason(String leaderboardName, String competitorIdAsString, + Util.Triple updateLeaderboardMaxPointsReason(String leaderboardName, String competitorIdAsString, String raceColumnName, MaxPointsReason maxPointsReason, Date date) throws NoWindException; - Triple updateLeaderboardScoreCorrection(String leaderboardName, String competitorIdAsString, + Util.Triple updateLeaderboardScoreCorrection(String leaderboardName, String competitorIdAsString, String columnName, Double correctedScore, Date date) throws NoWindException; void updateCompetitorDisplayNameInLeaderboard(String leaderboardName, String competitorIdAsString, String displayName); @@ -252,10 +276,11 @@ public interface SailingService extends RemoteService { void updateRacesDelayToLive(List regattaAndRaceIdentifiers, long delayToLiveInMs); - void updateEvent(UUID eventId, String eventName, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, - List regattaNames); + EventDTO updateEvent(UUID eventId, String eventName, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, + Iterable leaderboardGroupIds, Iterable imageURLs, Iterable videoURLs) throws Exception; - EventDTO createEvent(String eventName, Date startDate, Date endDate, String venueName, boolean isPublic, List courseAreaNames); + EventDTO createEvent(String eventName, Date startDate, Date endDate, String description, boolean isPublic, + List courseAreaNames, Iterable imageURLs, Iterable videoURLs) throws Exception; void removeEvent(UUID eventId); @@ -278,20 +303,26 @@ public interface SailingService extends RemoteService { WindInfoForRaceDTO getWindSourcesInfo(RegattaAndRaceIdentifier raceIdentifier); + List getRemoteSailingServerReferences(); + + void removeSailingServers(Set toRemove) throws Exception; + + RemoteSailingServerReferenceDTO addRemoteSailingServerReference(RemoteSailingServerReferenceDTO sailingServer) throws Exception; + List getResultImportUrls(String resultProviderName); void removeResultImportURLs(String resultProviderName, Set toRemove) throws Exception; void addResultImportUrl(String resultProviderName, String url) throws Exception; - Void updateLeaderboardScoreCorrectionMetadata(String leaderboardName, Date timePointOfLastCorrectionValidity, + void updateLeaderboardScoreCorrectionMetadata(String leaderboardName, Date timePointOfLastCorrectionValidity, String comment); List getUrlResultProviderNames(); - void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List> controlPoints); + void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List> controlPoints); - void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd); + void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd); void removeLeaderboardColumns(String leaderboardName, List columnsToRemove); @@ -303,7 +334,7 @@ public interface SailingService extends RemoteService { List listSwissTiminigReplayRaces(String swissTimingUrl); - List, List>> getLeaderboardDataEntriesForAllRaceColumns(String leaderboardName, + List, List>> getLeaderboardDataEntriesForAllRaceColumns(String leaderboardName, Date date, DetailType detailType) throws Exception; List getOverallLeaderboardNamesContaining(String leaderboardName); @@ -318,13 +349,14 @@ public interface SailingService extends RemoteService { CourseAreaDTO createCourseArea(UUID eventId, String courseAreaName); List getBoatClassNamesWithPolarSheetsAvailable(); + + List> getLeaderboardsNamesOfMetaleaderboard(String metaLeaderboardName); PolarSheetGenerationResponse showCachedPolarSheetForBoatClass(String boatClassName); - List> getLeaderboardsNamesOfMetaleaderboard(String metaLeaderboardName); - Pair checkLeaderboardName(String leaderboardName); + Util.Pair checkLeaderboardName(String leaderboardName); List getRegattaStructureForEvent(UUID eventId); @@ -356,9 +388,16 @@ public interface SailingService extends RemoteService { Iterable getCompetitors(); - Iterable getCompetitorsOfLeaderboard(String leaderboardName); + /** + * + * @param leaderboardName + * @param lookInRaceLogs If set to {@code true}, the {@link RaceLog}s are checked for the competitor registrations. + * If set to {@code false}, the {@link RaceDefinition}s are checked instead. + * @return + */ + Iterable getCompetitorsOfLeaderboard(String leaderboardName, boolean lookInRaceLogs); - CompetitorDTO updateCompetitor(CompetitorDTO competitor); + CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor); void allowCompetitorResetToDefaults(Iterable competitors); @@ -372,7 +411,7 @@ public interface SailingService extends RemoteService { boolean setStartTimeAndProcedure(RaceLogSetStartTimeAndProcedureDTO dto); - Triple getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName); + Util.Triple getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName); Iterable getAllIgtimiAccountEmailAddresses(); @@ -383,4 +422,98 @@ public interface SailingService extends RemoteService { void removeIgtimiAccount(String eMailOfAccountToRemove); Map importWindFromIgtimi(List selectedRaces) throws Exception; + + void denoteForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName) throws Exception; + + /** + * Revoke the {@link DenoteForTrackingEvent}. This does not affect an existing {@code RaceLogRaceTracker} + * or {@link TrackedRace} for this {@code RaceLog}. + * + * @see RaceLogTrackingAdapter#removeDenotationForRaceLogTracking + */ + void removeDenotationForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName); + + void denoteForRaceLogTracking(String leaderboardName) throws Exception; + + /** + * Performs all the necessary steps to start tracking the race. + * The {@code RaceLog} needs to be denoted for racelog-tracking beforehand. + * + * @see RaceLogTrackingAdapter#startTracking + */ + void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName) + throws NotDenotedForRaceLogTrackingException, Exception; + + void setCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName, Set competitors); + + Collection getCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName); + + void addMarkToRaceLog(String leaderboardName, String raceColumnName, String fleetName, MarkDTO markDTO); + + Collection getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName); + + /** + * Adds the course definition to the racelog, while trying to reuse existing marks, controlpoints and waypoints + * from the previous course definition in the racelog. + */ + void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName, List> course); + + RaceCourseDTO getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName); + + /** + * Adds a fix to the {@link GPSFixStore}, and creates a mapping with a virtual device for exactly the current timepoint. + */ + void pingMarkViaRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, MarkDTO mark, PositionDTO position); + + void copyCourseAndCompetitorsToOtherRaceLogs(Util.Triple raceLogFrom, + Set> raceLogsTo); + + void addDeviceMappingToRaceLog(String leaderboardName, String raceColumnName, String fleetName, DeviceMappingDTO mapping) + throws TransformationException; + + List getDeviceMappingsFromRaceLog(String leaderboardName, String raceColumnName, String fleetName) + throws TransformationException; + + List getDeserializableDeviceIdentifierTypes(); + + void closeOpenEndedDeviceMapping(String leaderboardName, String raceColumnName, String fleetName, DeviceMappingDTO mapping, + Date closingTimePoint) throws NoCorrespondingServiceRegisteredException, TransformationException; + + /** + * Revoke the events in the {@code RaceLog} that are identified by the {@code eventIds}. + * This only affects such events that implement {@link Revokable}. + */ + void revokeRaceLogEvents(String leaderboardName, String raceColumnName, String fleetName, List eventIds) + throws NotRevokableException; + + Collection getGPSFixImporterTypes(); + + List getTrackFileImportDeviceIds(List uuids) + throws NoCorrespondingServiceRegisteredException, TransformationException; + + /** + * A client should search a server in a two-step process. First, the client should ask the server which other + * servers are available for searching additional content. Then, in a second step, the client should fire the + * queries by parallel asynchronous calls to the one server, passing the name of the remote server reference to + * search, or null in order to search the server to which the query is sent by the call. This allows a + * client to asynchronously receive the results from various servers, not requiring the client to block until all + * results from all servers have been received. The key reason for this two-step process is that the GWT RPC does + * not support streaming of results. + * + * @return the list of server reference names, corresponding with {@link RemoteSailingServerReference#getName()}, to + * be used as parameter in {@link #search(String, KeywordQuery)}. This list does not contain the + * null value used to represent the search on the main server to which the query is sent. + */ + Iterable getSearchServerNames(); + + /** + * Call this method once for each result of {@link #getSearchServerNames()} and once with null for + * the serverNameOfNullForMain parameter. + * + * @param serverNameOrNullForMain + * use null to search on the server to which this request is sent; use a name as retrieved + * by {@link #getSearchServerNames()} which corresponds to a name of a + * {@link RemoteSailingServerReference}, to search a remote server. + */ + Iterable search(String serverNameOrNullForMain, KeywordQuery query) throws Exception; } 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 index e9f8f118eac..3dc82a58baa 100755 --- 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 @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.client; +import java.io.Serializable; import java.util.Collection; import java.util.Date; import java.util.List; @@ -8,6 +9,7 @@ import java.util.Set; import java.util.UUID; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.common.DataImportProgress; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.LeaderboardType; @@ -25,13 +27,13 @@ import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherTyp import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.IncrementalOrFullLeaderboardDTO; +import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; @@ -41,10 +43,14 @@ import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; +import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; +import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceLogDTO; @@ -53,6 +59,7 @@ import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.RemoteSailingServerReferenceDTO; import com.sap.sailing.gwt.ui.shared.ReplicationStateDTO; import com.sap.sailing.gwt.ui.shared.ScoreCorrectionProviderDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; @@ -63,9 +70,12 @@ import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO; import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO; import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO; +import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; import com.sap.sailing.gwt.ui.shared.VenueDTO; import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; +import com.sap.sse.common.Util; +import com.sap.sse.common.search.KeywordQuery; /** * The async counterpart of {@link SailingService} @@ -78,7 +88,7 @@ public interface SailingServiceAsync { * The string returned in the callback's pair is the common event name * @param listHiddenRaces */ - void listTracTracRacesInEvent(String eventJsonURL, boolean listHiddenRaces, AsyncCallback>> callback); + void listTracTracRacesInEvent(String eventJsonURL, boolean listHiddenRaces, AsyncCallback>> callback); /** * @param regattaToAddTo @@ -129,11 +139,17 @@ public interface SailingServiceAsync { void getRawWindFixes(RegattaAndRaceIdentifier raceIdentifier, Collection windSources, AsyncCallback callback); /** - * @param from if null, the tracked race's start of tracking is used - * @param to if null, the tracked race's time point of newest event is used + * @param onlyUpToNewestEvent + * if true, no wind data will be returned for time points later than + * {@link TrackedRace#getTimePointOfNewestEvent() trackedRace.getTimePointOfNewestEvent()}. This is + * helpful in case the client wants to populate a chart during live mode. If false, the + * "best effort" readings are provided for the time interval requested, no matter if based on any sensor + * evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent() + * trackedRace.getTimePointOfNewestEvent()}. */ - void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, Date to, long resolutionInMilliseconds, - Collection windSourceTypeNames, AsyncCallback callback); + void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, Date to, + long resolutionInMilliseconds, Collection windSourceTypeNames, boolean onlyUpToNewestEvent, + AsyncCallback callback); /** * @param windSourceTypeNames @@ -145,20 +161,17 @@ public interface SailingServiceAsync { AsyncCallback callback); /** - * Same as {@link #getWindInfo(RegattaAndRaceIdentifier, Date, long, int, double, double, Collection, AsyncCallback)}, only - * that the wind is not requested for a specific position, but instead the wind sources associated with the tracked - * race identified by raceIdentifier are requested to deliver their original position. This will in - * particular preserve the positions of actual measurements and will deliver the averaged positions for averaged / - * combined wind read-outs. - * - * @param from - * must not be null - * @param numberOfFixes - * no matter how great this value is chosen, never returns data beyond the newest event recorded in the - * race + * @param onlyUpToNewestEvent + * if true, no wind data will be returned for time points later than + * {@link TrackedRace#getTimePointOfNewestEvent() trackedRace.getTimePointOfNewestEvent()}. This is + * helpful in case the client wants to populate a chart during live mode. If false, the + * "best effort" readings are provided for the time interval requested, no matter if based on any sensor + * evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent() + * trackedRace.getTimePointOfNewestEvent()}. */ - void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, int numberOfFixes, - Collection windSourceTypeNames, AsyncCallback callback); + void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, + int numberOfFixes, Collection windSourceTypeNames, boolean onlyUpToNewestEvent, + AsyncCallback callback); void setWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO wind, AsyncCallback callback); @@ -172,7 +185,7 @@ public interface SailingServiceAsync { void getLeaderboardByName(String leaderboardName, Date date, Collection namesOfRaceColumnsForWhichToLoadLegDetails, - String previousLeaderboardId, AsyncCallback callback); + boolean addOverallDetails, String previousLeaderboardId, AsyncCallback callback); void getLeaderboardNames(AsyncCallback> callback); @@ -223,10 +236,10 @@ public interface SailingServiceAsync { void updateLeaderboardCarryValue(String leaderboardName, String competitorIdAsString, Double carriedPoints, AsyncCallback callback); void updateLeaderboardMaxPointsReason(String leaderboardName, String competitorIdAsString, String raceColumnName, - MaxPointsReason maxPointsReason, Date date, AsyncCallback> asyncCallback); + MaxPointsReason maxPointsReason, Date date, AsyncCallback> asyncCallback); void updateLeaderboardScoreCorrection(String leaderboardName, String competitorIdAsString, String columnName, - Double correctedScore, Date date, AsyncCallback> asyncCallback); + Double correctedScore, Date date, AsyncCallback> asyncCallback); void updateLeaderboardScoreCorrectionMetadata(String leaderboardName, Date timePointOfLastCorrectionValidity, String comment, AsyncCallback callback); @@ -314,6 +327,8 @@ public interface SailingServiceAsync { void getEvents(AsyncCallback> callback); + void getPublicEventsOfAllSailingServers(AsyncCallback> callback); + /** * Creates a {@link EventDTO} for the {@link com.sap.sailing.domain.base.Event} with the name eventName, which contains the * name, the description and a list with {@link RegattaDTO RegattaDTOs} contained in the event.
@@ -335,10 +350,13 @@ public interface SailingServiceAsync { void removeEvents(Collection eventIds, AsyncCallback asyncCallback); - void createEvent(String eventName, Date startDate, Date endDate, String description, boolean isPublic, List courseAreaNames, AsyncCallback callback); + void createEvent(String eventName, Date startDate, Date endDate, String description, boolean isPublic, + List courseAreaNames, Iterable imageURLs, Iterable videoURLs, + AsyncCallback callback); void updateEvent(UUID eventId, String eventName, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, - List regattaNames, AsyncCallback callback); + Iterable leaderboardGroupIds, Iterable imageURLs, Iterable videoURLs, + AsyncCallback callback); void createCourseArea(UUID eventId, String courseAreaName, AsyncCallback callback); @@ -384,7 +402,13 @@ public interface SailingServiceAsync { void getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback callback); - void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List> controlPoints, AsyncCallback callback); + void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List> controlPoints, AsyncCallback callback); + + void getRemoteSailingServerReferences(AsyncCallback> callback); + + void removeSailingServers(Set toRemove, AsyncCallback callback); + + void addRemoteSailingServerReference(RemoteSailingServerReferenceDTO sailingServer, AsyncCallback callback); void getResultImportUrls(String resultProviderName, AsyncCallback> callback); @@ -394,7 +418,7 @@ public interface SailingServiceAsync { void getUrlResultProviderNames(AsyncCallback> callback); - void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd, + void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd, AsyncCallback callback); void removeLeaderboardColumns(String leaderboardName, List columnsToRemove, AsyncCallback callback); @@ -439,12 +463,12 @@ public interface SailingServiceAsync { * list of values whose indices correspond with the elements in the {@link CompetitorDTO} list. */ void getLeaderboardDataEntriesForAllRaceColumns(String leaderboardName, Date date, DetailType detailType, - AsyncCallback, List>>> callback); + AsyncCallback, List>>> callback); void getLeaderboardsNamesOfMetaleaderboard(String metaLeaderboardName, - AsyncCallback>> callback); + AsyncCallback>> callback); - void checkLeaderboardName(String leaderboardName, AsyncCallback> callback); + void checkLeaderboardName(String leaderboardName, AsyncCallback> callback); void getBuildVersion(AsyncCallback callback); @@ -480,9 +504,10 @@ public interface SailingServiceAsync { void getCompetitors(AsyncCallback> asyncCallback); - void getCompetitorsOfLeaderboard(String leaderboardName, AsyncCallback> asyncCallback); + void getCompetitorsOfLeaderboard(String leaderboardName, boolean lookInRaceLogs, + AsyncCallback> asyncCallback); - void updateCompetitor(CompetitorDTO competitor, AsyncCallback asyncCallback); + void addOrUpdateCompetitor(CompetitorDTO competitor, AsyncCallback asyncCallback); void allowCompetitorResetToDefaults(Iterable competitors, AsyncCallback asyncCallback); @@ -505,7 +530,7 @@ public interface SailingServiceAsync { * pass identifier will still be returned, but the start time field will be null. */ void getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName, - AsyncCallback> callback); + AsyncCallback> callback); void getAllIgtimiAccountEmailAddresses(AsyncCallback> callback); @@ -525,6 +550,87 @@ public interface SailingServiceAsync { AsyncCallback asyncCallback); void getLeaderboardsByEvent(EventDTO event, AsyncCallback> callback); + void denoteForRaceLogTracking(String leaderboardName, + String raceColumnName, String fleetName, + AsyncCallback callback); + + void denoteForRaceLogTracking(String leaderboardName, AsyncCallback callback); + + void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, + AsyncCallback callback); + + void setCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName, + Set competitors, AsyncCallback callback); + + void getCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName, + AsyncCallback> callback); + + void addMarkToRaceLog(String leaderboardName, String raceColumnName, String fleetName, MarkDTO markDTO, + AsyncCallback callback); + + void getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName, + AsyncCallback> callback); + + void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName, + List> course, AsyncCallback callback); + + void getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName, + AsyncCallback callback); + + void pingMarkViaRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, MarkDTO mark, + PositionDTO position, AsyncCallback callback); + + void getDeserializableDeviceIdentifierTypes(AsyncCallback> callback); + + void getDeviceMappingsFromRaceLog(String leaderboardName, String raceColumnName, String fleetName, + AsyncCallback> callback); + + void addDeviceMappingToRaceLog(String leaderboardName, String raceColumnName, String fleetName, + DeviceMappingDTO mapping, AsyncCallback callback); + + void closeOpenEndedDeviceMapping(String leaderboardName, String raceColumnName, String fleetName, + DeviceMappingDTO mapping, Date closingTimePoint, AsyncCallback callback); + + void revokeRaceLogEvents(String leaderboardName, String raceColumnName, String fleetName, List eventIds, + AsyncCallback callback); + void removeSeries(RegattaIdentifier regattaIdentifier, String seriesName, AsyncCallback callback); -} + void removeDenotationForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, + AsyncCallback callback); + + void copyCourseAndCompetitorsToOtherRaceLogs(Util.Triple raceLogFrom, + Set> raceLogsTo, AsyncCallback callback); + + void getGPSFixImporterTypes(AsyncCallback> callback); + + void getTrackFileImportDeviceIds(List uuids, AsyncCallback> callback); + + /** + * A client should search a server in a two-step process. First, the client should ask the server which other + * servers are available for searching additional content. Then, in a second step, the client should fire the + * queries by parallel asynchronous calls to the one server, passing the name of the remote server reference to + * search, or null in order to search the server to which the query is sent by the call. This allows a + * client to asynchronously receive the results from various servers, not requiring the client to block until all + * results from all servers have been received. The key reason for this two-step process is that the GWT RPC does + * not support streaming of results. + * + * @return the list of server reference names, corresponding with {@link RemoteSailingServerReference#getName()}, to + * be used as parameter in {@link #search(String, KeywordQuery)}. This list does not contain the + * null value used to represent the search on the main server to which the query is sent. + */ + void getSearchServerNames(AsyncCallback> callback); + + + /** + * Call this method once for each result of {@link #getSearchServerNames()} and once with null for + * the serverNameOfNullForMain parameter. + * + * @param serverNameOrNullForMain + * use null to search on the server to which this request is sent; use a name as retrieved + * by {@link #getSearchServerNames()} which corresponds to a name of a + * {@link RemoteSailingServerReference}, to search a remote server. + */ + void search(String serverNameOrNullForMain, KeywordQuery query, + AsyncCallback> callback); +} 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 7226bd13a11..d9214686df8 100755 --- 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 @@ -289,7 +289,8 @@ public interface StringMessages extends Messages { String leaderboardsInGroup(); String replication(); String errorFetchingReplicaData(String message); - String averageCrossTrackErrorInMeters(); + String averageAbsoluteCrossTrackErrorInMeters(); + String averageSignedCrossTrackErrorInMeters(); String enterMaster(); String errorStartingReplication(String hostname, String exchangeName, String message); String helpLines(); @@ -357,6 +358,12 @@ public interface StringMessages extends Messages { String pleaseEnterNonEmptyUrl(); String addResultImportUrl(); String errorAddingResultImportUrl(String message); + String sailingServers(); + String errorRefreshingSailingServers(String message); + String successfullyUpdatedSailingServers(); + String errorRemovingSailingServers(String message); + String addSailingServer(); + String errorAddingSailingServer(String message); String errorTryingToObtainLeaderboardContents(String message); String lastScoreUpdate(); String warningBrowserUnsupported(); @@ -364,14 +371,17 @@ public interface StringMessages extends Messages { String controlPoint(); String errorUpdatingRaceCourse(String message); String successfullyUpdatedCourse(); - String selectOneMarkOrTwoMarksForGate(); + String selectTwoMarksForGate(); String errorTryingToObtainTheMarksOfTheRace(String message); String errorTryingToObtainRaceCourse(String message); String insertWaypointBeforeSelected(); String insertWaypointAfterSelected(); + String addGate(); String scoringSchemeLowPointSystem(); String scoringSchemeHighPointSystem(); String scoringSchemeLowPointWinnerGetsZero(); + String scoringSchemeWinnerGetsFive(); + String scoringSchemeWinnerGetsSix(); String scoringSystem(); String createFlexibleLeaderboard(); String createRegattaLeaderboard(); @@ -485,8 +495,10 @@ public interface StringMessages extends Messages { String totalTimeSailedReachingInSecondsTooltip(); String totalTimeSailedUpwindInSecondsTooltip(); String windwardDistanceToGoInMetersTooltip(); - String averageCrossTrackErrorInMetersTooltip(); - String raceAverageCrossTrackErrorInMetersTooltip(); + String averageAbsoluteCrossTrackErrorInMetersTooltip(); + String averageSignedCrossTrackErrorInMetersTooltip(); + String raceAverageAbsoluteCrossTrackErrorInMetersTooltip(); + String raceAverageSignedCrossTrackErrorInMetersTooltip(); String gapChangeSinceLegStartInSecondsTooltip(); String velocityMadeGoodInKnotsTooltip(); String sideToWhichMarkAtLegStartWasRoundedTooltip(); @@ -965,6 +977,42 @@ public interface StringMessages extends Messages { String releaseNotes(); String hasSplitFleetContiguousScoring(); String eventOverview(); + String addRaceLogTracker(); + String addRaceLogTrackers(); + String denoteForRaceLogTracking(); + String raceLogTracking(); + String startRaceLogTracking(); + String filterByLeaderboard(); + String removeLeaderboardFilter(); + String competitorRegistrations(); + String trackerStatus(); + String canOnlyBeEditedBeforeStartingTracking(); + String defineCourse(); + String mapDevices(); + @Key("edit_what") + String edit(String what); + @Key("add_what") + String add(String what); + @Key("remove_what") + String remove(String what); + String pleaseEnterA(String what); + String pingPosition(String whose); + String pleaseEnterAValidValueFor(String what, String validTemplate); + String copyCourseAndCompetitors(); + String nowSelectRaceColumnAndFleetToCopyTo(); + String selectRowInTableToCopyCourse(); + String deviceId(); + String deviceType(); + String mappedToType(); + String mappedTo(); + String deviceMappings(); + String atMostOneEndOfTheTimeRangeMayBeOpen(); + String pleaseSelectAnItemToMapTo(); + String device(); + String timeRange(); + String closeTimeRange(); + String setClosingTimePoint(); + String startOfTimeRangeMustLieBeforeEnd(); String start(); String overallProgress(); String showAddedScores(); @@ -973,4 +1021,26 @@ public interface StringMessages extends Messages { String importWindTooltip(); String authorName(); String authorPriority(); + String removeDenotation(); + String competitorRegistrationsMissingProceed(String namesOfRaces); + String pleaseSelectAControlPoint(); + String twoMarkControlPoint(); + String registeredCompetitors(); + String competitorPool(); + String selectAtLeastOne(); + String selectRaces(); + String importFixes(); + String noSailingServerInstancesYet(); + String registeredSailingServerInstances(); + String filterEventsByName(); + String leaderboardsExceptFromSelectedGroup(); + String availableLeaderboardGroups(); + String leaderboardGroupsOfSelectedEvent(); + String addRaces(); + String withNamePrefix(); + String pleaseSelectASeriesFirst(); + String imageURLs(); + String videoURLs(); + String manage2SailFinalNotice(); + String invalidSelection(); } 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 972f8375291..b16a9c6cc3d 100755 --- 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 @@ -311,9 +311,12 @@ racesInLeaderboard=Races in leaderboard leaderboardsInGroup=Leaderboards in group replication=Replication errorFetchingReplicaData=Error fetching replica data: {0} -averageCrossTrackErrorInMeters=\u2205 XTE -averageCrossTrackErrorInMetersTooltip=The average cross track error to the great circle segment connecting leg start with leg end -raceAverageCrossTrackErrorInMetersTooltip=The average cross track error to the great circle segments connecting leg start with leg end during this race. +averageAbsoluteCrossTrackErrorInMeters=\u2205 XTE +averageAbsoluteCrossTrackErrorInMetersTooltip=The average cross track error to the great circle segment connecting leg start with leg end +averageSignedCrossTrackErrorInMeters=\u2205 XTE +/- +averageSignedCrossTrackErrorInMetersTooltip=The average position on the course as a signed distance from the course middle line; 0 meaning on the course middle line; negative values mean left-hand side, positive values mean right-hand side. +raceAverageAbsoluteCrossTrackErrorInMetersTooltip=The average absolute cross track error to the great circle segments connecting leg start with leg end for all upwind legs of this race, ignoring to which side of the course middle line the competitor is +raceAverageSignedCrossTrackErrorInMetersTooltip=The signed average cross track error to the great circle segments connecting leg start with leg end for all upwind legs of this race; negative values mean left of the course middle line, positive values mean right. enterMaster=Hostname of Master Instance errorStartingReplication=Error starting replication from host {0} / exchange {1}: {2} helpLines=Help lines @@ -382,6 +385,12 @@ errorRemovingResultImportUrls=Error removing result import URLs: {0} pleaseEnterNonEmptyUrl=Please enter a URL. addResultImportUrl=Add result import URL errorAddingResultImportUrl=Error adding result import URL: {0} +sailingServers=SailingServer instances +errorRefreshingSailingServers=Error refreshing SailingServer instances: {0} +successfullyUpdatedSailingServers=Successfully updated SailingServer instances +errorRemovingSailingServers=Error removing SailingServer instances: {0} +addSailingServer=Add SailingServer instance +errorAddingSailingServer=Error adding SailingServer instance: {0} errorTryingToObtainLeaderboardContents=Error trying to obtain leaderboard contents: {0} lastScoreUpdate=Last Score Update warningBrowserUnsupported=This website is not optimized for your browser. Please switch to a newer one like Firefox or Chrome. @@ -389,16 +398,19 @@ newMark=New Mark controlPoint=Mark/Gate errorUpdatingRaceCourse=Error updating race course: {0} successfullyUpdatedCourse=Successfully updated race course -selectOneMarkOrTwoMarksForGate=Select a single mark or two marks to create a gate +selectTwoMarksForGate=Select two marks to create a gate errorTryingToObtainTheMarksOfTheRace=Error trying to obtain the marks of the race course: {0} errorTryingToObtainRaceCourse=Error trying to obtain the race course: {0} insertWaypointBeforeSelected=Insert waypoint before selected insertWaypointAfterSelected=Insert waypoint after selected +addGate=Add Gate scoringSchemeLowPointSystem=Low Point System scoringSchemeHighPointSystem=High Point System scoringSchemeHighPointEssOverall=High Point System ESS Overall scoringSchemeHighPointLastBreaksTie=High Point System, Last Race Breaks Tie scoringSchemeLowPointWinnerGetsZero=Low Point System, Winner Gets Zero +scoringSchemeWinnerGetsFive=High Point System, Winner Gets 5 Points +scoringSchemeWinnerGetsSix=High Point System, Winner Gets 6 Points scoringSystem=Scoring system createFlexibleLeaderboard=Create flexible leaderboard createRegattaLeaderboard=Create regatta leaderboard @@ -419,7 +431,7 @@ totalTimeSailedUpwindInSecondsTooltip=The total time the competitor sailed upwin totalTimeSailedReachingInSeconds=Total time reaching totalTimeSailedReachingInSecondsTooltip=The total time the competitor sailed on reaching legs in the regatta. totalTimeSailedInSeconds=Total time -totalTimeSailedInSecondsTooltip=The total time the competitor sailed in the regatta. +totalTimeSailedInSecondsTooltip=The total time the competitor sailed in the regatta, counted from race start time. totalDistanceTraveled=Total distance totalDistanceTraveledTooltip=The total distance sailed in the regatta. totalAverageSpeedOverGround=\u2205 SOG @@ -927,7 +939,7 @@ emailAddress=e-mail address password=Password doYouReallyWantToRemoveLeaderboard=Do you really want to remove the leaderboard: ''{0}''? metaLeaderboardCannotBeChanged=This is a meta leaderboard. It can''t be changed here. -failedToSetNewStartTime=Failed to set new start time. +failedToSetNewStartTime=Failed to set new start time. This can happen when the same start time is already present in the race log or someone has added a start time that is newer (in terms of a pass id and logical time) than the one that you just added. doYouReallyWantToRemoveIgtimiAccount=Do you really want to remove Igtimi account granting access to data of user {0}? igtimiWindImport=Import Wind from Igtimi seeIgtimiTabForAccountSettings=See the ''Igtimi'' tab for account settings determining which data from Igtimi will be considered. @@ -950,6 +962,40 @@ use=Use groupingProvider=Grouping Provider releaseNotes=News and Release History hasSplitFleetContiguousScoring=Split fleets scored contiguously +eventOverview=Event Overview +addRaceLogTracker=Add RaceLog Tracker +addRaceLogTrackers=Add RaceLog Trackers +denoteForRaceLogTracking=Denote for RaceLog tracking +raceLogTracking=RaceLog Tracking +startRaceLogTracking=Start RaceLog Tracking +filterByLeaderboard=Filter by Leaderboard +removeLeaderboardFilter=Remove Leaderboard filter +competitorRegistrations=Competitor Registrations +trackerStatus=Tracker Status +canOnlyBeEditedBeforeStartingTracking=Can only be edited before starting tracking +defineCourse=Define Course Layout +mapDevices=Map Devices to Competitors and Marks +edit_what=Edit {0} +add_what=Add {0} +remove_what=Remove {0} +pleaseEnterA=Please enter a {0} +pingPosition=Ping {0} position +pleaseEnterAValidValueFor=Please enter a valid value for {0} ({1}) +copyCourseAndCompetitors=Copy course and competitors to other races +nowSelectRaceColumnAndFleetToCopyTo=Now select the race column and fleet to copy the course to. +selectRowInTableToCopyCourse=Select a single row in the table, to which to copy the course. +deviceId=Device ID +deviceType=Device Type +mappedToType=Mapped to Type +mappedTo=Mapped to +deviceMappings=Device Mappings +atMostOneEndOfTheTimeRangeMayBeOpen=At most one end of the mapping''s time range may be left open (left unset) +pleaseSelectAnItemToMapTo=Please select an item to map the device to +device=Device +timeRange=Time Range +closeTimeRange=Close open end of Time Range +setClosingTimePoint=Set Timepoint that closes open time range +startOfTimeRangeMustLieBeforeEnd=The start of the time range must lie before its end overallProgress=Overall Progress eventOverview=Event Overview start=Start @@ -958,4 +1004,26 @@ ratio=ratio importWind=Import Wind importWindTooltip=Unchecking this option will drastically lower the amount of data transfered. authorName=Author name -authorPriority=Author priority \ No newline at end of file +authorPriority=Author priority +removeDenotation=Remove Denotation +competitorRegistrationsMissingProceed=At least one of the selected races ({0}) is missing competitor registrations. Proceed anyway? +pleaseSelectAControlPoint=Please select a control point +twoMarkControlPoint=2-mark-controlpoint +registeredCompetitors=Registered Competitors +competitorPool=Competitor Pool +selectAtLeastOne=Select at least one +selectRaces=Select the Races +importFixes=Import Fixes +noSailingServerInstancesYet=No Sailing Server instances yet. +registeredSailingServerInstances=Registered Sailing Server Instances +filterEventsByName=Filter Events by name: +leaderboardsExceptFromSelectedGroup=Leaderboards (except those of selected group) +availableLeaderboardGroups=Available Leaderboard Groups +leaderboardGroupsOfSelectedEvent=Leaderboard Groups Assigned to Event +addRaces=Add races +withNamePrefix=with name prefix +pleaseSelectASeriesFirst=Please select a series first. +imageURLs=Image URLs +videoURLs=Video URLs +manage2SailFinalNotice=For Manage2Sail XRR please note:\nAs long as the final flag is not set for the Regatta in the json\nDocument, the regatta results will not be shown in the list. +invalidSelection=The current selection isn''t valid. 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 6dae002db2e..9ee61858287 100755 --- 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 @@ -312,9 +312,12 @@ racesInLeaderboard=Rennen im Leaderboard leaderboardsInGroup=Leaderboards in der Gruppe replication=Replikation errorFetchingReplicaData=Fehler beim Beschaffen der Replikationsdaten: {0} -averageCrossTrackErrorInMeters=\u2205 XTE -averageCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie in diesem Schenkel -raceAverageCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie in diesem Rennen +averageAbsoluteCrossTrackErrorInMeters=\u2205 XTE +averageAbsoluteCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie in diesem Schenkel; die Seite, zu der der Teilnehmer abweicht, spielt dabei keine Rolle. +raceAverageAbsoluteCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie für alle Kreuzen in diesem Rennen; die Seite, zu der der Teilnehmer abweicht, spielt dabei keine Rolle. +averageSignedCrossTrackErrorInMeters=\u2205 XTE +/- +averageSignedCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie in diesem Schenkel, wobei negative Werte Abweichungen nach links, positive Werte Abweichungen nach rechts bedeuten. +raceAverageSignedCrossTrackErrorInMetersTooltip=Die durchschnittliche Querabweichung zur Kursmittellinie für alle Kreuzen diesem Rennen, wobei negative Werte Abweichungen nach links, positive Werte Abweichungen nach rechts bedeuten. enterMaster=Hostname der Master-Instanz errorStartingReplication=Fehler beim Starten der Replikation von Host {0} / Exchange {1}: {2} helpLines=Hilfslinien @@ -383,6 +386,12 @@ errorRemovingResultImportUrls=Fehler beim Löschen von Ergebnisimport URLs: {0} pleaseEnterNonEmptyUrl=Bitte gebe eine URL ein. addResultImportUrl=Ergebnisimport-URL hinzufügen errorAddingResultImportUrl=Fehler beim Hinzufügen einer Ergebnisimport URL: {0} +sailingServers=SailingServer Instanzen +errorRefreshingSailingServers=Fehler beim Aktualisieren der SailingServer Instanzen: {0} +successfullyUpdatedSailingServers=SailingServer Instanzen erfolgreich aktualisiert +errorRemovingSailingServers=Fehler beim Löschen von SailingServer Instanzen: {0} +addSailingServer=SailingServer Instanz hinzufügen +errorAddingSailingServer=Fehler beim Hinzufügen einer SailingServer Instanz: {0} errorTryingToObtainLeaderboardContents=Fehler beim Berechnen der Ergebnisliste: {0} lastScoreUpdate=Letzte Ergebnisänderung warningBrowserUnsupported=Diese Webseite ist für Ihren Browser nicht optimiert. Wir empfehlen den Firefox oder Chrome in der neuesten Version. @@ -390,16 +399,19 @@ newMark=Neue Marke controlPoint=Einzelmarke/Gate errorUpdatingRaceCourse=Fehler beim Aktualisieren des neuen Kurses: {0} successfullyUpdatedCourse=Kurs erfolgreich aktualisiert -selectOneMarkOrTwoMarksForGate=Eine einzelne Marke auswählen oder zwei Marken, um ein Gate zu erstellen +selectTwoMarksForGate=Zwei Marken auswählen, um ein Gate zu erstellen errorTryingToObtainTheMarksOfTheRace=Fehler beim Ermitteln der Marken des Regattakurses: {0} errorTryingToObtainRaceCourse=Fehler beim Ermitteln des Rennkurses: {0} insertWaypointBeforeSelected=Wegpunkt nach dem selektierten einfügen insertWaypointAfterSelected=Wegpunkt vor dem selektierten einfügen +addGate=Gate hinzufügen scoringSchemeLowPointSystem=Low Point System scoringSchemeHighPointSystem=High Point System scoringSchemeHighPointEssOverall=High Point System ESS Gesamtwertung scoringSchemeHighPointLastBreaksTie=High Point System, letztes Rennen entscheidet bei Gleichstand scoringSchemeLowPointWinnerGetsZero=Low Point System, Gewinner erhält 0 Punkte +scoringSchemeWinnerGetsFive=High Point System, Gewinner erhält 5 Punkte +scoringSchemeWinnerGetsSix=High Point System, Gewinner erhält 6 Punkte scoringSystem=Bepunktungssystem createFlexibleLeaderboard=Flexible Rangliste anlegen createRegattaLeaderboard=Rangliste für Regatta anlegen @@ -420,7 +432,7 @@ totalTimeSailedUpwindInSecondsTooltip=Gesamtzeit, die der Teilnehmer in der Rega totalTimeSailedReachingInSeconds=Gesamtzeit Holeschlag totalTimeSailedReachingInSecondsTooltip=Gesamtzeit, die der Teilnehmer in der Regatta auf Holeschlägen gesegelt ist. totalTimeSailedInSeconds=Gesamtzeit -totalTimeSailedInSecondsTooltip=Gesamtzeit, die der Teilnehmer für die Regatta gebraucht hat. +totalTimeSailedInSecondsTooltip=Gesamtzeit, die der Teilnehmer für die Regatta gebraucht hat, gezählt ab der jeweiligen Startzeit jeder Wettfahrt. totalDistanceTraveled=Gesamtdistanz totalDistanceTraveledTooltip=Gesamtdistanz, die der Teilnehmer in der Regatta zurückgelegt hat. totalAverageSpeedOverGround=\u2205 FüG @@ -946,6 +958,40 @@ use=Verwende groupingProvider=Gruppierungs-Versorger releaseNotes=Neuigkeiten und Änderungshistorie hasSplitFleetContiguousScoring=Gruppen durchgängig bepunkten +eventOverview=Event Übersicht +addRaceLogTracker=RaceLog Tracker hinzufügen +addRaceLogTrackers=RaceLog Tracker hinzufügen +denoteForRaceLogTracking=Für RaceLog Tracking markieren +raceLogTracking=RaceLog Tracking +startRaceLogTracking=RaceLog Tracking starten +filterByLeaderboard=Nach Rangliste filtern +removeLeaderboardFilter=Nicht nach Rangliste filtern +competitorRegistrations=Team Anmeldungen +trackerStatus=Tracker-Status +canOnlyBeEditedBeforeStartingTracking=Kann nur vor Beginn des Trackings bearbeitet werden +defineCourse=Kurs definieren +mapDevices=Gerätezuordnung zu Teams und Tonnen +edit_what={0} bearbeiten +add_what={0} hinzufügen +remove_what={0} entfernen +pleaseEnterA=Bitte {0} eingeben. +pingPosition={0}-Position pingen +pleaseEnterAValidValueFor=Bitte geben Sie für {0} einen gültigen Wert ein ({1}) +copyCourseAndCompetitors=Kurs und Teams in andere Rennen kopieren +nowSelectRaceColumnAndFleetToCopyTo=Bitte wählen Sie nun die Renn-Spalte und Flotte zum Kopieren des Kurses aus. +selectRowInTableToCopyCourse=Bitte selektieren Sie eine einzelne Zeile der Tabelle, um den Kurs dorthin zu kopieren. +deviceId=Geräte ID +deviceType=Gerätetyp +mappedToType=Zuordnungs-typ +mappedTo=Zugeordnet zu +deviceMappings=Geräte Zuordnungen +atMostOneEndOfTheTimeRangeMayBeOpen=Es darf höchstens ein Ende der zeitlichen Zuordnung offen bleiben (nicht gesetzt sein) +pleaseSelectAnItemToMapTo=Bitte wählen Sie ein Objekt aus, dem das Gerät zugeordnet werden soll +device=Gerät +timeRange=Zeitspanne +closeTimeRange=Offenes Ende schließen +setClosingTimePoint=Zeitpunkt setzen um offene Zeitspanne zu schließen +startOfTimeRangeMustLieBeforeEnd=Der Anfang der Zeitspanne muss vor deren Ende liegen overallProgress=Gesamtfortschritt eventOverview=Veranstaltungsübersicht start=Start @@ -954,4 +1000,24 @@ ratio=Verhältnis importWind=Wind importieren importWindTooltip=Diese Option zu deselektieren führt zu einer wesentlich kleineren übertragenen Datenmenge. authorName=Benutzername -authorPriority=Benutzer-Priorität +authorPriority=Benutzer-Priorität +removeDenotation=Markierung entfernen +competitorRegistrationsMissingProceed=Für mindestens eins der Rennen ({0}) sind keine Teams registriert. Trotzdem fortfahren? +pleaseSelectAControlPoint=Bitte eine Einzelmark/ein Gate auswählen +twoMarkControlPoint=2-Marken-Kontrollpunkt +registeredCompetitors=Registrierte Teams +competitorPool=Team Vorrat +importFixes=Fixes importieren +noSailingServerInstancesYet=Bisher noch keine Sailing Server-Instanzen. +registeredSailingServerInstances=Registrierte Sailing Server-Instanzen +filterEventsByName=Events nach Name filtern: +leaderboardsExceptFromSelectedGroup=Ranglisten (außer denen der ausgewählten Gruppe) +availableLeaderboardGroups=Verfügbare Ranglisten-Gruppen +leaderboardGroupsOfSelectedEvent=Der Veranstaltung zugewiesene Ranglisten-Gruppen +addRaces=Wettfahrten hinzufügen +withNamePrefix=mit Namenspräfix +pleaseSelectASeriesFirst=Bitte zuerst eine Serie auswählen. +imageURLs=Bilder-URLs +videoURLs=Video-URLs +manage2SailFinalNotice=Für Manage2Sail XRR bitte beachten:\nSolange das final flag für die Regatta im JSON Dokument noch nicht\ngesetzt ist, werden keine entsprechenden Ergebnisse angezeigt. +invalidSelection=Die aktuelle Selektion ist nicht gültig. \ No newline at end of file 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 new file mode 100644 index 00000000000..58e86a16b6f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -0,0 +1,979 @@ +# If you want new properties translated then sent this over GMR (Mark Angell ) +helloWorld=Привет Мир +kielWeel2011=Кильская Неделя 2011 +stgAccount=Счет STG +tracTracEvents=События Трак Трак +swissTimingEvents=События Swiss Timing +trackedBefore=Ранее Отслеженные +listRaces=Перечень Гонок +wind=ветра +windPanelLabel=Это окно информации о ветре, которое до сих пор абсолютно пустое. +refresh=Обновить +remove=Удалить +windSource=Источники информации о ветре +dampeningInterval=Интервал ослабления +map=Карта +race=Гонка +races=Гонки +tracked=Отслежена +time=Время +timeTooltip=Учет времени участника, выступавшего на этом этапе. На первом этапе учет времени ведется с начала гонки, а не при пересечении участником линии старта. +playSpeed=Скорость воспроизведения +playSpeedHelp=Используйте "1" для обычной скорости воспроизведения, "2" для удвоенной скорости воспроизведения и т. д. +playModeLive=В прямом эфире +playModeReplay=Режим повтора +timeDelay=Задержка +timeDelayHelp=Не указывайте, для воспроизведения повтора в любой момент времени; введите секунды (например, "60" для одной минуты или "0" без указания) задержки перед загрузкой данных в режиме реального времени +speedInKnots=Скорость (узлы) +fromDeg=Начиная с (градус) +noRacesYet=Гонок пока нет. +total=\u03A3 +carry=Суммарная +competitor=Участник +competitors=Участники +defaultLeaderboard=Таблица лидеров по умолчанию +noSuchLeaderboard=Такая таблица лидеров отсутствует +leaderboardConfiguration=Форма таблицы лидеров +newDotDotDot=Новый... +editScores=Изменить баллы... +columnNamesInSelectedLeaderboard=Названия гонок в выбранной таблице лидеров +addDotDotDot=Добавить... +renameDotDotDot=Переименовать... +trackedRaceConnectedToSelectedRaceName=Отслеженная гонка, соединенная с выбранным названием гонки +linkToColumn=Соединить со столбцом +unlink=Отсоединить +leaderboardName=Имя таблицы лидеров +cancel=Отмена +pleaseEnterAName=Пожалуйста, введите имя +pleaseEnterABoatClass=Пожалуйста, введите класс судна +discardRacesFromHowManyStartedRacesOn=Не учитывать еще одну начинающуюся гонку в количестве начавшихся гонок +leaderboardWithThisNameAlreadyExists=Таблица лидеров с таким именем уже существует +discardThresholdsMustBeNumeric=Предельные значения не учитываемых должны быть выражены числом +discardThresholdsMustBeAscending=Предельные значения не учитываемых должны быть возрастающими +discarding=Неучет +startingFromNumberOfRaces=Начиная с количества гонок +renameLeaderboard=Переименовать таблицу лидеров +addColumnToLeaderboard=Добавить столбец в таблицу лидеров +pleaseEnterNameForNewRaceColumn=Введите, пожалуйста, имя для столбца новой гонки +ok=OK +medalRace=Медальная гонка +renameRace=Переименовать гонку +openSelectedLeaderboard=Открыть выбранную таблицу лидеров +rank=Занимаемое место +rankTooltip=Место, занимаемое участником в гонке +regattaRank=Место, занимаемое в регате +regattaRankTooltip=Место, занимаемое участником в регате +overallRank=Итоговое место +overallRankTooltip=Место, занимаемое участником на этапах +totalRegattaRank=Итоговое место в регате +collapse=- +expand=+ +leg=L +distanceInMeters=Расстояние +averageSpeedInKnots=\u2205 Скорость +averageSpeedInKnotsTooltip=Средняя скорость относительно земли в ходе этого этапа,\nрассчитывается путем деления пройденного расстояния на затраченное на это время. +raceAverageSpeedInKnotsTooltip=Средняя скорость относительно земли в ходе этой гонки,\nрассчитывается путем деления пройденного расстояния на затраченное на это время. +rankGain=Улучшение места +rankGainTooltip=Разность между местом, занятым на текущем и предыдущем этапах. +sapSailingAnalytics=Аналитическая информация парусного спорта +leaderboard=Таблица лидеров +leaderboards=Таблицы лидеров +leaderboardSettings=Параметры таблицы лидеров +settings=Параметры +selectAtLeastOneLegDetail=Выберите хотя бы одну подробность об этапе +currentSpeedOverGroundInKnots=Скорость относительно земли (SOG) +currentSpeedOverGroundInKnotsTooltip=Текущая скорость относительно земли. +estimatedTimeToNextWaypointInSeconds=Расчетное время прибытия (ETA). +estimatedTimeToNextWaypointInSecondsTooltip= Расчетное время, необходимое участнику для прибытия в следующую промежуточную точку маршрута, исходя из скорости лавирования участника и с учетом истинного направление ветра. +gapToLeaderInSeconds=Отставание от лидера +gapToLeaderInSecondsTooltip=Время, необходимое участнику для достижения текущего положения лидера, исходя из скорости лавирования. +velocityMadeGoodInKnots=Скорость лавирования (VMG) +velocityMadeGoodInKnotsTooltip=Скорость лавирования, представляет собой скорость продвижения на ветер/ по ветру для этапов движения против ветра/ по ветру соответственно, или скорость в направлении движения при лавировании по ветру +windwardDistanceToGoInMeters=Расстояние, которое необходимо пройти на ветер +windwardDistanceToGoInMetersTooltip=Расстояние движения на ветер/ по ветру от текущего положения участника до конца этапа для этапов движения против ветра/ по ветру соответственно, или скорость в направлении движения при лавировании по ветру. +windwardDistanceToLeaderInMeters=Расстояние на ветер до лидера +windwardDistanceToLeaderInMetersTooltip=Расстояние на ветер/ под ветер от участника до лидера для этапов движения против ветра/ по ветру соответственно или скорость в направлении движения при лавировании по ветру. +name=Имя +leaveEmptyForDefault=Оставьте пустым по умолчанию. +trackNewEvent=Отследить новое событие +trackableRaces=Отслеживаемые гонки +trackWind=Отслеживать ветер +regattaName=Название регаты +eventName=Название события +event=Событие +startTime=Время старта +regatta=Регата +regattas=Регаты +startTracking=Начать отслеживание +stopTracking=Остановить отслеживание +connections=Подключения +historyOfConnections=История подключений +defineNewConnection=Определить новое подключение +trackedRaces=Отслеженные гонки +canSendRequests=Можно отправить запрос +connectAndReadRaces=Подключиться и прочитать данные о гонке +ports=Порты +liveData=Данные в реальном времени +storedData=Сохраненные данные +uris=URI-адреса +jsonUrl=JSON URL +livePort=Порт данных в реальном времени +storedPort=Порт сохраненных данных +liveUri=Данные пользователя в реальном времени +storedUri=Сохраненные данные пользователя +eventsConnectedTo=Текущие отслеживаемые события +hostname=Имя узла +declinationCheckbox=Корректировка направления ветра с помощью отклонения +raceStartTrackingColumn=Отслеживание начато +pauseAutomaticRefresh=Приостановка автоматического обновления +autoRefresh=Обновлять автоматически +delayInSeconds=Задержка (и) +delayBetweenAutoAdvances=Задержка между автоматическими обновлениями в секундах: +delayMustBeNonNegative=Значение задержки должно быть больше нуля +chooseUpdateIntervalOfAtLeastOneSecond=Выберите интервал обновления, он должен быть не менее одной секунды +errorSettingWindForRace=Ошибка при настройке параметра ветер для гонки +errorFetchingWindInformationForRace=Ошибка при получении данных о ветре для гонки +errorWhileTryingToSetWindSourceForRace=Ошибка при попытке установить флажок «начинается встречный ветер» +inEvent=В случае +to=к +showEstimatedWind=Показать оценочный ветер +timing=Хронометраж +raceDetailsToShow=Подробные сведения о гонке +overallDetailsToShow=Общие сведения +legDetailsToShow=Сведения об этапе +columnMoveUp=Вверх +columnMoveDown=Вниз +port=Порт +raceStartTimeColumn=Гонка началась +showOnlySelectedCompetitors=Показать только выбранных участников +showSelectedCompetitorsInfo=Показать окно краткого описания только для выбранных участников +tailLength=Длина (с) хвоста +selectedRaces=Какие гонки вы хотели бы отобразить? +lengthInSeconds=Длина (с) +stressTest=Стресс тест +numberOfManeuvers=Маневры +numberOfManeuversTooltip=Количество маневров, выполненных во время этого этапа. +numberOfManeuversInRaceTooltip=Количество маневров, выполненных во время этой гонки. +tacks=Галсы +jibes=Перекладывания паруса +penaltyCircles=Штрафные круги +medalRaceIsNull=Значение медальной гонки недопустимо +configuration=Конфигурация +maneuverTypes=Маневры +chooseChart=Выбрать график +distanceTraveled=Пройденное расстояние +distanceTraveledTooltip=Расстояние, пройденное от старта до финиша этапа\ а не до текущего момента времени, если этап не завершен. +raceDistanceTraveledTooltip=Расстояние, пройденное от старта до финиша гонки\ а не до текущего момента времени, если гонка не завершена. +velocityMadeGoodLong=Скорость лавирования +speedOverGroundLong=Скорость относительно земли +gapToLeaderLong=Отставание от лидера +pointsToLoad=Баллов для нагрузки +speed=Скорость +distance=Расстояние +in=в +secondsUnit=с +metersUnit=м +close=Закрыть +compareCompetitors=Сравнить участников +description=Описание +sailNumber=Номер на парусе +country=Страна +no3LetterCodes=Не удалось найти 3-х буквенные коды стран МОК. +add=Добавить +delete=Удалить +showCharts=Показать графики +raceWithThisNameAlreadyExists=Гонка с таким именем уже существует. +raceWithThisNameAlreadyExistsInRegatta=Гонка с таким именем уже существует в этой регате. +headUp=Заголовок +bearAway=Поворачивать под ветер +tack=Галс +tackTooltip=Количество галсов совершенных во время этой гонки. +jibe=Перебрасывать парус +jibeTooltip=Количество перебрасываний паруса во время этой гонки. +penaltyCircle=Штрафной круг +penaltyCircleTooltip=Количество штрафных кругов, пройденных в ходе этой гонки. +markPassing=Проход знака +mark=Знак +otherManeuver=Неизвестный маневр +douglasPeuckerPoints=Точки алгоритма Дугласа-Пойкера +filterRacesByName=Сортировать гонки по названию +trackSettings=Отслеживать параметры +actionEdit=Изменить +actionEditCompetitors=Изменить участников +actionEditScores=Изменить баллы +actionOpenBrowser=Открыть в браузере +actionRemove=Удалить +actionEditSeries=Изменить серии +actionRaceEdit=Изменить или переименовать гонку +actionRaceUnlink=Отсоединить гонку +actionRaceRemove=Удалить гонку +actionExportXML=Экспорт в формате XML +filterLeaderboardsByName=Сортировать таблицы лидеров по названию +actions=Действия +islinked=Соединен +yes=Да +no=Нет +hoverOverAPoint=Наведите курсор на точку для просмотра подробных сведений +noSelection=Ничего не выбрано +raceIsKnownToStartUpwind=Гонка начинается с этапа с наветренной стороны +events=События +settingsForComponent=Параметры {0} +noEventsFound=События не обнаружены +noEventSelected=События не выбраны +noLeaderboardsFound=Талицы лидеров не обнаружены +searchEvents=Поиск событий +location=Местонахождение +from=С +until=По +onlyLiveEvents=Только события в прямом эфире +locationNotAvailable=Местонахождение недоступно +startDateNotAvailable=Дата начала недоступна +startDate=Дата начала +endDate=Дата окончания +unknownManeuver=Неизвестный маневр +tailLengthMustBePositive=Число длины хвоста должно быть больше нуля. +failedToLoadRaceInformation=Сбой при загрузке сведений о гонке: {0} +competitorPassedMarkAtDate={0} прошли {1} в {2} +valueForCompetitorAt={0} в {1}: {2} +reallyRemoveRace=Вы действительно хотите удалить гонку ''{0}''? Примечание: все записи о гонке будут также удалены без возможности восстановления! +showDetails=Показать подробности +hideLeaderboard=Скрыть таблицу лидеров +hideLeaderboards=Скрыть таблицы лидеров +showLeaderboard=Показать таблицу лидеров +hideRaces=Скрыть гонки +showMap=Показать карту +hideMap=Скрыть карту +noLeaderboardSelected=Таблица лидеров не выбрана +leaderboardGroups=Группы таблицы лидеров +filterLeaderboardGroupsByName=Сортировать группы таблицы лидеров по имени +createNewLeaderboardGroup=Создать группу таблицы лидеров +leaderboardGroupConfiguration=Конфигурация группы таблицы лидеров +legs=Этапы +currentLeg=На этапе +currentLegTooltip=Номер этапа, на котором находится участник в данный момент, начиная с 1 +leaderboardGroup=Группа таблицы лидеров +pleaseEnterNonEmptyDescription=Пожалуйста, введите непустое описание +groupWithThisNameAlreadyExists=Группа таблицы лидеров с этим именем уже существует. +detailsOfLeaderboardGroup=Сведения о группе таблицы лидеров +edit=Изменить +save=Сохранить +abort=Отмена +noLeaderboardGroupWithNameFound=Группа таблицы лидеров с именем {0} не обнаружена +overview=Обзор +welcomeToSailingAnalytics=Добро пожаловать в Аналитическую информацию парусного спорта SAP +welcomeToSailingAnalyticsBody=SAP приближает любителей парусного спорта к происходящим на воде событиям как никогда раньше.\ +\ Таблица лидеров в прямом эфире расширяет возможности для любителей, отображая в режиме реального времени место занимаемое участником, скорости судов, а также победителей с указанием отрыва. Следите за каждой гонкой и благодаря SAP не пропускайте ни одного совершенного маневра.\ +\ Это самый простой способ наблюдать за парусной гонкой в прямом эфире, где бы вы ни находились. +home=Главная +clickAndDragToZoomIn=Щелкните и перетащите область графика для увеличения +windSpeed=Скорость ветра (узлы) +allTimesInUTC=Время указано в универсальном глобальном времени по Гринвичу (UTC) +autoZoomTo=Автоматически увеличить до +autoZoomToBoats=Суден +autoZoomToBuoys=Буев +autoZoomToWindSensors=Датчиков ветра +autoZoomOff=Выкл +autoZoomToTails=Хвостов +autoZoomSelectedCompetitors=Масштабировать только выбранных участников +bearing=Направление (ветра) +zoom=Масштабирование +stepSizeMustBeGreaterThanNull=Размер шага должен быть больше 0 +stepSizeInSeconds=Размер шага в секундах +refreshInterval=Обновить интервал (ы) +refreshIntervalMustBeGreaterThanXSeconds=Обновление интервала должно быть больше, чем {0} секунд +competitorCharts=Графики участника +selectAtLeastOneCompetitor=Для отображения графиков участников, выберите хотя бы одного участника +directionChange=Изменение направления +speedChange=Изменение скорости +degreesShort=градус +untracked=Неотслеженная +delayForLiveMode=Задержка для режима прямого эфира: +notAvailable=Недоступно +details=Подробности +noGroupSelected=Группа не выбрана +combinedWindSourceTypeName=Комбинированная +courseBasedWindSourceTypeName=Проложенный курс +trackBasedEstimationWindSourceTypeName=Расчетный +expeditionWindSourceTypeName=Измеренный +webWindSourceTypeName=Вручную +raceCommitteeWindSourceTypeName=Гоночный комитет +clickChartToSetTime=Щелкните по графику для установки времени +position=Положение +windSourcesUsed=Использованные источники информации о ветре +errorTryingToUpdateWindSourcesToExclude=Ошибка при попытке обновления источников информации о ветре для исключения для гонки {0}: {1} +feedback=Комментарии +startStopPlaying=Начать/Остановить воспроизведение +backToLive=Вернуться к моменту времени в прямом эфире +slowPlaySpeedDown=Замедлить скорость воспроизведения или даже воспроизводить назад. +speedPlaySpeedUp=Увеличить скорость воспроизведения или снова воспроизводить вперед. +showHideComponent=Показать/ Скрыть {0} +degreesBoatToTheWind=Угол к ветру +racesInLeaderboard=Гонки в таблице лидеров +leaderboardsInGroup=Таблицы лидеров в группе +replication=Дублирование +errorFetchingReplicaData=Ошибка получения данных дублирования: {0} +averageCrossTrackErrorInMeters=\u2205 XTE +averageCrossTrackErrorInMetersTooltip=Среднее боковое уклонение от заданного курса по большой дуге окружности, соединяющей начало и конец этапа +raceAverageCrossTrackErrorInMetersTooltip=Среднее боковое уклонение от заданного курса по большой дуге окружности, соединяющей начало и конец этапа в течение этой гонки. +enterMaster=Имя узла ведущего экземпляра +errorStartingReplication=Ошибка начала повтора с узла {0} / обмена {1}: {2} +helpLines=Вспомогательные линии +startLine=Линия старта +finishLine=Линия финиша +advantageLine=Линия преимущества +courseMiddleLine=Средняя линия курса +messagingPortNumber=Номер порта обмена сообщениями ( по умолчанию 0) +servletPortNumber=Номер порта сервлета +registeredAt=Зарегистрирован в {0} +replicatingFromMaster=Дублирование с основного {0}, порт передачи обмена сообщениями {1}, порт сервлета {2} +showWindSpeedSeries=Показать данные о силе ветра +showWindDirectionSeries=Показать данные о направлении ветра +fleet=Флот +boatClass=Класс судна +setDelayToLive=Установить задержку от прямого эфира +pleaseEnterNonEmptyVenue=Введите, пожалуйста, место проведения соревнования. +pleaseEnterStartAndEndDate=Пожалуйста, введите дату начала и дату окончания. +startDateMustBeforeEndDate=Дата начала должна предшествовать дате окончания. +eventWithThisNameAlreadyExists=Событие с этим именем уже существует. +venue=Место проведения +pleaseSelectABoatClass=Выберите, пожалуйста, класс судна. +regattaWithThisNameAlreadyExists=Регата с этим именем уже существует. +seriesWithThisNameAlreadyExists=Серия с этим именем уже существует. +fleetWithThisNameAlreadyExists=Флот с этим именем уже существует. +fleets=Флоты +series=Серии +splitFleet=Разделить флот +color=Цвет +medalSeries=Медальные серии +noColor=Цвет не указан +pleaseSelectARegatta=Пожалуйста, выберите регату. +addFleet=Добавить флот +doYouReallyWantToRemoveRegatta=Вы действительно хотите удалить регату "{0}"? Это приведет к удалению без возможности восстановления всех таблиц лидеров для этой регаты! +errorLoadingScoreCorrectionProviders=Ошибка при загрузке провайдеров для корректировки очков: {0} +selectResultListToImportFrom=Выберите список результатов для импорта из +of=из +errorObtainingScoreCorrections=Ошибка получения корректировки очков от провайдера {0} и события {1} для класса судна {2} опубликованного {3}: {4} +assignRaceNumbersToRaceColumns=Назначение номеров гонок из официальных результатов столбам гонок таблицы лидеров +importOfficialResults=Импорт официальных результатов... +selectAll=Выбрать все +apply=Применить +successfullyUpdatedScores=Очки обновлены успешно +errorUpdatingScoresForLeaderboard=Ошибка обновления очков для таблицы лидеров {0}: {1} +maneuverTypesToShowWhenCompetitorIsClicked=Типы маневров для отображения при нажатии на участника +simulateWithStartTimeNow=Моделирования с "сейчас" в качестве времени начала +boatClassDoesNotMatchSelectedRegatta=Выбранные гонки содержат классы судов не соответствующие классу судов ''{0}'' выбранной регаты.\\ Вы действительно хотите использовать регату ''{1}''? +regattaExistForSelectedBoatClass=Есть только одна регата для выбранных классов судов.\\ Вы действительно хотите использовать выбор ''ни одной регаты''? +reload=Перезагрузить +addRegatta=Добавить регату... +exchangeName=Заменить имя +course=Курс +courseLayout=Проложенный курс +buoy=Буй +gate=Ворота +buoys=Буи +waypoint=Точка маршрута +showAllCompetitors=Показать всех участников +boatTails=Лодочные хвосты +resultImportUrls=URL-адреса импорта результатов +errorRefreshingResultImportUrlList=Ошибка обновления списка URL-адресов импорта результатов: {0} +successfullyUpdatedResultImportUrls=Список URL-адресов импорта результатов успешно обновлен +errorRemovingResultImportUrls=Ошибка удаления URL-адресов импорта результатов: {0} +pleaseEnterNonEmptyUrl=Пожалуйста, введите URL-адрес +addResultImportUrl=Добавить URL-адрес импорта результатов +errorAddingResultImportUrl=Ошибка добавления URL-адреса импорта результатов: {0} +errorTryingToObtainLeaderboardContents=Ошибка попытки получения содержимого таблицы лидеров: {0} +lastScoreUpdate=Обновление последних очков +warningBrowserUnsupported=Этот веб-сайт не оптимизирован для вашего браузера. Пожалуйста, выберите браузер новой версии, такой как Firefox или Chrome. +newMark=Новый знак +controlPoint=Знак/Ворота +errorUpdatingRaceCourse=Ошибка обновления курса гонки: {0} +successfullyUpdatedCourse=Курс гонки успешно обновлен +selectOneMarkOrTwoMarksForGate=Выберите одну отметку или две отметки для создания ворот +errorTryingToObtainTheMarksOfTheRace=Ошибка попытки получения отметок дистанции гонки: {0} +errorTryingToObtainRaceCourse=Ошибка попытки получения курса гонки: {0} +insertWaypointBeforeSelected=Вставьте ранее выбранную точку маршрута +insertWaypointAfterSelected=Вставьте точку маршрута, выбранную позднее +scoringSchemeLowPointSystem=Низкобальная (линейная) система подсчета очков +scoringSchemeHighPointSystem=Высокобальная система подсчета очков +scoringSchemeHighPointEssOverall=Высокобальная система подсчета, итоговые баллы экстремальной серии ESS +scoringSchemeHighPointLastBreaksTie=Высокобальная система подсчета, тай-брейк на последней гонке +scoringSchemeLowPointWinnerGetsZero=Низкобальная система подсчета очков, победитель получает ноль очков +scoringSchemeWinnerGetsFive=Высокобальная система подсчета, победитель получает 5 очков +scoringSchemeWinnerGetsSix=Высокобальная система подсчета, победитель получает 6 очков +scoringSystem=Система подсчета очков +createFlexibleLeaderboard=Создание адаптивной таблицы лидеров +createRegattaLeaderboard=Создание таблицы лидеров регаты +editFlexibleLeaderboard=Изменение адаптивной таблицы лидеров +editRegattaLeaderboard=Изменение таблицы лидеров регаты +defaultRegatta=Регата по умолчанию +useOverallLeaderboard=Использование общей таблицы лидеров +overallStandings=Общий зачет +type=Тип +actionAddRaces=Добавить гонки +racesWithNamePrefix=Гонки с именем-префиксом +maximumSpeedOverGroundInKnots=Максимальная скорость относительно земли +maximumSpeedOverGroundInKnotsTooltip=Самая большая скорость относительно земли участника, измеренная в ходе регаты. +totalTimeSailedDownwindInSeconds=Общее время под ветером +totalTimeSailedDownwindInSecondsTooltip=Общее время, которое в течение регаты участник шел под ветер. +totalTimeSailedUpwindInSeconds=Общее время против ветер +totalTimeSailedUpwindInSecondsTooltip=Общее время, которое в течение регаты участник шел против ветер. +totalTimeSailedReachingInSeconds=Общее время лавирования по ветру +totalTimeSailedReachingInSecondsTooltip=Общее время этапов лавирования по ветру участником в ходе регаты. +totalTimeSailedInSeconds=Общее время +totalTimeSailedInSecondsTooltip=Общее время, которое участник проплыл в регате. +totalDistanceTraveled=Общее расстояние +totalDistanceTraveledTooltip=Общее расстояние, пройденное во время регаты. +totalAverageSpeedOverGround=\u2205 Скорость относительно земли +totalAverageSpeedOverGroundTooltip=Средняя скорость относительно земли на протяжении регаты. +hhmmssUnit=чч:мм:сс +actionAddWindData=Добавить данные о ветре +valueMustBeBetweenMinMax=Значение ''{0}''должно быть в пределах от {1} до {2}. +optional=Дополнительно +pleaseEnterAValue=Введите, пожалуйста, значение. +latitude=Широта +longitude=Долгота +actionAddEvent=Добавить событие +publicationUrl=URL-адрес для публикации +suppress=скрыть +isPublic=является открытым +suppress=скрыть +unsuppress=Отобразить +suppressedCompetitors=Скрытые участники +mediaPanel=Аудио и Видео +url=URL +mimeType=MIME-тип +title=Заголовок +duration=Продолжительность (ч:мм:сс.мс) +addMediaTrack=Добавить аудио/видео +addYoutubeTrack=Добавить Youtube +youtubeId=Идентификатор Youtube +reallyRemoveMediaTrack=Вы действительно хотите удалить медиа трек''{0}''? +pleaseSelectASeries=Пожалуйста, выберите серию +pleaseSelectARace=Пожалуйста, выберите гонку +displayGroupsInReverseOrder=Отобразить группы в обратном порядке +actionConfigureUrl=Настроить URL-адрес +configureLeaderboardUrlDescription=Настроить таблицу лидеров и получить результирующий URL-адрес по ссылке +embedded=Внедренный +additionalUrlSettings=Дополнительные параметры URL-адреса +restrictToSingleSelectedRace=Ограничить одной выбранной гонкой +expandSelectedRace=Расширить выбранную гонку +showRaceDetails=Разрешить подробности для гонки +numberOfRaces=Количество гонок +numberOfRacesMustBeNonNegativeNumber=Пожалуйста, введите количество последних гонок, которые должны отображаться +chooseTheWayYouSelectRaces=Выберите способ выбора гонок +selectFromAllRaces=Выбрать все гонки +selectANumberOfRaces=Выбрать несколько гонок +numberOfLastNRaces=Количество самых последних гонок для отображения +expandLastRace=Всегда раскрывать последнюю отображенную гонку +factor=Фактор +errorUpdatingIsMedalRace=Ошибка обновления параметров медальной гонки: {0} +maneuverLoss=Потеря при выполнении маневра +averageManeuverLossInMeters=\u2205 Потеря при выполнение маневра +averageManeuverLossInMetersTooltip=Усредненная потеря расстояния при выполнении маневра. +averageTackLossInMeters=\u2205 Потеря при смене галса +averageTackLossInMetersTooltip=Усредненная потеря расстояния при смене галса. +averageJibeLossInMeters=\u2205 Потеря при перекладывании паруса +averageJibeLossInMetersTooltip=Усредненная потеря расстояния при перекладывании паруса. +shape=Форма +pattern=Модель +legend=Легенда +live=Прямой эфир +swissTimingArchiveConnector=события SwissTiming, помещенные в архив +errorFetchingChartData=Ошибка получения данных графика: {0} +loadingCompetitorData=Загрузка данных участника... +competitorRegattaDataAfterRaceN={0} после гонки {1} +rankChart=Рейтинг +totalPoints=Итоговое количество очков +raceTotalPointsTooltip=Итоговое количество очков участника в гонке +regattaTotalPointsTooltip=Итоговое количество очков участника в регате +showRankChart=Показать таблицу рейтинга +overallLeaderboardSelection=Итоговая таблица лидеров +errorTryingToObtainOverallLeaderboards=Ошибка получения итоговых таблиц лидеров: {0} +finished=Завершившиеся +passedTo=Переданные в {0} +gapChangeSinceLegStartInSeconds=Изменение отставания +gapChangeSinceLegStartInSecondsTooltip=Изменение отставания от лидера с начала последнего этапа.\nОтрицательное число означает, что отставание уменьшилось;\nположительное число означает, что отставание увеличилось. +sideToWhichMarkAtLegStartWasRounded=Отметка округленная до +sideToWhichMarkAtLegStartWasRoundedTooltip=Сторона в которую округляется отметка на старте этапа. +raceIsLive=Гонка {0} в прямом эфире +racesAreLive=Гонки {0} в прямом эфире +knotsUnit=Узлы +scoringSchemeHighPointFirstGetsOne=Самый лучший результат, победитель получает 1 очко +scoringSchemeHighPointFirstGetsTen=Самый лучший результат, победитель получает 10 очков +knotsUnit=Узлы +generatePolarSheet=Создать полярную электронную таблицу +polars=Полярная диаграмма +polarSheetChart=Полярная электронная таблица +polarSheetMinimumDataSizePerGraph=Минимальное количество данных на графике +polarSheetMinimumDataSizePerGraphTooltip=Все графики с меньшим количеством точек данных визуально не отображаются.\nТаким образом, не отображаются отвлекающие внимание диаграммы для \nскоростей ветра, в которых отсутствует достаточный объем данных. +polarSheetMinimumDataSizePerAngle=Минимальное количество точек данных в расчете на угол +polarSheetMinimumDataSizePerAngleTooltip=На каждом графике будут отображаться только данные об углах с указанным числом\nбазовых точек данных. Не имеющие важного значения\nчасти графика могут быть скрыты с помощью задания этой пороговой величины. +polarSheetMinimumWindConfidence=Минимальное значение достоверных данных о ветре +polarSheetMinimumWindConfidenceTooltip=Только фиксированные значения ветра с достоверностью, превышающей указанное пороговое значение\nприменяются. Эта достоверность от 0 до 1. +polarSheetMinimumConfidenceMeasure=Минимально допустимое измерение достоверности +polarSheetMinimumConfidenceMeasureTooltip=Минимальная степень достоверности для точки, указанной на графике.\nЕсли она ниже установленного порогового значения, то точка не отображается.\nЗначение лежит от 0 до 1 и детерминируется \nс использованием статистических мер. +polarSheetUseOnlyWindGaugeData=Для определения скорости ветра используйте только данные, полученные с помощью анемометра +polarSheetUseOnlyWindGaugeDataTooltip=Благодаря учету данных о скорости ветра, полученных с помощью анемометра\nмы исключаем примерные значения скорости ветра, введенные вручную, которые могли бы исказить данные, отображенные на полярной диаграмме. +polarSheetUseOnlyEstimationData=Используйте, определенные на глаз значения только для направления ветра +polarSheetUseOnlyEstimationDataTooltip=Использование точных расчетных значений о направлении ветра,\nпозволяет нам устранить ошибки полярных диаграмм, которые вращаются из-за неправильных данных, полученных с анемометра\n. +polarSheetRemoveOutliers=Удалите сильно отличающиеся значения (Подход основан на расстоянии) +polarSheetRemoveOutliersTooltip=Сильно отличающиеся значения удаляются с помощью применения подхода, основанного на расстоянии.\nПараметры могут быть отрегулированы ниже. +polarSheetOutlierDetectionRadius=Соседний радиус, определяющий сильно отличающееся значение +polarSheetOutlierDetectionRadiusTooltip=Радиус, используемый в алгоритме определения сильно отличающегося значения\nна основании расстояния. +polarSheetOutlierDetectionMinimumPerc=Минимальный процент соседних точек, определяющий сильно отличающееся значение +polarSheetOutlierDetectionMinimumPercTooltip=Процент соседних точек данных /всего точек данных\nдолжен быть ниже указанного значения для того, чтобы считаться сильно отличающимся значением. +polarSheetNumberOfHistogramColumns=Количество столбцов гистограммы +polarSheetWindSteppingInKnots=Шаг значения ветра в узлах +polarSheetWindSteppingMaxDistance=Максимальное расстояние до уровня ветра +polarSheetWindSteppingMaxDistanceTooltip=Устанавливает максимальный радиус, для которого уровни ветра будут фиксированными. +polarSheetSplitByWindGauges=Разделить источники данных о ветре +polarSheetSplitByWindGaugesTooltip=Разделение источников данных о ветре позволяет найти неисправные анемометры.\nСтатистическая правильность не гарантируется. Такое исправление может \nбыть учтено в полярной диаграмме результатов несколько раз (для разных анемомтеров). +pleaseSeeToolTips=Пожалуйста, наведите курсор на свойства, чтобы ознакомиться с всплывающими подсказками, при их наличии. +competitorColumnTooltip=Имена членов команды этого участника. +sailIdColumnTooltip=Отображает флаг страны и номер на парусе участника. +rankColumnTooltip=Итоговое место этого участника в регате. +carryColumnTooltip=Очки, перенесенные из предыдущих гонок. +totalsColumnTooltip=Итоговое количество очков участника в регате. +windData=Данные о ветре +gpsData=GPS-данные +status=Статус +noDataFound=Данные не обнаружены +displayName=Отобразить имя +histogram=Гистограмма +numberOfDataPoints=Количество точек данных +angleAndTotalNumberOfDataPointsAndCovAndCm=Угол: {0}; Общее количество точек данных: {1}; Коэффициент отклонения: {2}; Степень достоверности: {3} +buoyZone=Зона буя +radiusInMeters=Радиус (м) +filterRegattasByName=Сортировка регат по имени +noRegattasYet=Пока регат нет. +addCourseArea=Добавить зону курса +courseAreas=Зоны курса +courseArea=Зона курса +pleaseEnterNonEmptyCourseArea=Пожалуйста, выведите зону курса. +pleaseSelectACourseArea=Пожалуйста, выберите зону курса. +radiusInMeters=Радиус (м) +lastUpperFlag=Верхний флажок +lastLowerFlag=Нижний флажок +cannotAddRacesToRegattaLeaderboardButOnlyToRegatta=Это таблица лидеров регаты. Вы можете добавлять гонки в эту таблицу лидеров только непосредственно в определении регаты. +correctScore=Исправить очки +correctScoreFor=Исправить очки для участника {0} в гонке {1} +penaltyOrRedress=Штраф/Корректировка +netScore=Чистые очки +timePointMustBeAfterStartOfTracking=Момент времени должен быть после начала отслеживания. +timePointMustBeBeforeEndOfTracking=Момент времени должен быть до окончания отслеживания. +regattaUsedForTheTrackedRace=Регата, используемая для отслеживаемой гонки +tracTracUpdateUrl=Обновление URl-адреса сервера ТракТрак +passingInstructions=Инструкции по передаче +refreshNow=Обновить сейчас +stopUpdating=Остановить обновление +startUpdating=Начать обновление +currentTime=Текущее время: +tractracUsername=Имя пользователя ТракТрак +tractracPassword=Пароль ТракТрак +operatorEquals=Равно +operatorNotEqualTo=Не равно +operatorLessThan=Меньше +operatorLessThanEquals=Меньше или равно +operatorGreaterThan=Больше чем +operatorGreaterThanEquals=Больше или равно +operatorContains=Содержит +operatorNotContains=Не содержит +operatorStartsWith=Начинается с +operatorEndsWith=Заканчивается на +nationality=Гражданство +numberMustBePositive=Цифра должна быть больше нуля. +distanceToLineAtRaceStart=Расстояние до линии «Старт» +distanceToLineFiveSecondsBeforeStart=Расстояние до линии 5с до старта +speedOverGroundAtRaceStart=Скорость относительно земли на линии «Старт» +speedOverGroundFiveSecondsBeforeStart=Скорость относительно земли на линии 5с до старта +speedOverGroundWhenPassingStart=Скорость относительно земли во время старта +distanceToLineAtRaceStartTooltip=Расстояние до стартовой линии во время начала гонки +distanceToLineFiveSecondsBeforeStartTooltip=Расстояние до стартовой линии за пять секунд до начала гонки +speedOverGroundAtRaceStartTooltip=Скорость относительно земли во время начала гонки +speedOverGroundFiveSecondsBeforeStartTooltip=Скорость относительно земли за пять секунд до начала гонки +speedOverGroundWhenPassingStartTooltip=Скорость относительно земли при пересечении участником линии старта +startTack=Сменить галс +startTackTooltip=галс, на котором участник начал +portTack=Левый галс +starboardTack=Правый галс +portSide=Левый борт +starboardSide=Правый борт +pleaseEnterANumber=Пожалуйста, введите номер. +competitorsFilter=Сортировать участников +flagStatus=Флажок состояния +additionalInformation=Информация +windFixListingDescription=Исправления для ветра (первые 3, последние 3, фактические значения) +noWindFixesAvailable=Исправления для ветра недоступны. +distanceToStarboardEndOfStartlineWhenPassingStart=Расстояние от линии до правого борта во время прохождения старта +distanceToStarboardEndOfStartlineWhenPassingStartTooltip=Расстояние до правого борта от стартовой линии или одной отметки начала, когда участник пересек линию старта +actionAddFilter=Добавить фильтр +actionEditFilter=Изменить фильтр +availableFilters=Доступные фильтры +createFilterHint=Создать набор фильтров для сортировки списка участников. +filterThisNameAlreadyExists=Фильтр с таким именем уже существует. +addAtLeastOneFilterCriteria=Добавьте хотя бы один критерий сортировки. +filterCriteria=Критерий сортировки +selectAFilterCriteria=Выбрать критерий сортировки +filterName=Имя фильтра +operator=Оператор +value=Значение +nationalityMustBeISOorIOCcode=Гражданство должно содержать двухбуквенный код МОК или трехбуквенный код МОК. +raceRank=Рейтинг гонки +waypoints=Точки маршрута +disableRaceFilter=Отключить фильтр +enableRaceFilter=Включить фильтр +raceStatusColumn=Статус гонки +loading=Загрузка (при медленном соединении с Интернетом процесс может занять несколько минут)... +racesWithHiddenState=Гонки со статусом «СКРЫТЫ» +mediaDateFormatError=Недействительный формат даты. Использовать: {0} +selectedCompetitors=Выбранные участники +filterNothing=Нечего сортировать +topNCompetitorsByRaceRank=Лидеры гонки {0} +topNCompetitorsByTotalRank=Лидеры в общем зачете {0} +simulator=Модель +optionsBar=Панель опций +simulateButton=Прогнозировать +setup=Установка +strength=Сила (в узлах) +oscillation=Колебание +oscillating=Колеблющийся +amplitude=Амплитуда(в градусах) +frequency=Частота (в 1/ч) +noise=Шум +sailing=Плавание под парусом +optimality=Оптимальность +maximumTurns=Максимальное количество галсов +raceDirection=Направление гонки +upWind=Против ветра +downWind=По ветру +strategies=Стратегии +omniscient=Всезнающий +opportunistic=Оппортунист +mixed=Смешанный +maps=Карты +startEnd=Старт-Финиш +racecourse=Курс +summary=Сводная информация +replay=Повтор +display=Отобразить +simulatorPolarHeader = Полярная диаграмма +show=Показать +hide=скрыть +leftWind=Ветер слева +rightWind=Ветер справа +strategyOption1=стратегияВариант1 +strategyOption2=стратегияВариант2 +strategyOption3=стратегияВариант3 +legLabel=Этап +raceLabel=Гонка +competitorLabel=Участник +freemode=Сохранять направления +whatIfCourse=Что если курс +editRegatta=Изменить регату... +selectSailingEvent=Пожалуйста, выберите событие парусного спорта... +selectCourseArea=Пожалуйста, выберите зону курса.. +courseAreaOverview=Обзор зоны +courseDesignOverview=Обзор создания курса +regattaOverview=Обзор регаты +additionalSettings=Дополнительные параметры +showFollowingCourseAreas=Показать следующие зоны курса +showFollowingRegattas=Показать следующие регаты +showOnlyRacesOfSameDay=Показать только гонки, проходившие в один и тот же день +showOnlyCurrentlyRunningRaces=Показать только проходящие гонки +seriesDefinesResultDiscardingRule=Серии определяют правила исключения результатов +addSeries=Все серии +regattaDefinesResultDiscardingRules=Регата определяет правила исключения результатов +startsWithZeroScore=Серия начинается с нуля очков +masterDataImportPanel=Импорт основных данных +importSuccess=Успех\nСозданная {0} группа таблиц (ы) лидеров и {1} таблиц (ы) и {2} события (й) и {3} регат (ы). +importSuccessOverwriteInfo=Пришлось перезаписать следующие регаты, необходимо повторно проложить их гонки: +regattaOverviewConfiguration=Обзор конфигурации регаты +firstRaceIsNonDiscardableCarryForward=Начиная с не исключаемых переносов в другую графу +addReplicationMaster=Подключение к главному серверу дублирования +connect=Подключиться +connectToMaster=Подключиться к Главному +exchangeHost=Имя узла обмена сообщениями +explainExchangeHostName=Имя узла, где сообщения ставятся в очередь ожидания. +explainReplicationHostname=Имя узла, где работает главный сервер дублирования. +explainReplicationExchangeName=Имя очереди ожидания обмена сообщениями. Она должна быть настроена как главный сервер. +explainReplicationExchangePort=Порт очереди ожидания обмена сообщениями. Он используется после первоначальной загрузки для всех данных. +explainReplicationServletPort=Номер порта сервлета, используемого для подключения для регистрации этой копии и получения первоначальной загрузки. +explainReplicasRegistered=Копии зарегистрированы (этот сервер главный ): +explainConnectionsToMaster=Подключения к главному серверу (это сервер копий): +explainNoConnectionsToMaster=Не настроенных подключений к главному серверу.Для настройки одного соединения щелкните «Подключиться к главному серверу» . +explainNoConnectionsFromReplicas=Подключений к серверу копий не обнаружено. Копии можно подключить используя их Консоль администрирования. +stopConnectionToMaster=Остановить копирование +asLink=Показать в ссылке +noStarttimeAnnouncedYet=Ожидание +raceIsRunning=Проходящая +raceIsFinishing=Завершающаяся +raceIsFinished=Завершенная +raceIsScheduled=Запланированная +raceIsInStartphase=Этап старта +generalRecall=Общая отмена +startPostponed=Отложенная +raceAbandoned=Прекращенная +stopAllReplicas=Разъединить соединение со всеми копиями (Опасно!) +warningServerIsReplica=ВНИМАНИЕ: Этот сервер настроек может быть сервером копий. НЕ ВНОСИТЕ никаких изменений или это приведет к нежелательным последствиям! +dropReplicaConnection=Разъединить соединение +timePanelName=Контроль времени +competitorFilterExplanation=Фильтр отображает всех участников, соответствующих критериям: +dropReplicaConnection=Разъединить соединения +protestTimeFinishesAt=Срок подачи протеста истекает в +protestTimeFinishedAt=Срок подачи протеста истек в +flags=Флаги +lastUpdate=Последние обновления +noMoreRacingToday=Сегодня больше нет гонок +furtherSignalsAshore=Дальнейшие сигналы на берегу +earlyStarters=Совершивших фальшстар +showingDetailsOfRace=Отобразить подробности для гонки {0} +startAt=Старт в +finishAt=Финиш в +protestEndsAt=Срок подачи протеста завершается в +unknown=Неизвестно +earlyStarters=Совершивших фальшстарт +raceLog=Запись о гонке +refreshRaceLog=Обновить запись о гонке +raceLogReloaded=Запись о гонке успешно повторно загружена из базы данных +reloadAllRaceLogs=Повторно загрузить все записи о гонке +export=Эскпорт +sideline=Боковая линия +classAndGolfUp=Флаг поднят – восемь минут до старта – процедура старта – через стартовые ворота +classDisplayed=Флаг поднят – пять минут до старта +papaDisplayed=Флаг режима старта четыре минуты до старта +blackDisplayed=Флаг режима старта – дисквалификация совершивших фальшстарт – четыре минуты до старта +zuluIndiaDisplayed=Флаг режима старта – штрафы для совершивших фальшстарт – четыре минуты до старта +papaRemoved=Флаг режима старта - одна минута до старта +blackRemoved=Флаг режима старта – дисквалификация для совершивших фальшстарт - одна минута до старта +zuluIndiaRemoved=Флаг режима старта – старт со штрафами совершившим фальшстарт – одна минута до старта +classFlagRemoved=Флаг убран – сигнал начала гонки +xray=Индивидуальная отмена – есть совершившие фальшстарт +firstSubstitute=Общая отмена – слишком много совершивших фальшстарт – процедура старта будет повторена +blueFlagDisplayed=Флаг отображается – первый участник пересек линию финиша +blueFlagRemoved=Флаг убран – последний участник пересек линию финиша или предельное время финиша прошло +golfRemoved=Флаг убран –ворота закрыты +answeringPennantOverAlphaDisplayed=Флаг отображается - старт отложен - сегодня больше нет гонок +answeringPennantOverHotelDisplayed=Флаг отображается – старт отложен - дальнейшие сигналы на берегу +answeringPennantDisplayed=Флаг отображается – старт отложен +answeringPennantRemoved=Флаг убран – процедура старта начинается через одну минуту +novemberOverAlphaDisplayed=Флаг поднят - гонка отменена – сегодня гонок больше нет +novemberOverHotelDisplayed=Флаг поднят – гонка отменена - дальнейшие сигналы на берегу +novemberDisplayed=Флаг поднят – гонка отменена +bravoDisplayed=Срок подачи протеста начался +bravoRemoved=Срок подачи протеста закончился +essThreeDisplayed=Три минуты до старта +essTwoDisplayed=Две минуты до старта +essOneDisplayed=Одна минуты до старта +essOneRemoved=Сигнал начала гонки +dataBeforeAfter=Данные до/после гонки +rawFixes=Все значения (включая сильно отличающиеся значения) +importRemoteHost=Удаленный узел: +importFetchRemoteLgs=Получить список группы таблицы лидеров +importUrlInvalid=Недействительный URL-адрес для получения групп таблицы лидеров: {0} +importGetLeaderboardsFailed=Запрос ПОЛУЧИТЬ группы таблиц лидеров выполнить не удалось для узла {0} с ошибкой {1} +importNoDataReturned=Данные сервером не возвращены +importServerError=Запрос ПОЛУЧИТЬ группы таблиц лидеров выполнить не удалось с ошибкой Сервера. Дополнительные сведения, см. в журнале сервера. +importLeaderboardGroups=Группы таблиц лидеров: +importOverrideSwitchLabel=Перезаписать существующие данные, если имена и идентификационные данные совпадают +importSelectedLeaderboardGroups=Импорт выбранных групп таблиц лидеров +importSelectAtLeastOne=Нужно выбрать хотя бы одну группу таблиц лидеров для импорта +deselectAll=Отменить выбор всех +polarDiagramButton=Полярная диаграмма +ratioBetweenTimeSinceLastPositionFixAndAverageSamplingInterval=Запаздывание GPS +ratioBetweenTimeSinceLastPositionFixAndAverageSamplingIntervalTooltip=Соотношение между последними полученными с помощью GPS данными о положении и усредненным интервалом выборки +trackingQuality=Качество отслеживания +legType=Тип этапа +sailID=Номер на парусе +seriesLeaderboard=Серия таблиц лидеров +regattaLeaderboards=Таблица лидеров регаты +clearSelection=Отменить выбор +running=Проходящая в настоящее время +runAsSubstantive=Начать +done=Готово +lastFinished=Последняя завершенная +run=Начать +times=раз +numberOfGPSFixes=Количество точек фиксации GPS +averageCleanedServerTime=\u2205 Чистое время на сервере +averageCleanedOverallTime=\u2205 Чистое итоговое время +dataMiningBenchmarkResults=Результаты сравнительного теста сбора данных +serverTime=Время на сервере +confidenceShouldBeBetween=Достоверность данных должна быть от 0 до 1. +minDataValuesNeedToBeAtLeastZero=Минимальные значения объема данных не могут быть отрицательными. +numberOfColumnsAtLeast2=Количество столбцов гистограммы должно быть больше одного. +outlierRadiusNeedsToBePositiveString=Сильно отличающееся значение радиуса должно быть положительным числом. +outlierMinimumNeighborhoodPctString=Сильно отличающееся значение в процентах соседней точки должно быть положительным и максимально равно 1. +oneOrMoreParametersAreEmpty=Поле параметра не может быть пустым. +errorWhileAddingSeriesToChart=При добавлении \nновой серии в график возникла ошибка +generatingPolarSheet=Создание полярной таблицы... +dataCount=Объем данных +generationFinished=Создание завершено! +generating=Создание +removeAllSheets=Удалить все листы +removeAll=Удалить все +selectSheet=Выбрать лист +cleanedServerTime=Чистое время на сервере +overallTime=Общее время +cleanedOverallTime=Чистое итоговое время +dataMiningResult=Результат сбора данных +groupBy=Группировать по +statisticToCalculate=Рассчитать статистику +queryResultsChartSubtitle=Обработанные {1}/{0} элементы данных через {2} секунд +noQuerySelected=Запросы не выбраны +runAutomatically=Запустить автоматически +windImport_Upload=Загрузить +windImport_Title=Импортировать данные о ветре от экспедиции +windImport_BoatId=Идентификационный номер судна: +windImport_SelectedRacesWarning=Вы действительно хотите импортировать данные о ветре в выбранные {0} гонки?\nЭто в любом случае повлияет только на гонки с применимым временным окном. +windImport_AllRacesWarning=Вы действительно хотите импортировать данные о ветре во все гонки?\nЭто в любом случае повлияет только на гонки с применимым временным окном. +windImport_ResultHeader=Импортированные данные с: {0} по: {1}. {2} повлияли на гонки. +windImport_ResultEntry=Гонка: {0} ({1}) - {2} корректировка с: {3} по: {4} +windImport_ResultError=Ошибка: {0} +showWindSpeedSeriesTooltip=Переключает видимость скорости ветра для всех источников данных +showWindDirectionSeriesTooltip=Переключает видимость направления ветра для всех источников данных +combinedWindSourceTypeTooltip=Объединяет данные из всех других источников данных о ветре в один набор данных +courseBasedWindSourceTypeTooltip=Значение основано на прокладке курса гонки (с учетом предположения, что гонка начнется с этапа против ветра) +trackBasedEstimationWindSourceTypeTooltip=Расчетные данные из маршрута участника +expeditionWindSourceTypeTooltip=Данные, полученные из мобильных систем измерения +webWindSourceTypeTooltip=Значения данных, установленные вручную +raceCommitteeWindSourceTypeTooltip=Значения данных, установленные вручную, полученные от официальных лиц гонки +sheetName=Имя листа +sheetNameTooltip=Вы можете присвоить имя своему полярному листу используя текстовое поле.\nИмя будет создано с использованием класса судна\nесли имя не предоставлено. +timeDownwind=Время по ветру +timeReaching=Время лавирования по ветру +timeUpwind=Время против ветра +raceTimeTooltip=Общее время затраченное в этой гонке +raceTimeDownwindTooltip=Общее время движения по ветру в этой гонке +raceTimeReachingTooltip=Общее время лавирования по ветру в этой гонке +raceTimeUpwindTooltip=Общее время движения против ветра в этой гонке +noStatisticSelectedError=Статистических данных для расчета не выбрано +noCustomGrouperScriptTextError=Скрипт блока группирования пустой +noDimensionToGroupBySelectedError=Размер для группировки по не выбран +noGrouperSelectedError=Тип блока группирования не выбран +queryNotValidBecause=Запрос не возможен, т.к. +dataMining=Сбор данных +errorRunningDataMiningQuery=Ошибка при выполнении запроса +hideToolbar=Скрыть панель инструментов +showSeriesLeaderboards=Показать таблицу лидеров серии +showOverallLeaderboard=Показать общую таблицу лидеров +lineAngleToWindAndAdvantage=длина: {0}м; угол к ветру: {1}градус; {2} борт {3}м имеет преимущество +doYouReallyWantToRemoveEvents=Вы действительно хотите удалить все события? +doYouReallyWantToRemoveEvent=Вы действительно хотите удалить событие {0}? +line=Строка +offset=Смещение +doYouReallyWantToRemoveCompetitors=Вы действительно хотите удалить всех выбранных участников? +doYouReallyWantToRemoveCompetitor=Вы действительно хотите удалить выбранного участника {0}? +filterCompetitors=Сортировать по имени, классу судна или номеру на парусе +editCompetitor=Изменить участника +pleaseEnterASailNumber=Пожалуйста, введите номер на парусе +successfullyAllowedCompetitorReset=Перезагрузка участника (ов) {0} разрешена. +id=Идентификатор +allowReload=Разрешить перезагрузку +compress=Сжать +compressTooltip=Используйте только, если на экземпляре сервера, с которого выполняется экспорт, хотя бы\nзапущена операция подтверждения 0fbf6071dea125bec4a56dee55d61c99def4a62e. +queryRunner=Отправка запросов +rerunQueryAfterRefresh=Повторно выполнить запрос после обновления +refreshIntervalMustntBeEmpty=Интервал обновления не может быть пустым +selectionTables=Выбор таблиц +addConfiguration=Добавить конфигурацию +noConfigurations=Конфигурации отсутствуют. +errorRetrievingConfiguration=Ошибка получения данных о конфигурации с сервера. +allowedCourseAreas=Разрешить зоны курса +minimumRoundsForCourse=Минимальное количество кругов для курса гонки +maximumRoundsForCourse=Максимальное количество кругов для курса гонки +resultsMailRecipient=Результаты получателя почты +deviceConfiguration=Конфигурация устройства +none=Ни одного +courseDesignerMode=Создатель курсов +racingProcedure=Процедура гонки +courseNames=Имена курсов гонок +generate=Создать +dontoverwrite=Не перезаписывать +setRacingProcedureConfiguration=Настроить конфигурацию регаты +racingProcedureConfiguration=Конфигурация регаты +activateIndividualRecall=Активировать индивидуальную отмену +rrs26Start=RRS26 старт +setConfiguration=Активировать конфигурацию +classFlag=Флаг класса +classFlagHelpText=Устанавливает флаг класса, используемый для {0}. Выберите ''Не перезаписывать''для активации флага по умолчанию для процедуры. +individualRecallHelpText=После активации можно обратиться к официальному лицу гонки для индивидуальной отмены. +startmodeFlags=Флаги режима старта +startmodeFlagsHelpText=Устанавливает доступные флаги для режима старта. Выберите «ни один» из предложенных, для включения флагов по умолчанию для этой процедуры. +gateStart=Ворота старта +activatePathfinder=Активировать «Следопыт» +pathfinderHelpText=После активации официальное лицо гонки может настроить судно «Следопыт». +essStart=Старт ESS +basicStart=Основной старт +configured=Настроенный +overwriteRacingProceduresConfiguration=Активировать конфигурацию регаты +overwriteRacingProceduresConfigurationHelpText=Включение этой опции предоставляет базовую конфигурацию регаты для всех опций, не настроенных в специальной конфигурации регаты. +setStartTime=Установить время старта +currentPass=Текущий пропуск +setStartTimeDescription=Выбрать новое время старта для гонки +hasAdditionalGolfDownTime=Активировать дополнительное время отсчета +additionalGolfDownTimeHelpText=Активировать дополнительное время отсчета +invalidColor=Недействительный цвет: {0} +igtimiAccounts=Учетные записи Igtimi +errorFetchingIgtimiAccounts=Ошибка получения всех Igtimi учетных записей: {0} +addIgtimiAccount=Добавить учетную запись Igtimi +errorAuthorizingAccessToIgtimiUser=Ошибка авторизации доступа к Igtimi пользователю {0}: {1} +successfullyAuthorizedAccessToIgtimiUser=Успешная авторизация доступа к Igtimi пользователю {0} +couldNotAuthorizedAccessToIgtimiUser=Невозможно авторизовать пользователя {0}; недействительное имя пользователя или пароль +errorGettingIgtimiAuthorizationUrl=Ошибка получения URL-адреса для авторизации Igtimi: {0} +addIgtimiUser=Добавить пользователя Igtimi +doYouReallyWantToRemoveLeaderboards=Вы действительно хотите удалить таблицы лидеров? +errorTryingToRemoveIgtimiAccount=Ошибка попытки удаления учетной записи Igtimi для пользователя {0} +successfullyRemoveIgtimiAccount=Учетная запись Igtimi для пользователя {0} удалена успешно +eMailMustNotBeEmpty=Адрес электронной почты не должен быть пустым +emailAddress=адрес электронной почты +password=Пароль +doYouReallyWantToRemoveLeaderboard=Вы действительно хотите удалить таблицу лидеров: ''{0}''? +metaLeaderboardCannotBeChanged=Это мета таблица лидеров. Ее нельзя изменить здесь. +failedToSetNewStartTime=Ошибка при установке нового времени старта. +doYouReallyWantToRemoveIgtimiAccount=Вы действительно хотите удалить учетную запись Igtimi, предоставляющую доступ к данным пользователя {0}? +igtimiWindImport=Импорт данных о ветре из Igtimi +seeIgtimiTabForAccountSettings=См. вкладку ''Igtimi'' для параметров учетной записи, определяющих какие данные из Igtimi будут учитываться. +importWindFromIgtimi=Импорт данных о ветре из Igtimi +errorImportingIgtimiWind=Ошибка импорта данных о ветре из Igtimi: {0} +resultFromIgtimiWindImport=Следующее количество фиксированных точек было импортировано для перечисленных гонок: {0} +timeToStart={0} до старта +timeSinceStart={0} со старта +distanceToLine=Растояние до линии старта +beatAngleTooltip=Угол между направлением движения участника и ветром +beatAngle=Угол лавирования +runAutomaticallyTooltip=Запустить запрос автоматически, например, после изменения статистических данных или группировки. +rerunQueryAfterRefreshTooltip=Повторно запускает запрос после обновления таблиц. +queryDefinitionProvider=Поставщик определения запроса +statisticProvider=Поставщик статистических данных +toCalculateThe=для расчета +use=Использовать +groupingProvider=Поставщик группировки +releaseNotes=Новости и история выпусков +hasSplitFleetContiguousScoring=Разделить флоты, имеющие близкие количества очков +overallProgress=Итоговое улучшение +eventOverview=Обзор события +start=Старт +showAddedScores=Показать добавленные очки для каждой записи таблицы лидеров +ratio=коэффициент +importWind=Имопрт данных о ветре +importWindTooltip=Снятие флажка с этой опции значительно снизит объем передаваемых данных. +authorName=Имя автора +authorPriority=Приоритет автора +removeDenotation=Remove Denotation +competitorRegistrationsMissingProceed=At least one of the selected races ({0}) is missing competitor registrations. Proceed anyway? +pleaseSelectAControlPoint=Please select a control point +twoMarkControlPoint=2-mark-controlpoint +registeredCompetitors=Registered Competitors +competitorPool=Competitor Pool +selectAtLeastOne=Select at least one +selectRaces=Select the Races +importFixes=Import Fixes +filterEventsByName=Filter Events by name: +leaderboardsExceptFromSelectedGroup=Leaderboards (except those of selected group) +availableLeaderboardGroups=Available Leaderboard Groups +leaderboardGroupsOfSelectedEvent=Leaderboard Groups Assigned to Event +addRaces=Add races +withNamePrefix=with name prefix +pleaseSelectASeriesFirst=Please select a series first. +imageURLs=Image URLs +videoURLs=Video URLs \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TextfieldEntryDialogWithCheckbox.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TextfieldEntryDialogWithCheckbox.java index 03b0b820700..cab62ac29ec 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TextfieldEntryDialogWithCheckbox.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TextfieldEntryDialogWithCheckbox.java @@ -4,15 +4,15 @@ import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.dialog.DataEntryDialog; -public class TextfieldEntryDialogWithCheckbox extends DataEntryDialog> { +public class TextfieldEntryDialogWithCheckbox extends DataEntryDialog> { private final CheckBox checkbox; private final TextBox entryField; public TextfieldEntryDialogWithCheckbox(String title, String message, String okButtonName, String cancelButtonName, - String checkboxLabel, String initialValue, Validator> validator, DialogCallback> callback) { + String checkboxLabel, String initialValue, Validator> validator, DialogCallback> callback) { super(title, message, okButtonName, cancelButtonName, validator, callback); checkbox = createCheckbox(checkboxLabel); entryField = createTextBox(initialValue); @@ -31,8 +31,8 @@ public class TextfieldEntryDialogWithCheckbox extends DataEntryDialog getResult() { - return new Pair(entryField.getValue(), checkbox.getValue()); + protected Util.Pair getResult() { + return new Util.Pair(entryField.getValue(), checkbox.getValue()); } @Override 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 8f3248af141..d32aa98164d 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 @@ -158,15 +158,12 @@ public class TimePanel extends SimplePanel implemen @Override public void onClick(ClickEvent event) { switch(TimePanel.this.timer.getPlayState()) { - case Stopped: + case Paused: TimePanel.this.timer.play(); break; case Playing: TimePanel.this.timer.pause(); break; - case Paused: - TimePanel.this.timer.play(); - break; } } }); @@ -313,7 +310,7 @@ public class TimePanel extends SimplePanel implemen setMinMax(getFromTime(), newMaxTime, /* fireEvent */ false); // no event because we guarantee that time is between min/max break; case Replay: - timer.stop(); + timer.pause(); break; } } @@ -420,7 +417,6 @@ public class TimePanel extends SimplePanel implemen } break; case Paused: - case Stopped: updatePlayPauseButtonsVisibility(playMode); playPauseButton.getElement().removeClassName("playPauseButtonPause"); playModeImage.setResource(playModeInactiveImg); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSelector.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSelector.java index 8ffbe037c96..869f12db900 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSelector.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSelector.java @@ -215,8 +215,6 @@ public class MediaSelector implements PlayStateListener, TimeListener, startPlaying(); break; case Paused: - // fall through to Stopped - case Stopped: pausePlaying(); default: break; @@ -463,8 +461,6 @@ public class MediaSelector implements PlayStateListener, TimeListener, } break; case Paused: - // fall through to Stopped - case Stopped: if (!mediaPlayer.isMediaPaused()) { mediaPlayer.pauseMedia(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/TimeFormatUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/TimeFormatUtil.java index 739d4345bcb..c3fd077022e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/TimeFormatUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/TimeFormatUtil.java @@ -7,6 +7,10 @@ import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; public class TimeFormatUtil { + static final int MILLISECONDS_PER_MINUTE = 60 * 1000; + + static final int MILLISECONDS_PER_HOUR = 60 * MILLISECONDS_PER_MINUTE; + public static final DateTimeFormat DATETIME_FORMAT = DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_MEDIUM); private static NumberFormat zeroPaddingNumberFormat_Min = NumberFormatterFactory.getDecimalFormat(2, 0); @@ -21,12 +25,12 @@ public class TimeFormatUtil { String hoursText = segements[0].trim(); int hours = Integer.valueOf(hoursText); isNegative = Integer.signum(hours) < 0; - milliseconds = Math.abs(hours) * 60 * 60 * 1000; + milliseconds = Math.abs(hours) * MILLISECONDS_PER_HOUR; case 2: String minutesText = segements[segements.length - 2].trim(); int minutes = Integer.valueOf(minutesText); isNegative = isNegative || Integer.signum(minutes) < 0; - milliseconds = milliseconds + Math.abs(minutes) * 60 * 1000; + milliseconds = milliseconds + Math.abs(minutes) * MILLISECONDS_PER_MINUTE; case 1: String secondsText = segements[segements.length - 1].trim(); float seconds = Float.valueOf(secondsText); @@ -46,19 +50,19 @@ public class TimeFormatUtil { StringBuilder result = new StringBuilder(); - long hours = (milliseconds / (60 * 60 * 1000)); + long hours = (milliseconds / MILLISECONDS_PER_HOUR); if (hours > 0) { result.append(String.valueOf(signum * hours) + ':'); signum = 1; } milliseconds = Math.abs(milliseconds); - long rest = (milliseconds % (60 * 60 * 1000)); - long minutes = (rest / (60 * 1000)); - if (minutes > 0) { + long rest = (milliseconds % MILLISECONDS_PER_HOUR); + long minutes = (rest / MILLISECONDS_PER_MINUTE); + if (minutes > 0 || result.length() > 0) { result.append(zeroPaddingNumberFormat_Min.format(signum * minutes) + ':'); signum = 1; } - rest = (rest % (60 * 1000)); + rest = (rest % MILLISECONDS_PER_MINUTE); double seconds = rest / 1000d; result.append(zeroPaddingNumberFormat_Sec.format(signum * seconds)); return result.toString(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/search/SearchEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/search/SearchEntryPoint.java new file mode 100755 index 00000000000..8b58edc27b7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/search/SearchEntryPoint.java @@ -0,0 +1,91 @@ +package com.sap.sailing.gwt.ui.client.search; + +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.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.TextArea; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; +import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; +import com.sap.sailing.gwt.ui.client.SailingService; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; +import com.sap.sse.common.search.KeywordQuery; +import com.sap.sse.gwt.client.EntryPointHelper; + +/** + * Sample entry point demonstrating the search capabilites of + * {@link SailingServiceAsync#search(String, com.sap.sse.common.search.KeywordQuery, AsyncCallback)} and + * {@link SailingServiceAsync#getSearchServerNames(AsyncCallback)}. + * + * @author Axel Uhl (D043530) + * + */ +public class SearchEntryPoint extends AbstractEntryPoint { + private final SailingServiceAsync sailingService = GWT.create(SailingService.class); + + @Override + protected void doOnModuleLoad() { + super.doOnModuleLoad(); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + VerticalPanel searchAndResults = new VerticalPanel(); + searchAndResults.setSize("100%", "30em"); + RootPanel.get().add(searchAndResults); + HorizontalPanel searchPanel = new HorizontalPanel(); + searchAndResults.add(searchPanel); + searchPanel.add(new Label("Search")); + final TextBox searchBox = new TextBox(); + searchPanel.add(searchBox); + Button searchButton = new Button("Search"); + searchPanel.add(searchButton); + final TextArea resultsLabel = new TextArea(); + resultsLabel.setSize("90%", "70%"); + resultsLabel.setEnabled(false); + searchAndResults.add(resultsLabel); + searchButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + final KeywordQuery query = new KeywordQuery(searchBox.getText().split(" ")); + resultsLabel.setText(""); + sailingService.getSearchServerNames(new AsyncCallback>() { + @Override + public void onSuccess(Iterable serverNames) { + search(resultsLabel, query, /* null meaning the "local" server */ null); + for (String serverName : serverNames) { + search(resultsLabel, query, serverName); + } + } + + @Override + public void onFailure(Throwable caught) { + Window.alert("Error: "+caught.getMessage()); + } + }); + } + }); + } + + private void search(final TextArea resultsLabel, final KeywordQuery query, final String serverName) { + sailingService.search(serverName, query, new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + Window.alert("Error: "+caught.getMessage()); + } + + @Override + public void onSuccess(Iterable result) { + for (LeaderboardSearchResultDTO r : result) { + resultsLabel.setText(resultsLabel.getText()+"\n"+r); + } + } + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractChartSettingsComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractChartSettingsComponent.java index e1e7eca5cd9..86e38762ff6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractChartSettingsComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractChartSettingsComponent.java @@ -5,8 +5,8 @@ import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java index 0123c90f005..eaea356dada 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java @@ -32,8 +32,6 @@ import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.actions.GetLeaderboardDataEntriesAction; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; @@ -43,6 +41,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.AbstractLazyComponent; import com.sap.sailing.gwt.ui.client.shared.components.Component; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.Timer; @@ -159,6 +158,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A break; } chart.setTitle(new ChartTitle().setText(DetailTypeFormatter.format(selectedDetailType)), null); + chart.ensureDebugId("CompetitorChart"); final String unit = DetailTypeFormatter.getUnit(getSelectedDetailType()); final String label = unit.isEmpty() ? "" : "[" + unit + "]"; chart.getYAxis().setAxisTitleText(label); @@ -223,9 +223,9 @@ public abstract class AbstractCompetitorLeaderboardChart extends A sailingService, leaderboardName, /* date */ null, selectedDetailType); asyncActionsExecutor.execute(getLeaderboardDataEntriesAction, LODA_LEADERBOARD_CHART_DATA_CATEGORY, - new AsyncCallback, List>>>() { + new AsyncCallback, List>>>() { @Override - public void onSuccess(List, List>> result) { + public void onSuccess(List, List>> result) { List chartSeries = new ArrayList(Arrays.asList(chart.getSeries())); chart.hideLoading(); setWidget(chart); @@ -259,7 +259,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A }); } - private void fillTotalRanksSeries(List, List>> result, List chartSeries) { + private void fillTotalRanksSeries(List, List>> result, List chartSeries) { Set unusedSeries = new HashSet(competitorSeries.values()); for (Series series : competitorSeries.values()) { for (Point p : new ArrayList(Arrays.asList(series.getPoints()))) { @@ -268,7 +268,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A } int raceColumnNumber = 0; int maxCompetitorCount = 0; - for (Triple, List> entry : result) { + for (com.sap.sse.common.Util.Triple, List> entry : result) { List dataValues = entry.getC(); raceColumnNames.add(entry.getA()); if(hasValidValues(dataValues)) { @@ -298,7 +298,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A chart.setHeight(maxCompetitorCount * 30 + 100 + "px"); } - private void fillTotalPointsSeries(List, List>> result, List chartSeries) { + private void fillTotalPointsSeries(List, List>> result, List chartSeries) { Double maxTotalPoints = 0.0; Set unusedSeries = new HashSet(competitorSeries.values()); for (Series series : competitorSeries.values()) { @@ -309,7 +309,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A int raceColumnNumber = 0; int maxCompetitorCount = 0; Map sumTotalPointsMap = new HashMap(); - for (Triple, List> entry : result) { + for (com.sap.sse.common.Util.Triple, List> entry : result) { List dataValues = entry.getC(); raceColumnNames.add(entry.getA()); if(hasValidValues(dataValues)) { 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 4a5b8f03ac1..a5cf8195332 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 @@ -8,17 +8,18 @@ import java.util.List; import java.util.Map; import org.moxieapps.gwt.highcharts.client.Axis; +import org.moxieapps.gwt.highcharts.client.BaseChart; import org.moxieapps.gwt.highcharts.client.Chart; import org.moxieapps.gwt.highcharts.client.ChartSubtitle; import org.moxieapps.gwt.highcharts.client.ChartTitle; import org.moxieapps.gwt.highcharts.client.Color; import org.moxieapps.gwt.highcharts.client.Credits; +import org.moxieapps.gwt.highcharts.client.PlotLine.DashStyle; import org.moxieapps.gwt.highcharts.client.Point; import org.moxieapps.gwt.highcharts.client.Series; import org.moxieapps.gwt.highcharts.client.ToolTip; import org.moxieapps.gwt.highcharts.client.ToolTipData; import org.moxieapps.gwt.highcharts.client.ToolTipFormatter; -import org.moxieapps.gwt.highcharts.client.PlotLine.DashStyle; import org.moxieapps.gwt.highcharts.client.events.ChartClickEvent; import org.moxieapps.gwt.highcharts.client.events.ChartClickEventHandler; import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEvent; @@ -39,9 +40,6 @@ import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.actions.GetCompetitorsRaceDataAction; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; @@ -53,6 +51,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.shared.CompetitorRaceDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider; import com.sap.sse.gwt.client.player.Timer; @@ -132,7 +131,7 @@ public abstract class AbstractCompetitorRaceChart> markPassingsData = competitorData.getMarkPassingsData(); + List> markPassingsData = competitorData.getMarkPassingsData(); List markPassingPoints = new ArrayList(); - for (Triple markPassingData : markPassingsData) { + for (com.sap.sse.common.Util.Triple markPassingData : markPassingsData) { if (markPassingData.getB() != null && markPassingData.getC() != null) { if (markPassingData.getB().before(toDate)) { Point markPassingPoint = new Point(markPassingData.getB().getTime(), @@ -318,11 +317,11 @@ public abstract class AbstractCompetitorRaceChart> raceData = competitorData.getRaceData(); + List> raceData = competitorData.getRaceData(); Point[] raceDataPointsToAdd = new Point[raceData.size()]; int currentPointIndex = 0; - for (Pair raceDataPoint : raceData) { + for (com.sap.sse.common.Util.Pair raceDataPoint : raceData) { Double dataPointValue = raceDataPoint.getB(); if(dataPointValue != null) { long dataPointTimeAsMillis = raceDataPoint.getA().getTime(); @@ -491,7 +490,7 @@ public abstract class AbstractCompetitorRaceChart zoomRange = timeRangeWithZoomProvider.getTimeZoom(); + com.sap.sse.common.Util.Pair zoomRange = timeRangeWithZoomProvider.getTimeZoom(); onTimeZoomChanged(zoomRange.getA(), zoomRange.getB()); } else { resetMinMaxAndExtremesInterval(/* redraw */ true); @@ -566,7 +565,7 @@ public abstract class AbstractCompetitorRaceChartnull
. */ - public Pair checkPassingRelationToSelection(ArrayList> markPassingInRange) { + public com.sap.sse.common.Util.Pair checkPassingRelationToSelection(ArrayList> markPassingInRange) { boolean everyPassingInRange = true; Boolean twoPassingsInRangeBeforeError = null; ArrayList competitorPassings = markPassingInRange.get(0); @@ -591,7 +590,7 @@ public abstract class AbstractCompetitorRaceChart(everyPassingInRange, twoPassingsInRangeBeforeError); + return new com.sap.sse.common.Util.Pair(everyPassingInRange, twoPassingsInRangeBeforeError); } @Override 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 88c43dee96c..a531abfc0ee 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 @@ -136,7 +136,7 @@ public abstract class AbstractRaceChart extends SimplePanel implements RaceSelec protected void setSeriesPoints(Series series, Point[] points) { if (timeRangeWithZoomProvider.isZoomed()) { - com.sap.sse.common.Pair timeZoom = timeRangeWithZoomProvider.getTimeZoom(); + com.sap.sse.common.Util.Pair timeZoom = timeRangeWithZoomProvider.getTimeZoom(); resetMinMaxAndExtremesInterval(/* redraw */ false); series.setPoints(points, false); changeMinMaxAndExtremesInterval(timeZoom.getA(), timeZoom.getB(), /* redraw */ false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartPointRecalculator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartPointRecalculator.java new file mode 100644 index 00000000000..6566d0ffec0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartPointRecalculator.java @@ -0,0 +1,58 @@ +package com.sap.sailing.gwt.ui.client.shared.charts; + +import org.moxieapps.gwt.highcharts.client.Point; + +public final class ChartPointRecalculator { + + private ChartPointRecalculator() { } + + public static Point stayClosestToPreviousPoint(Point previousPoint, Point newPoint) { + double previousY = previousPoint.getY().doubleValue(); + double newY = newPoint.getY().doubleValue(); + + double bestNewY = newY; + boolean hasBeenMoved; + do { + hasBeenMoved = false; + double deltaPreviousYToNewY = Math.abs(previousY - bestNewY); + double deltaPreviousYToNewYUp = Math.abs(previousY - (bestNewY + 360)); + double deltaPreviousYToNewYDown = Math.abs(previousY - (bestNewY - 360)); + + if (deltaPreviousYToNewYUp < deltaPreviousYToNewY || + deltaPreviousYToNewYDown < deltaPreviousYToNewY) { + bestNewY = deltaPreviousYToNewYUp <= deltaPreviousYToNewYDown ? + bestNewY + 360 : bestNewY - 360; + hasBeenMoved = true; + } + } while (hasBeenMoved); + + return new Point(newPoint.getX(), bestNewY); + } + + public static Point keepOverallDeltaMinimal(Double yMin, Double yMax, Point newPoint) { + double y = newPoint.getY().doubleValue(); + + if (yMax != null && yMin != null && (y < yMin || y > yMax)) { + double deltaMin = Math.abs(yMin - y); + double deltaMax = Math.abs(yMax - y); + + double yDown = y - 360; + double deltaMinDown = Math.abs(yMin - yDown); + + double yUp = y + 360; + double deltaMaxUp = Math.abs(yMax - yUp); + + if (isRecalculationNeeded(deltaMin, deltaMax, deltaMinDown, deltaMaxUp)) { + y = deltaMaxUp <= deltaMinDown ? yUp : yDown; + return new Point(newPoint.getX(), y); + } + } + + return newPoint; + } + + private static boolean isRecalculationNeeded(double deltaMin, double deltaMax, double deltaMinDown, double deltaMaxUp) { + return (deltaMinDown < deltaMin || deltaMinDown < deltaMax) || (deltaMaxUp < deltaMin || deltaMaxUp < deltaMax); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorLeaderboardChartSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorLeaderboardChartSettingsDialogComponent.java index 327fbbb90bf..8b81686a151 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorLeaderboardChartSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorLeaderboardChartSettingsDialogComponent.java @@ -49,6 +49,7 @@ public class MultiCompetitorLeaderboardChartSettingsDialogComponent implements S Label chartSelectionLabel = new Label(stringMessages.chooseChart()); mainPanel.add(chartSelectionLabel); chartTypeSelectionListBox = dialog.createListBox(/* isMultiSelect */false); + chartTypeSelectionListBox.ensureDebugId("ChartTypeListBox"); int i = 0; for (DetailType detailType : availableDetailsTypes) { chartTypeSelectionListBox.addItem(DetailTypeFormatter.format(detailType), detailType.name()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChart.java index 33dd2ed3a9b..51fd75dc417 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChart.java @@ -9,18 +9,19 @@ import java.util.Map; import java.util.Set; import org.moxieapps.gwt.highcharts.client.Axis; +import org.moxieapps.gwt.highcharts.client.BaseChart; import org.moxieapps.gwt.highcharts.client.Chart; import org.moxieapps.gwt.highcharts.client.ChartSubtitle; import org.moxieapps.gwt.highcharts.client.ChartTitle; import org.moxieapps.gwt.highcharts.client.Color; import org.moxieapps.gwt.highcharts.client.Credits; import org.moxieapps.gwt.highcharts.client.PlotLine; +import org.moxieapps.gwt.highcharts.client.PlotLine.DashStyle; import org.moxieapps.gwt.highcharts.client.Point; import org.moxieapps.gwt.highcharts.client.Series; import org.moxieapps.gwt.highcharts.client.ToolTip; import org.moxieapps.gwt.highcharts.client.ToolTipData; import org.moxieapps.gwt.highcharts.client.ToolTipFormatter; -import org.moxieapps.gwt.highcharts.client.PlotLine.DashStyle; import org.moxieapps.gwt.highcharts.client.events.ChartClickEvent; import org.moxieapps.gwt.highcharts.client.events.ChartClickEventHandler; import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEvent; @@ -41,19 +42,18 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.impl.ColorMapImpl; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.actions.GetWindInfoAction; import com.sap.sailing.gwt.ui.client.ErrorReporter; 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.client.WindSourceTypeFormatter; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; -import com.sap.sailing.gwt.ui.client.WindSourceTypeFormatter; 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.common.Util; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider; import com.sap.sse.gwt.client.player.Timer; @@ -73,7 +73,6 @@ public class WindChart extends AbstractRaceChart implements Component windSourceSpeedSeries; private final Map windSourceDirectionPoints; private final Map windSourceSpeedPoints; - private Pair overallDirectionMinMax; private Long timeOfEarliestRequestInMillis; private Long timeOfLatestRequestInMillis; @@ -96,11 +95,10 @@ public class WindChart extends AbstractRaceChart implements Component(); windSourceDirectionPoints = new HashMap(); windSourceSpeedPoints = new HashMap(); - overallDirectionMinMax = new Pair(null, null); colorMap = new ColorMapImpl(); chart = new Chart() .setPersistent(true) - .setZoomType(Chart.ZoomType.X) + .setZoomType(BaseChart.ZoomType.X) .setMarginLeft(65) .setMarginRight(65) .setWidth100() @@ -166,7 +164,7 @@ public class WindChart extends AbstractRaceChart implements Component currentSeries = Arrays.asList(chart.getSeries()); Set visibleSeries = new HashSet(currentSeries); - if(settings.isShowWindDirectionsSeries()) { + if (settings.isShowWindDirectionsSeries()) { for (Map.Entry e : windSourceDirectionSeries.entrySet()) { Series series = e.getValue(); if (settings.getWindDirectionSourcesToDisplay().contains(e.getKey().getType())) { @@ -248,7 +244,6 @@ public class WindChart extends AbstractRaceChart implements Component timeOfLatestRequestInMillis) { Point newDirectionPoint = new Point(wind.requestTimepoint, wind.dampenedTrueWindFromDeg); if (wind.dampenedTrueWindSpeedInKnots != null) { @@ -350,22 +345,17 @@ public class WindChart extends AbstractRaceChart implements Component directionMax) { - directionMax = direction; - } - if (directionMin == null || direction < directionMin) { - directionMin = direction; - } - Point newSpeedPoint = new Point(wind.requestTimepoint, wind.dampenedTrueWindSpeedInKnots); speedPoints[currentPointIndex++] = newSpeedPoint; } } - overallDirectionMinMax = new Pair(directionMin, directionMax); Point[] newDirectionPoints; Point[] newSpeedPoints = null; @@ -430,7 +420,6 @@ public class WindChart extends AbstractRaceChart implements Component(null, null); windSourceDirectionPoints.clear(); windSourceSpeedPoints.clear(); @@ -451,7 +440,8 @@ public class WindChart extends AbstractRaceChart implements Component() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChartPointRecalculator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChartPointRecalculator.java deleted file mode 100644 index 13e0ea69965..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChartPointRecalculator.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.sap.sailing.gwt.ui.client.shared.charts; - -import org.moxieapps.gwt.highcharts.client.Point; - -public final class WindChartPointRecalculator { - - private WindChartPointRecalculator() { } - - public static Point recalculateDirectionPoint(Double yMin, Double yMax, Point directionPoint) { - double y = directionPoint.getY().doubleValue(); - - if (yMax != null && yMin != null && (y < yMin || y > yMax)) { - double deltaMin = Math.abs(yMin - y); - double deltaMax = Math.abs(yMax - y); - - double yDown = y - 360; - double deltaMinDown = Math.abs(yMin - yDown); - - double yUp = y + 360; - double deltaMaxUp = Math.abs(yMax - yUp); - - if (isRecalculationNeeded(deltaMin, deltaMax, deltaMinDown, deltaMaxUp)) { - y = deltaMaxUp <= deltaMinDown ? yUp : yDown; - return new Point(directionPoint.getX(), y); - } - } - - return directionPoint; - } - - private static boolean isRecalculationNeeded(double deltaMin, double deltaMax, double deltaMinDown, double deltaMaxUp) { - return (deltaMinDown < deltaMin || deltaMinDown < deltaMax) || (deltaMaxUp < deltaMin || deltaMaxUp < deltaMax); - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CollapsablePanelHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CollapsablePanelHeader.java index b4bae949958..4dfd4b30fa5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CollapsablePanelHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CollapsablePanelHeader.java @@ -1,12 +1,12 @@ package com.sap.sailing.gwt.ui.client.shared.components; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.event.logical.shared.OpenEvent; import com.google.gwt.event.logical.shared.OpenHandler; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.DOM; -import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasText; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeSettings.java index 42b433dcb80..ca2c1e9e88e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeSettings.java @@ -1,9 +1,9 @@ package com.sap.sailing.gwt.ui.client.shared.components; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public class CompositeSettings { - public static class ComponentAndSettingsPair extends Pair, SettingsType> { + public static class ComponentAndSettingsPair extends Util.Pair, SettingsType> { private static final long serialVersionUID = -569811233041583043L; public ComponentAndSettingsPair(Component a, SettingsType b) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeValidator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeValidator.java index 4eea20f65d9..d9f996553ae 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeValidator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeValidator.java @@ -2,12 +2,12 @@ package com.sap.sailing.gwt.ui.client.shared.components; import java.util.ArrayList; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.client.shared.components.CompositeSettings.ComponentAndSettingsPair; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; public class CompositeValidator implements Validator { - private static class ComponentAndValidator extends Pair, Validator> { + private static class ComponentAndValidator extends Util.Pair, Validator> { private static final long serialVersionUID = -4190322565836849861L; public ComponentAndValidator(Component a, Validator b) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/ImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/ImagesBarCell.java index 1485b968509..86f6d744521 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/ImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/ImagesBarCell.java @@ -31,6 +31,8 @@ public abstract class ImagesBarCell extends AbstractSafeHtmlCell { */ private final static ImagesBarTemplates imageTemplate = GWT.create(ImagesBarTemplates.class); + private Context context; + protected class ImageSpec { private final AbstractImagePrototype imagePrototype; private final String actionName; @@ -138,9 +140,15 @@ public abstract class ImagesBarCell extends AbstractSafeHtmlCell { } protected abstract Iterable getImageSpecs(); + + protected Context getContext() { + return context; + } @Override protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) { + this.context = context; + /* * Always do a null check on the value. Cell widgets can pass null to * cells if the underlying data contains a null, or if the data arrives diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/CollapsedListEditorUi.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/CollapsedListEditorUi.java index 054ac15adeb..278f82700a6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/CollapsedListEditorUi.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/CollapsedListEditorUi.java @@ -12,6 +12,26 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; +/** + * Base class for "collapsed" list editing UIs for use in a {@link ListEditorComposite}. An editing strategy of this + * type shows the list of values in a single character string, together with an "Edit" button which pops up a dialog + * that contains an "expanded" editor of type {@link ExpandedListEditorUi}. + *

+ * + * To implement a "collapsed" editor, subclasses have to provide the {@link String} representation of a value list in + * method {@link #getCollapsedValueText(List)} and need to provide the widget used to edit the list. This widget will be + * displayed in the pop-up dialog, together with a "Save" and a "Cancel" button. It is a common pattern to use a + * {@link ListEditorComposite} with the respective {@link ExpandedListEditorUi} strategy that will be passed through + * from this class's constructor to the {@link #createExpandedUi(List, ExpandedListEditorUi)} method.

+ * + * Implementing subclasses may choose to override the {@link #onRowAdded()} and/or the {@link #onRowRemoved()} method(s) + * to be notified of changes to the list. + * + * @author Lukas Niemeier + * @author Axel Uhl (D043530) + * + * @param + */ public abstract class CollapsedListEditorUi extends ListEditorUi { private static final int collapsedTitleMaxLength = 20; @@ -78,11 +98,6 @@ public abstract class CollapsedListEditorUi extends ListEditorUi> { private ListEditorComposite expandedComposite; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ExpandedListEditorUi.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ExpandedListEditorUi.java index 63885cc8b52..a012c7f9d23 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ExpandedListEditorUi.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ExpandedListEditorUi.java @@ -11,12 +11,41 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.StringMessages; +/** + * A base class for an editing UI strategy for a list of objects of type ValueType for use in a + * {@link ListEditorComposite} instance. The widget that renders this list editing UI strategy has a section at the top + * that allows the user to add a new list entry. The widget used for this must be provided by implementing subclasses + * through the {@link #createAddWidget()} method. Next to this widget, an "Add" button is displayed. + *

+ * + * Beneath this section for adding new values, a grid with the existing values is displayed. Each value is rendered by a + * widget provided by implementing subclasses through the {@link #createValueWidget(int, Object)} method. If the + * canRemoveItems constructor parameter was set to true then next to each of these widgets, a + * "Remove" button is shown. + *

+ * + * Implementing subclasses may choose to override the {@link #onRowAdded()} and/or the {@link #onRowRemoved()} method(s) + * to be notified of changes to the list. + * + * @author Lukas Niemeier + * @author Axel Uhl (D043530) + * + * @param + */ public abstract class ExpandedListEditorUi extends ListEditorUi { private final ImageResource removeImage; private Grid expandedValuesGrid; private final boolean canRemoveItems; + /** + * @param removeImage + * may be null if canRemoveItems==false; otherwise, must contain a valid image + * to be used by the "Remove" button + * @param canRemoveItems + * if true, next to each widget rendering one value in the value list a "Remove" push button will + * be shown using the removeImage + */ public ExpandedListEditorUi(StringMessages stringMessages, ImageResource removeImage, boolean canRemoveItems) { super(stringMessages); this.removeImage = removeImage; @@ -30,7 +59,8 @@ public abstract class ExpandedListEditorUi extends ListEditorUi extends ListEditorUi extends ListEditorUi extends ListEditorUi valueWidget, ValueType newValue, boolean fireEvents) { - for(int i = 0; i < expandedValuesGrid.getRowCount(); i++) { + for (int i = 0; i < expandedValuesGrid.getRowCount(); i++) { Widget gridWidget = expandedValuesGrid.getWidget(i, 0); - if(gridWidget.getElement() == valueWidget.getElement()) { + if (gridWidget.getElement() == valueWidget.getElement()) { context.getValue().set(i, newValue); if (fireEvents) { context.onChange(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ListEditorComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ListEditorComposite.java index 541b83d151f..4e9afd8e952 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ListEditorComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ListEditorComposite.java @@ -10,6 +10,28 @@ import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HasValue; +/** + * A widget that lets users edit a list of ValueType objects. How the values are displayed and made + * editable is controlled by the {@link ListEditorUiStrategy} passed to the constructor. There are two standard UI + * strategies: a "collapsed" strategy provided by the abstract class {@link CollapsedListEditorUi} and an "expanded" + * strategy, provided by the abstract class {@link ExpandedListEditorUi}. The collapsed strategy displays the values + * separated by commas in a single text box and has an "edit' button that pops up a dialog with an "expanded" editing + * view for the list. The "expanded" strategy has a widget at its top that can be used to add an element, including an + * "Add" button, and beneath it a grid with one row per value where each row holds a (potentially editable) widget + * displaying a value, and a "remove" button next to it. The popup that displays the "expanded" strategy editor has a + * "Save" and a "Cancel" button at its bottom.

+ * + * To use this micro-framework for list editing, you can either provide only an "expanded" editor, or you can provide + * a "collapsed" and an "expanded" editor which are then used as a cascade. If your values are of type {@link String}, you + * may use the pre-defined {@link StringListEditorComposite} or {@link StringListInlineEditorComposite} classes. A + * "collapsed" editor UI strategy requires the corresponding "expanded" strategy (which is used for the "edit" pop-up + * dialog) as a constructor argument. + * + * @author Lukas Niemeier + * @author Axel Uhl (D043530) + * + * @param + */ public class ListEditorComposite extends Composite implements HasValue>, HasValueChangeHandlers> { @@ -17,7 +39,7 @@ public class ListEditorComposite extends Composite implements HasValu private List values; - protected ListEditorComposite(List initialValues, ListEditorUiStrategy activeUi) { + public ListEditorComposite(List initialValues, ListEditorUiStrategy activeUi) { this.values = new ArrayList(initialValues); this.activeUi = activeUi; this.activeUi.setContext(this); @@ -53,9 +75,4 @@ public class ListEditorComposite extends Composite implements HasValu public void onChange() { ValueChangeEvent.fire(this, getValue()); } - - public boolean isCollapsed() { - return activeUi.isCollapsed(); - } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ListEditorUiStrategy.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ListEditorUiStrategy.java index c16ccf902bb..646c2011868 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ListEditorUiStrategy.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/ListEditorUiStrategy.java @@ -2,15 +2,58 @@ package com.sap.sailing.gwt.ui.client.shared.controls.listedit; import com.google.gwt.user.client.ui.Widget; +/** + * Describes what a UI strategy for a {@link ListEditorComposite value list editor} must provide and which callbacks it + * will receive. The {@link #initWidget()} method provides the core UI element used to display an editable or uneditable + * list of values of type ValueType. When the {@link #refresh()} method is invoked by the framework, the + * implementing class needs to update the contents of the widget returned from {@link #initWidget} based on the + * {@link ListEditorComposite#getValue() values} of the context editor that was passed to + * {@link #setContext(ListEditorComposite)} during editor initialization.

+ * + * Objects will receive calls to {@link #onRowAdded()} when a value was added to the list, and {@link #onRowRemoved()} when + * a value was removed from the list.

+ * + * There are two default implementations of this interface: {@link CollapsedListEditorUi} and {@link ExpandedListEditorUi}. + * + * @author Lukas Niemeier + * @author Axel Uhl (D043530) + * + * @param + */ public interface ListEditorUiStrategy { - Widget initWidget(); - - void refresh(); - + /** + * Called by the framework before any other operations of this object are called. + * + * @param context + * the editor using this strategy to display and/or edit the value list. The + * {@link ListEditorComposite#getValue()} method can be used to obtain the current + * list of values from the editor. + */ void setContext(ListEditorComposite context); - boolean isCollapsed(); - + /** + * Called by the framework to obtain the UI widget to display to the user to show and/or make + * editable the list of values managed by the context {@link ListEditorComposite editor}. + */ + Widget initWidget(); + + /** + * Instructs this object to refresh the contents of the widget returned from {@link #initWidget()} + * based on the {@link ListEditorComposite#getValue() values} managed by the editor. This method + * is called when the entire value list has changed or has been set for the first time. + */ + void refresh(); + + /** + * Called by the framework after a value was added to the list. Note that this is not the place + * for this UI strategy to actually implement the UI changes. Those are expected to already have + * happened before. + */ void onRowAdded(); + + /** + * Called by the framework after a value was removed from the list. Note that this is not the place for this UI + * strategy to actually implement the UI changes. Those are expected to already have happened before. + */ void onRowRemoved(); } \ 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/listedit/StringListInlineEditorComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/StringListInlineEditorComposite.java index 23b2bc4d143..0c7c35118c9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/StringListInlineEditorComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/listedit/StringListInlineEditorComposite.java @@ -105,6 +105,7 @@ public class StringListInlineEditorComposite extends StringListEditorComposite { @Override protected Widget createValueWidget(final int rowIndex, String newValue) { final TextBox textBox = new TextBox(); + textBox.ensureDebugId("ValueTextBox"); textBox.setValue(newValue); textBox.addKeyUpHandler(new KeyUpHandler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/panels/UserStatusPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/panels/UserStatusPanel.java index ffeededf90e..040f95b3036 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/panels/UserStatusPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/panels/UserStatusPanel.java @@ -1,17 +1,14 @@ package com.sap.sailing.gwt.ui.client.shared.panels; import com.google.gwt.dom.client.Style; -import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.UserManagementServiceAsync; import com.sap.sailing.gwt.ui.shared.UserDTO; public class UserStatusPanel extends FlowPanel { - private UserDTO user; + private final UserDTO user; private final Label userNameLabel; @@ -19,27 +16,18 @@ public class UserStatusPanel extends FlowPanel { private final Label userRolesText; - public UserStatusPanel(final UserManagementServiceAsync userManagementService, final ErrorReporter errorReporter) { + public UserStatusPanel(UserDTO user) { super(); + this.user = user; userNameLabel = new Label("User:"); userNameText = new Label(""); userRolesText = new Label(""); - user = null; - userManagementService.getUser(new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Could not read user: " + caught.getMessage()); - } - @Override - public void onSuccess(UserDTO result) { - updateUser(result); - } - }); addFloatingWidget(userNameLabel); addFloatingWidget(userNameText); addFloatingWidget(userRolesText); - updateUser(user); + + updateUser(); } private void addFloatingWidget(Widget w) { @@ -48,9 +36,7 @@ public class UserStatusPanel extends FlowPanel { add(w); } - private void updateUser(UserDTO newUser) { - this.user = newUser; - + private void updateUser() { if(user != null) { userNameText.setText(user.principalName); userRolesText.setText(user.roles.toString()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java index e0657294f8a..c25717ce397 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java @@ -12,11 +12,11 @@ import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.dto.BoatClassDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO; import com.sap.sailing.gwt.ui.shared.racemap.BoatClassVectorGraphics; import com.sap.sailing.gwt.ui.shared.racemap.CanvasOverlayV3; +import com.sap.sse.common.Util; /** * A google map overlay based on a HTML5 canvas for drawing boats (images) @@ -44,7 +44,7 @@ public class BoatOverlay extends CanvasOverlayV3 { private Color color; - private Map> boatScaleAndSizePerZoomCache; + private Map> boatScaleAndSizePerZoomCache; private final BoatClassVectorGraphics boatVectorGraphics; @@ -70,7 +70,7 @@ public class BoatOverlay extends CanvasOverlayV3 { this.boatClass = competitorDTO.getBoatClass(); this.color = color; - boatScaleAndSizePerZoomCache = new HashMap>(); + boatScaleAndSizePerZoomCache = new HashMap>(); boatVectorGraphics = BoatClassVectorGraphicsResolver.resolveBoatClassVectorGraphics(boatClass.getName()); } @@ -79,7 +79,7 @@ public class BoatOverlay extends CanvasOverlayV3 { if (mapProjection != null && boatFix != null) { // the possible zoom level range is 0 to 21 (zoom level 0 would show the whole world) int zoom = map.getZoom(); - Pair boatScaleAndSize = boatScaleAndSizePerZoomCache.get(zoom); + Util.Pair boatScaleAndSize = boatScaleAndSizePerZoomCache.get(zoom); if (boatScaleAndSize == null) { boatScaleAndSize = getBoatScaleAndSize(boatClass); boatScaleAndSizePerZoomCache.put(zoom, boatScaleAndSize); @@ -152,7 +152,7 @@ public class BoatOverlay extends CanvasOverlayV3 { this.boatFix = boatFix; } - public Pair getBoatScaleAndSize(BoatClassDTO boatClass) { + public Util.Pair getBoatScaleAndSize(BoatClassDTO boatClass) { // the minimum boat length is related to the hull of the boat, not the overall length double minBoatHullLengthInPx = boatVectorGraphics.getMinHullLengthInPx(); @@ -168,6 +168,6 @@ public class BoatOverlay extends CanvasOverlayV3 { // as the canvas contains the whole boat the canvas size relates to the overall length, not the hull length double scaledCanvasSize = (boatVectorGraphics.getOverallLengthInPx()) * boatSizeScaleFactor; - return new Pair(boatSizeScaleFactor, Size.newInstance(scaledCanvasSize + scaledCanvasSize / 2.0, scaledCanvasSize + scaledCanvasSize / 2.0)); + return new Util.Pair(boatSizeScaleFactor, Size.newInstance(scaledCanvasSize + scaledCanvasSize / 2.0, scaledCanvasSize + scaledCanvasSize / 2.0)); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java index f3b9dcbab55..396c9733d20 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java @@ -53,7 +53,7 @@ public class CombinedWindPanel extends FlowPanel { protected void redraw() { if (windTrackInfoDTO != null) { - if (windTrackInfoDTO.windFixes.size() > 0) { + if (!windTrackInfoDTO.windFixes.isEmpty()) { WindDTO windDTO = windTrackInfoDTO.windFixes.get(0); double speedInKnots = windDTO.dampenedTrueWindSpeedInKnots; double windFromDeg = windDTO.dampenedTrueWindFromDeg; @@ -73,10 +73,6 @@ public class CombinedWindPanel extends FlowPanel { } } - public WindTrackInfoDTO getWindTrackInfoDTO() { - return windTrackInfoDTO; - } - public void setWindInfo(WindTrackInfoDTO windTrackInfoDTO, WindSource windSource) { this.windTrackInfoDTO = windTrackInfoDTO; this.windSource = windSource; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java index b45db1899a1..73764c14668 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java @@ -11,10 +11,10 @@ import com.google.gwt.maps.client.base.Point; import com.google.gwt.maps.client.base.Size; import com.sap.sailing.domain.common.MarkType; import com.sap.sailing.domain.common.dto.PositionDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.racemap.CanvasOverlayV3; import com.sap.sailing.gwt.ui.shared.racemap.MarkVectorGraphics; +import com.sap.sse.common.Util; /** * A google map overlay based on a HTML5 canvas for drawing course marks (images) and the buoy zone if the mark is a buoy. @@ -36,7 +36,7 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { private final MarkVectorGraphics markVectorGraphics; - private Map> markScaleAndSizePerZoomCache; + private Map> markScaleAndSizePerZoomCache; public CourseMarkOverlay(MapWidget map, int zIndex, MarkDTO markDTO) { super(map, zIndex); @@ -46,7 +46,7 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { this.showBuoyZone = false; markVectorGraphics = new MarkVectorGraphics(markDTO.type, markDTO.color, markDTO.shape, markDTO.pattern); - markScaleAndSizePerZoomCache = new HashMap>(); + markScaleAndSizePerZoomCache = new HashMap>(); setCanvasSize(50, 50); } @@ -57,7 +57,7 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { if (mapProjection != null && mark != null && position != null) { int zoom = map.getZoom(); - Pair markScaleAndSize = markScaleAndSizePerZoomCache.get(zoom); + Util.Pair markScaleAndSize = markScaleAndSizePerZoomCache.get(zoom); if(markScaleAndSize == null) { markScaleAndSize = getMarkScaleAndSize(position); markScaleAndSizePerZoomCache.put(zoom, markScaleAndSize); @@ -107,7 +107,7 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { } } - public Pair getMarkScaleAndSize(PositionDTO markPosition) { + public Util.Pair getMarkScaleAndSize(PositionDTO markPosition) { double minMarkHeight = 20; // the original buoy vector graphics is too small (2.1m x 1.5m) for higher zoom levels @@ -126,7 +126,7 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { // To calculate the display real mark size the scale factor from canvas units to the real double markSizeScaleFactor = markHeightInPixel / (markVectorGraphics.getMarkHeightInMeters() * 100); - return new Pair(markSizeScaleFactor, Size.newInstance(markHeightInPixel * 2.0, markHeightInPixel * 2.0)); + return new Util.Pair(markSizeScaleFactor, Size.newInstance(markHeightInPixel * 2.0, markHeightInPixel * 2.0)); } private String getTitle() { 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 9f4e17f6210..75a95143493 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 @@ -15,9 +15,10 @@ import com.google.gwt.maps.client.overlays.Polyline; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.shared.GPSFixDTO; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Triple; /** * Manages the cache of {@link GPSFixDTO}s for the competitors and the polylines encoding the tails that visualize the @@ -432,7 +433,7 @@ public class FixesAndTails { * {@link Triple#getC() third} component tells whether the existing fixes can remain and be augmented by * those requested or need to be replaced */ - protected Triple, Map, Map> computeFromAndTo( + protected Util.Triple, Map, Map> computeFromAndTo( Date upTo, Iterable competitorsToShow, long effectiveTailLengthInMilliseconds) { Date tailstart = new Date(upTo.getTime() - effectiveTailLengthInMilliseconds); Map from = new HashMap(); @@ -472,7 +473,7 @@ public class FixesAndTails { overlapWithKnownFixes.put(competitor, overlap); } } - return new Triple, Map, Map>(from, to, + return new Util.Triple, Map, Map>(from, to, overlapWithKnownFixes); } 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 f63795a06b6..b7639935332 100755 --- 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 @@ -66,12 +66,9 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.RGBColor; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction; import com.sap.sailing.gwt.ui.actions.GetWindInfoAction; -import com.sap.sailing.gwt.ui.adminconsole.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.ErrorReporter; @@ -86,6 +83,7 @@ import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; 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.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.LegInfoDTO; @@ -101,6 +99,7 @@ import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapAPIKey; import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.Timer; @@ -269,7 +268,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe private LatLngBounds currentMapBounds; public RaceMap(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter, Timer timer, - CompetitorSelectionProvider competitorSelection, StringMessages stringMessages) { + CompetitorSelectionProvider competitorSelection, StringMessages stringMessages, boolean showMapControls) { this.setSize("100%", "100%"); this.stringMessages = stringMessages; this.sailingService = sailingService; @@ -290,13 +289,13 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe settings = new RaceMapSettings(); lastTimeChangeBeforeInitialization = null; isMapInitialized = false; - initializeData(); + initializeData(showMapControls); combinedWindPanel = new CombinedWindPanel(raceMapImageManager, stringMessages); combinedWindPanel.setVisible(false); } - private void loadMapsAPIV3() { + private void loadMapsAPIV3(final boolean showMapControls) { boolean sensor = true; // load all the libs for use in the maps @@ -309,9 +308,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe public void run() { MapOptions mapOptions = MapOptions.newInstance(); mapOptions.setScrollWheel(true); - mapOptions.setMapTypeControl(true); - mapOptions.setPanControl(true); - mapOptions.setZoomControl(true); + mapOptions.setMapTypeControl(showMapControls); + mapOptions.setPanControl(showMapControls); + mapOptions.setZoomControl(showMapControls); mapOptions.setScaleControl(true); MapTypeStyle[] mapTypeStyles = new MapTypeStyle[4]; @@ -330,15 +329,16 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe ScaleControlOptions scaleControlOptions = ScaleControlOptions.newInstance(); scaleControlOptions.setPosition(ControlPosition.BOTTOM_RIGHT); mapOptions.setScaleControlOptions(scaleControlOptions); + mapOptions.setStreetViewControl(false); + if (showMapControls) { + ZoomControlOptions zoomControlOptions = ZoomControlOptions.newInstance(); + zoomControlOptions.setPosition(ControlPosition.TOP_RIGHT); + mapOptions.setZoomControlOptions(zoomControlOptions); - ZoomControlOptions zoomControlOptions = ZoomControlOptions.newInstance(); - zoomControlOptions.setPosition(ControlPosition.TOP_RIGHT); - mapOptions.setZoomControlOptions(zoomControlOptions); - - PanControlOptions panControlOptions = PanControlOptions.newInstance(); - panControlOptions.setPosition(ControlPosition.TOP_RIGHT); - mapOptions.setPanControlOptions(panControlOptions); - + PanControlOptions panControlOptions = PanControlOptions.newInstance(); + panControlOptions.setPosition(ControlPosition.TOP_RIGHT); + mapOptions.setPanControlOptions(panControlOptions); + } map = new MapWidget(mapOptions); RaceMap.this.add(map, 0, 0); RaceMap.this.add(combinedWindPanel, 10, 10); @@ -424,7 +424,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe final Iterable competitorsToShow = getCompetitorsToShow(); if (race != null) { - final Triple, Map, Map> fromAndToAndOverlap = + final com.sap.sse.common.Util.Triple, Map, Map> fromAndToAndOverlap = fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); final int requestID = ++boatPositionRequestIDCounter; @@ -491,7 +491,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); windSourceTypeNames.add(WindSourceType.COMBINED.name()); - GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, race, newTime, 1000L, 1, windSourceTypeNames); + GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, race, newTime, 1000L, 1, windSourceTypeNames, + /* onlyUpToNewestEvent==false means get us any data we can get by a best effort */ false); asyncActionsExecutor.execute(getWindInfoAction, GET_WIND_DATA_CATEGORY, new AsyncCallback() { @Override public void onFailure(Throwable caught) { @@ -500,7 +501,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe @Override public void onSuccess(WindInfoForRaceDTO windInfo) { - List> windSourcesToShow = new ArrayList>(); + List> windSourcesToShow = new ArrayList>(); if (windInfo != null) { for (WindSource windSource: windInfo.windTrackInfoByWindSource.keySet()) { WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource); @@ -508,7 +509,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe case EXPEDITION: // we filter out measured wind sources with a very little confidence if (windTrackInfoDTO.minWindConfidence > 0.01) { - windSourcesToShow.add(new Pair(windSource, windTrackInfoDTO)); + windSourcesToShow.add(new com.sap.sse.common.Util.Pair(windSource, windTrackInfoDTO)); } break; case COMBINED: @@ -628,10 +629,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } } - protected void showWindSensorsOnMap(List> windSensorsList) { + protected void showWindSensorsOnMap(List> windSensorsList) { if (map != null) { Set toRemoveWindSources = new HashSet(windSensorOverlays.keySet()); - for (Pair windSourcePair : windSensorsList) { + for (com.sap.sse.common.Util.Pair windSourcePair : windSensorsList) { WindSource windSource = windSourcePair.getA(); WindTrackInfoDTO windTrackInfoDTO = windSourcePair.getB(); @@ -762,7 +763,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return LatLng.newInstance(lat2 / Math.PI * 180., lon2 / Math.PI * 180.); } - private Pair getLeadingVisibleCompetitorInfo(Iterable competitorsToShow) { + private com.sap.sse.common.Util.Pair getLeadingVisibleCompetitorInfo(Iterable competitorsToShow) { CompetitorDTO leadingCompetitorDTO = null; int legOfLeaderCompetitor = -1; // this only works because the quickRanks are sorted @@ -770,7 +771,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (Util.contains(competitorsToShow, quickRank.competitor)) { leadingCompetitorDTO = quickRank.competitor; legOfLeaderCompetitor = quickRank.legNumber; - return new Pair(legOfLeaderCompetitor, leadingCompetitorDTO); + return new com.sap.sse.common.Util.Pair(legOfLeaderCompetitor, leadingCompetitorDTO); } } return null; @@ -778,11 +779,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe private void showAdvantageLine(Iterable competitorsToShow, Date date) { if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null - && lastCombinedWindTrackInfoDTO.windFixes.size() > 0) { + && !lastCombinedWindTrackInfoDTO.windFixes.isEmpty()) { boolean drawAdvantageLine = false; if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) { // find competitor with highest rank - Pair visibleLeaderInfo = getLeadingVisibleCompetitorInfo(competitorsToShow); + com.sap.sse.common.Util.Pair visibleLeaderInfo = getLeadingVisibleCompetitorInfo(competitorsToShow); // the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat; // don't show anything GPSFixDTO lastBoatFix = null; @@ -896,7 +897,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe private void showStartAndFinishLines(final CoursePositionsDTO courseDTO) { if (map != null && courseDTO != null && lastRaceTimesInfo != null) { - Pair leadingVisibleCompetitorInfo = getLeadingVisibleCompetitorInfo(getCompetitorsToShow()); + com.sap.sse.common.Util.Pair leadingVisibleCompetitorInfo = getLeadingVisibleCompetitorInfo(getCompetitorsToShow()); int legOfLeadingCompetitor = leadingVisibleCompetitorInfo == null ? -1 : leadingVisibleCompetitorInfo.getA(); int numberOfLegs = lastRaceTimesInfo.legInfos.size(); // draw the start line @@ -1428,7 +1429,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe for (ManeuverDTO maneuver : maneuversForCompetitor) { if (settings.isShowManeuverType(maneuver.type)) { LatLng latLng = LatLng.newInstance(maneuver.position.latDeg, maneuver.position.lngDeg); - Marker maneuverMarker = raceMapImageManager.maneuverIconsForTypeAndTargetTack.get(new Util.Pair(maneuver.type, maneuver.newTack)); + Marker maneuverMarker = raceMapImageManager.maneuverIconsForTypeAndTargetTack.get(new com.sap.sse.common.Util.Pair(maneuver.type, maneuver.newTack)); MarkerOptions options = MarkerOptions.newInstance(); options.setTitle(maneuver.toString(stringMessages)); options.setIcon(maneuverMarker.getIcon_MarkerImage()); @@ -1480,16 +1481,18 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } else { closer = fixAfter; } - Date betweenDate = new Date((fixBefore.timepoint.getTime() + fixAfter.timepoint.getTime()) / 2); - PositionDTO betweenPosition = new PositionDTO((fixBefore.position.latDeg + fixAfter.position.latDeg)/2, - (fixBefore.position.lngDeg + fixAfter.position.lngDeg)/2); - double betweenBearing = new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees).middle( - new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)).getDegrees(); + // now compute a weighted average depending on the time difference to "date" (see also bug 1924) + double factorForAfter = (double) (date.getTime()-fixBefore.timepoint.getTime()) / (double) (fixAfter.timepoint.getTime() - fixBefore.timepoint.getTime()); + double factorForBefore = 1-factorForAfter; + PositionDTO betweenPosition = new PositionDTO(factorForBefore*fixBefore.position.latDeg + factorForAfter*fixAfter.position.latDeg, + factorForBefore*fixBefore.position.lngDeg + factorForAfter*fixAfter.position.lngDeg); + double betweenBearing = new ScalableBearing(new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees)). + multiply(factorForBefore).add(new ScalableBearing(new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)). + multiply(factorForAfter)).divide(1).getDegrees(); SpeedWithBearingDTO betweenSpeed = new SpeedWithBearingDTO( - (fixBefore.speedWithBearing.speedInKnots + fixAfter.speedWithBearing.speedInKnots)/2, + factorForBefore*fixBefore.speedWithBearing.speedInKnots + factorForAfter*fixAfter.speedWithBearing.speedInKnots, betweenBearing); - // TODO move SpeedWithBearing and GPSFix with implementation classes to com.sap.sailing.domain.common and share in GWT bundle - result = new GPSFixDTO(betweenDate, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind, + result = new GPSFixDTO(date, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind, closer.tack, closer.legType, fixBefore.extrapolated || fixAfter.extrapolated); } } else { @@ -1786,8 +1789,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } @Override - public void initializeData() { - loadMapsAPIV3(); + public void initializeData(boolean showMapControls) { + loadMapsAPIV3(showMapControls); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapImageManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapImageManager.java index e68d25917f4..9b62a85c315 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapImageManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapImageManager.java @@ -11,7 +11,7 @@ import com.google.gwt.maps.client.overlays.MarkerImage; import com.google.gwt.maps.client.overlays.MarkerOptions; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public class RaceMapImageManager { @@ -25,12 +25,12 @@ public class RaceMapImageManager { */ protected ImageTransformer windSensorIconTransformer; - protected Map, Marker> maneuverIconsForTypeAndTargetTack; + protected Map, Marker> maneuverIconsForTypeAndTargetTack; private static RaceMapResources resources = GWT.create(RaceMapResources.class); public RaceMapImageManager() { - maneuverIconsForTypeAndTargetTack = new HashMap, Marker>(); + maneuverIconsForTypeAndTargetTack = new HashMap, Marker>(); combinedWindIconTransformer = new ImageTransformer(resources.combinedWindIcon()); windSensorIconTransformer = new ImageTransformer(resources.expeditionWindIcon()); @@ -43,44 +43,44 @@ public class RaceMapImageManager { public void loadMapIcons(MapWidget map) { if(map != null) { Marker tackToStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|71bf44|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.TACK, Tack.STARBOARD), tackToStarboardIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.TACK, Tack.STARBOARD), tackToStarboardIcon); Marker tackToPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|d95252|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.TACK, Tack.PORT), tackToPortIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.TACK, Tack.PORT), tackToPortIcon); Marker jibeToStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|71bf44|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.JIBE, Tack.STARBOARD), jibeToStarboardIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.JIBE, Tack.STARBOARD), jibeToStarboardIcon); Marker jibeToPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|d95252|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.JIBE, Tack.PORT), jibeToPortIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.JIBE, Tack.PORT), jibeToPortIcon); Marker headUpOnStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|71bf44|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.HEAD_UP, Tack.STARBOARD), headUpOnStarboardIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.HEAD_UP, Tack.STARBOARD), headUpOnStarboardIcon); Marker headUpOnPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|d95252|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.HEAD_UP, Tack.PORT), headUpOnPortIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.HEAD_UP, Tack.PORT), headUpOnPortIcon); Marker bearAwayOnStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|71bf44|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.BEAR_AWAY, Tack.STARBOARD), bearAwayOnStarboardIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.BEAR_AWAY, Tack.STARBOARD), bearAwayOnStarboardIcon); Marker bearAwayOnPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|d95252|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.BEAR_AWAY, Tack.PORT), bearAwayOnPortIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.BEAR_AWAY, Tack.PORT), bearAwayOnPortIcon); Marker markPassingToStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|71bf44|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.MARK_PASSING, Tack.STARBOARD), markPassingToStarboardIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.MARK_PASSING, Tack.STARBOARD), markPassingToStarboardIcon); Marker markPassingToPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|d95252|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.MARK_PASSING, Tack.PORT), markPassingToPortIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.MARK_PASSING, Tack.PORT), markPassingToPortIcon); Marker unknownManeuverIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=?|FFFFFF|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.UNKNOWN, Tack.STARBOARD), unknownManeuverIcon); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.UNKNOWN, Tack.PORT), unknownManeuverIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.UNKNOWN, Tack.STARBOARD), unknownManeuverIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.UNKNOWN, Tack.PORT), unknownManeuverIcon); Marker penaltyCircleToStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|71bf44|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.PENALTY_CIRCLE, Tack.STARBOARD), penaltyCircleToStarboardIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.PENALTY_CIRCLE, Tack.STARBOARD), penaltyCircleToStarboardIcon); Marker penaltyCircleToPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|d95252|000000", 10, 33); - maneuverIconsForTypeAndTargetTack.put(new Pair(ManeuverType.PENALTY_CIRCLE, Tack.PORT), penaltyCircleToPortIcon); + maneuverIconsForTypeAndTargetTack.put(new Util.Pair(ManeuverType.PENALTY_CIRCLE, Tack.PORT), penaltyCircleToPortIcon); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java index 3b9df175ac6..a52f4406db8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java @@ -18,20 +18,20 @@ import com.google.gwt.user.client.ui.LongBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.ManeuverType; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; 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.client.ManeuverTypeFormatter; -import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; public class RaceMapSettingsDialogComponent implements SettingsDialogComponent { //Initializing the lists to prevent a null pointer exception in the first validation call - private List> checkboxAndManeuverType = new ArrayList>(); - private List> checkboxAndZoomType = new ArrayList>(); - private List> checkboxAndHelpLineType = new ArrayList>(); + private List> checkboxAndManeuverType = new ArrayList>(); + private List> checkboxAndZoomType = new ArrayList>(); + private List> checkboxAndHelpLineType = new ArrayList>(); private CheckBox zoomOnlyToSelectedCompetitorsCheckBox; private CheckBox showDouglasPeuckerPointsCheckBox; private CheckBox showOnlySelectedCompetitorsCheckBox; @@ -76,7 +76,7 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent(cb, zoomType)); + checkboxAndZoomType.add(new Util.Pair(cb, zoomType)); zoomSettingsBoxesPanel.add(cb); //Save specific checkboxes for easier value change handling @@ -105,7 +105,7 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent(checkbox, maneuverType)); + checkboxAndManeuverType.add(new Util.Pair(checkbox, maneuverType)); vp.add(checkbox); } showDouglasPeuckerPointsCheckBox = dialog.createCheckbox(stringMessages.douglasPeuckerPoints()); @@ -168,13 +168,13 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent dialog, HelpLineTypes helpLineType) { CheckBox cb = dialog.createCheckbox(RaceMapSettingsTypeFormatter.formatHelpLineType(helpLineType, stringMessages)); cb.setValue(initialSettings.getHelpLinesSettings().isVisible(helpLineType)); - checkboxAndHelpLineType.add(new Pair(cb, helpLineType)); + checkboxAndHelpLineType.add(new Util.Pair(cb, helpLineType)); return cb; } private void zoomSettingsChanged() { boolean disableOnlySelected = true; - for (Pair pair : checkboxAndZoomType) { + for (Util.Pair pair : checkboxAndZoomType) { pair.getA().setEnabled(true); if (disableOnlySelectedWhenAreFalse.contains(pair.getA())) { if (pair.getA().getValue()) { @@ -192,7 +192,7 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent p : checkboxAndManeuverType) { + for (Util.Pair p : checkboxAndManeuverType) { result.showManeuverType(p.getB(), p.getA().getValue()); } RaceMapHelpLinesSettings helpLinesSettings = getHelpLinesSettings(); @@ -214,7 +214,7 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent zoomTypes = new ArrayList(); boolean noAutoZoomSelected = true; - for (Pair pair : checkboxAndZoomType) { + for (Util.Pair pair : checkboxAndZoomType) { if (pair.getA().getValue()) { zoomTypes.add(pair.getB()); noAutoZoomSelected = false; @@ -228,7 +228,7 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent helpLineTypes = new HashSet(); - for (Pair pair : checkboxAndHelpLineType) { + for (Util.Pair pair : checkboxAndHelpLineType) { if (pair.getA().getValue()) { helpLineTypes.add(pair.getB()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DateAndTimeFormatterUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/DateAndTimeFormatterUtil.java similarity index 95% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DateAndTimeFormatterUtil.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/DateAndTimeFormatterUtil.java index a46d0540561..6f847bd796a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DateAndTimeFormatterUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/DateAndTimeFormatterUtil.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.adminconsole; +package com.sap.sailing.gwt.ui.common.client; import java.util.Date; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/shared/Dummy.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/shared/Dummy.java new file mode 100644 index 00000000000..5bcf51c5c3b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/shared/Dummy.java @@ -0,0 +1,5 @@ +package com.sap.sailing.gwt.ui.common.shared; + +public class Dummy { + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningEntryPoint.java index a439f87d4de..14dec88a056 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningEntryPoint.java @@ -15,10 +15,10 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.google.gwt.user.client.ui.SplitLayoutPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.datamining.shared.impl.SimpleQueryDefinition; import com.sap.sailing.datamining.shared.DataTypes; import com.sap.sailing.datamining.shared.DimensionIdentifier; import com.sap.sailing.datamining.shared.StatisticType; +import com.sap.sailing.datamining.shared.impl.QueryDefinitionDeprecatedImpl; import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel; import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; @@ -28,12 +28,13 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.datamining.client.execution.SimpleQueryRunner; -import com.sap.sailing.gwt.ui.datamining.client.presentation.ResultsChart; +import com.sap.sailing.gwt.ui.datamining.client.presentation.PlainResultsPresenter; import com.sap.sailing.gwt.ui.datamining.client.selection.QueryDefinitionProviderWithControls; import com.sap.sailing.gwt.ui.datamining.client.settings.QueryRunnerSettings; import com.sap.sailing.gwt.ui.datamining.client.settings.RefreshingSelectionTablesSettings; import com.sap.sse.datamining.shared.components.AggregatorType; import com.sap.sse.datamining.shared.components.GrouperType; +import com.sap.sse.gwt.client.EntryPointHelper; public class DataMiningEntryPoint extends AbstractEntryPoint { private final SailingServiceAsync sailingService = GWT.create(SailingService.class); @@ -46,8 +47,8 @@ public class DataMiningEntryPoint extends AbstractEntryPoint { protected void doOnModuleLoad() { super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); - registerASyncService((ServiceDefTarget) dataMiningService, RemoteServiceMappingConstants.dataMiningServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) dataMiningService, RemoteServiceMappingConstants.dataMiningServiceRemotePath); RootLayoutPanel rootPanel = RootLayoutPanel.get(); SplitLayoutPanel splitPanel = new SplitLayoutPanel(); @@ -60,7 +61,7 @@ public class DataMiningEntryPoint extends AbstractEntryPoint { queryDefinitionProvider.getEntryWidget().addStyleName("dataMiningPanel"); dockPanel.add(queryDefinitionProvider.getEntryWidget()); - ResultsPresenter resultsPresenter = new ResultsChart(stringMessages); + ResultsPresenter resultsPresenter = new PlainResultsPresenter(stringMessages); splitPanel.addSouth(resultsPresenter.getWidget(), 400); splitPanel.add(dockPanel); @@ -69,7 +70,7 @@ public class DataMiningEntryPoint extends AbstractEntryPoint { queryDefinitionProviderWithControls.addControl(queryRunner.getEntryWidget()); queryDefinitionProviderWithControls.addControl(createSettingsControlWidget(queryRunner, queryDefinitionProvider)); - SimpleQueryDefinition queryDefinition = new SimpleQueryDefinition(LocaleInfo.getCurrentLocale().getLocaleName(), GrouperType.Dimensions, StatisticType.Speed, AggregatorType.Average, DataTypes.GPSFix); + QueryDefinitionDeprecatedImpl queryDefinition = new QueryDefinitionDeprecatedImpl(LocaleInfo.getCurrentLocale().getLocaleName(), GrouperType.Dimensions, StatisticType.Speed, AggregatorType.Average, DataTypes.GPSFix); queryDefinition.appendDimensionToGroupBy(DimensionIdentifier.RegattaName); queryDefinitionProvider.applyQueryDefinition(queryDefinition); queryRunner.run(queryDefinitionProvider.getQueryDefinition()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningService.java index ce6f83a8213..484b0cb7c60 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningService.java @@ -4,7 +4,7 @@ import java.util.Collection; import com.google.gwt.user.client.rpc.RemoteService; import com.sap.sailing.datamining.shared.DataTypes; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sse.datamining.shared.DataMiningSerializationDummy; import com.sap.sse.datamining.shared.QueryResult; import com.sap.sse.datamining.shared.dto.FunctionDTO; @@ -13,7 +13,7 @@ public interface DataMiningService extends RemoteService { Collection getDimensionsFor(DataTypes dataType); - QueryResult runQuery(QueryDefinition queryDefinition) throws Exception; + QueryResult runQuery(QueryDefinitionDeprecated queryDefinition) throws Exception; DataMiningSerializationDummy pseudoMethodSoThatSomeDataMiningClassesAreAddedToTheGWTSerializationPolicy(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningServiceAsync.java index 7ede7c5f667..22b40c21461 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/DataMiningServiceAsync.java @@ -4,7 +4,7 @@ import java.util.Collection; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.datamining.shared.DataTypes; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sse.datamining.shared.DataMiningSerializationDummy; import com.sap.sse.datamining.shared.QueryResult; import com.sap.sse.datamining.shared.dto.FunctionDTO; @@ -13,7 +13,7 @@ public interface DataMiningServiceAsync { void getDimensionsFor(DataTypes dataType, AsyncCallback> callback); - void runQuery(QueryDefinition queryDefinition, AsyncCallback> callback); + void runQuery(QueryDefinitionDeprecated queryDefinition, AsyncCallback> callback); /** * This method does nothing, but is needed to ensure, that GenericGroupKey<String> in the GWT serialization policy.
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/GroupingProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/GroupingProvider.java index 419bf891334..800f67090f8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/GroupingProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/GroupingProvider.java @@ -2,8 +2,8 @@ package com.sap.sailing.gwt.ui.datamining.client; import java.util.Collection; -import com.sap.sailing.datamining.shared.QueryDefinition; import com.sap.sailing.datamining.shared.DimensionIdentifier; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sse.datamining.shared.components.GrouperType; @@ -14,7 +14,7 @@ public interface GroupingProvider extends Component { public Collection getDimensionsToGroupBy(); public String getCustomGrouperScriptText(); - public void applyQueryDefinition(QueryDefinition queryDefinition); + public void applyQueryDefinition(QueryDefinitionDeprecated queryDefinition); public void addGroupingChangedListener(GroupingChangedListener listener); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryDefinitionChangedListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryDefinitionChangedListener.java index 8ebae367469..7f0d3beb112 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryDefinitionChangedListener.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryDefinitionChangedListener.java @@ -1,9 +1,9 @@ package com.sap.sailing.gwt.ui.datamining.client; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; public interface QueryDefinitionChangedListener { - void queryDefinitionChanged(QueryDefinition newQueryDefinition); + void queryDefinitionChanged(QueryDefinitionDeprecated newQueryDefinition); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryDefinitionProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryDefinitionProvider.java index 3ca6d80c975..265e071237a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryDefinitionProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryDefinitionProvider.java @@ -1,13 +1,13 @@ package com.sap.sailing.gwt.ui.datamining.client; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.gwt.ui.client.shared.components.Component; public interface QueryDefinitionProvider extends Component { - public Iterable validateQueryDefinition(QueryDefinition queryDefinition); - public QueryDefinition getQueryDefinition(); - public void applyQueryDefinition(QueryDefinition queryDefinition); + public Iterable validateQueryDefinition(QueryDefinitionDeprecated queryDefinition); + public QueryDefinitionDeprecated getQueryDefinition(); + public void applyQueryDefinition(QueryDefinitionDeprecated queryDefinition); public void addQueryDefinitionChangedListener(QueryDefinitionChangedListener listener); public void removeQueryDefinitionChangedListener(QueryDefinitionChangedListener listener); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryRunner.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryRunner.java index eb49e21d441..de6ea600ee9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryRunner.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/QueryRunner.java @@ -1,11 +1,11 @@ package com.sap.sailing.gwt.ui.datamining.client; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.datamining.client.settings.QueryRunnerSettings; public interface QueryRunner extends QueryDefinitionChangedListener, Component { - public void run(QueryDefinition queryDefinition); + public void run(QueryDefinitionDeprecated queryDefinition); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/SelectionProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/SelectionProvider.java index 96feba89d13..309303bd5aa 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/SelectionProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/SelectionProvider.java @@ -3,8 +3,8 @@ package com.sap.sailing.gwt.ui.datamining.client; import java.util.Collection; import java.util.Map; -import com.sap.sailing.datamining.shared.QueryDefinition; import com.sap.sailing.datamining.shared.DimensionIdentifier; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.gwt.ui.client.shared.components.Component; public interface SelectionProvider extends Component { @@ -13,7 +13,7 @@ public interface SelectionProvider extends Component public Map> getSelection(); - public void applySelection(QueryDefinition queryDefinition); + public void applySelection(QueryDefinitionDeprecated queryDefinition); public void clearSelection(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/StatisticProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/StatisticProvider.java index 3680b34687c..124f6b55238 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/StatisticProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/StatisticProvider.java @@ -1,7 +1,7 @@ package com.sap.sailing.gwt.ui.datamining.client; import com.sap.sailing.datamining.shared.DataTypes; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.datamining.shared.StatisticType; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.datamining.client.selection.SimpleStatistic; @@ -16,6 +16,6 @@ public interface StatisticProvider extends Component { public void addStatisticChangedListener(StatisticChangedListener listener); - public void applyQueryDefinition(QueryDefinition queryDefinition); + public void applyQueryDefinition(QueryDefinitionDeprecated queryDefinition); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/execution/SimpleQueryRunner.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/execution/SimpleQueryRunner.java index c275bdb5052..a76eee2a89b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/execution/SimpleQueryRunner.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/execution/SimpleQueryRunner.java @@ -5,7 +5,7 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; @@ -54,7 +54,7 @@ public class SimpleQueryRunner implements QueryRunner { } @Override - public void run(QueryDefinition queryDefinition) { + public void run(QueryDefinitionDeprecated queryDefinition) { Iterable errorMessages = queryDefinitionProvider.validateQueryDefinition(queryDefinition); if (errorMessages == null || !errorMessages.iterator().hasNext()) { // queryStatusLabel.setText(" | " + stringMessages.running()); @@ -89,7 +89,7 @@ public class SimpleQueryRunner implements QueryRunner { } @Override - public void queryDefinitionChanged(QueryDefinition newQueryDefinition) { + public void queryDefinitionChanged(QueryDefinitionDeprecated newQueryDefinition) { run(newQueryDefinition); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/presentation/PlainResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/presentation/PlainResultsPresenter.java new file mode 100644 index 00000000000..837d871a265 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/presentation/PlainResultsPresenter.java @@ -0,0 +1,65 @@ +package com.sap.sailing.gwt.ui.datamining.client.presentation; + +import java.util.Map.Entry; + +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.datamining.client.ResultsPresenter; +import com.sap.sse.datamining.shared.GroupKey; +import com.sap.sse.datamining.shared.QueryResult; + +public class PlainResultsPresenter implements ResultsPresenter { + + private final StringMessages stringMessages; + + private final SimplePanel mainPanel; + private final HTML resultsLabel; + + public PlainResultsPresenter(StringMessages stringMessages) { + this.stringMessages = stringMessages; + + mainPanel = new SimplePanel(); + resultsLabel = new HTML(); + mainPanel.setWidget(resultsLabel); + } + + @Override + public void showResult(QueryResult result) { + StringBuilder resultsBuilder = new StringBuilder("" + result.getResultSignifier() + ""); + resultsBuilder.append(stringMessages.queryResultsChartSubtitle(result.getRetrievedDataAmount(), result.getFilteredDataAmount(), result.getCalculationTimeInSeconds())); + + resultsBuilder.append("
    "); + for (Entry resultEntry : result.getResults().entrySet()) { + resultsBuilder.append("
  • "); + resultsBuilder.append("" + resultEntry.getKey().toString() + ": "); + resultsBuilder.append(resultEntry.getValue().doubleValue()); + resultsBuilder.append("
  • "); + } + resultsBuilder.append("
"); + + resultsLabel.setHTML(resultsBuilder.toString()); + } + + @Override + public void showError(String error) { + resultsLabel.setHTML(error); + } + + @Override + public void showError(String mainError, Iterable detailedErrors) { + StringBuilder errorBuilder = new StringBuilder(mainError + ":
    "); + for (String detailedError : detailedErrors) { + errorBuilder.append("
  • " + detailedError + "
  • "); + } + errorBuilder.append("
"); + showError(errorBuilder.toString()); + } + + @Override + public Widget getWidget() { + return mainPanel; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/presentation/ResultsChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/presentation/ResultsChart.java index 6e135714135..164e8e1a3d0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/presentation/ResultsChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/presentation/ResultsChart.java @@ -59,7 +59,7 @@ public class ResultsChart implements ResultsPresenter { errorLabel.setStyleName("chart-importantMessage"); createChart(); - mainPanel.setWidget(chart); + showError(this.stringMessages.invalidSelection()); } @Override @@ -144,7 +144,6 @@ public class ResultsChart implements ResultsPresenter { // This is needed, so that the subtitle is updated. Otherwise the text would stay empty mainPanel.setWidget(null); mainPanel.setWidget(chart); - } public List getSortedKeysFrom(QueryResult result) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/AbstractQueryDefinitionProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/AbstractQueryDefinitionProvider.java index ec8f1617bf3..6c018654cc3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/AbstractQueryDefinitionProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/AbstractQueryDefinitionProvider.java @@ -5,8 +5,8 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; -import com.sap.sailing.datamining.shared.QueryDefinition; import com.sap.sailing.datamining.shared.DimensionIdentifier; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -34,7 +34,7 @@ public abstract class AbstractQueryDefinitionProvider implements QueryDefinition } @Override - public Iterable validateQueryDefinition(QueryDefinition queryDefinition) { + public Iterable validateQueryDefinition(QueryDefinitionDeprecated queryDefinition) { Collection errorMessages = new ArrayList(); if (queryDefinition != null) { @@ -51,7 +51,7 @@ public abstract class AbstractQueryDefinitionProvider implements QueryDefinition return errorMessages; } - private String validateGrouper(QueryDefinition queryDefinition) { + private String validateGrouper(QueryDefinitionDeprecated queryDefinition) { GrouperType grouper = queryDefinition.getGrouperType(); if (grouper == null) { return stringMessages.noGrouperSelectedError(); @@ -72,7 +72,7 @@ public abstract class AbstractQueryDefinitionProvider implements QueryDefinition return null; } - private String validateStatisticAndAggregator(QueryDefinition queryDefinition) { + private String validateStatisticAndAggregator(QueryDefinitionDeprecated queryDefinition) { return queryDefinition.getStatisticType() == null || queryDefinition.getAggregatorType() == null ? stringMessages.noStatisticSelectedError() : null; } @@ -92,7 +92,7 @@ public abstract class AbstractQueryDefinitionProvider implements QueryDefinition protected void notifyQueryDefinitionChanged() { if (!blockChangeNotification) { - QueryDefinition queryDefinition = getQueryDefinition(); + QueryDefinitionDeprecated queryDefinition = getQueryDefinition(); for (QueryDefinitionChangedListener listener : listeners) { listener.queryDefinitionChanged(queryDefinition); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/ComplexStatisticProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/ComplexStatisticProvider.java index 73072c3a201..1acb7ab9696 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/ComplexStatisticProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/ComplexStatisticProvider.java @@ -11,7 +11,7 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ValueListBox; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.datamining.shared.DataTypes; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.datamining.shared.StatisticType; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; @@ -108,7 +108,7 @@ public class ComplexStatisticProvider implements StatisticProvider { } @Override - public void applyQueryDefinition(QueryDefinition queryDefinition) { + public void applyQueryDefinition(QueryDefinitionDeprecated queryDefinition) { applyStatistic(statisticsManager.getStatistic(queryDefinition.getDataType())); aggregatorListBox.setValue(queryDefinition.getAggregatorType(), false); dataTypeListBox.setValue(queryDefinition.getDataType(), false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/GroupBySelectionPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/GroupBySelectionPanel.java index 7ca31cbe5e2..af38d5aea30 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/GroupBySelectionPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/GroupBySelectionPanel.java @@ -15,7 +15,7 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ValueListBox; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.datamining.shared.DimensionIdentifier; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.datamining.shared.components.GrouperType; @@ -42,7 +42,7 @@ public abstract class GroupBySelectionPanel extends FlowPanel { dimensionsToGroupByBoxes.add(dimensionToGroupByBox); } - public void apply(QueryDefinition queryDefinition) { + public void apply(QueryDefinitionDeprecated queryDefinition) { grouperTypeListBox.setValue(queryDefinition.getGrouperType(), true); switch (queryDefinition.getGrouperType()) { @@ -54,7 +54,7 @@ public abstract class GroupBySelectionPanel extends FlowPanel { } } - private void applyDimensionsToGroupBy(QueryDefinition queryDefinition) { + private void applyDimensionsToGroupBy(QueryDefinitionDeprecated queryDefinition) { int index = 0; for (DimensionIdentifier dimension : queryDefinition.getDimensionsToGroupBy()) { dimensionsToGroupByBoxes.get(index).setValue(dimension, true); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/QueryDefinitionProviderWithControls.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/QueryDefinitionProviderWithControls.java index 74205db1ac6..af47c768b1a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/QueryDefinitionProviderWithControls.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/QueryDefinitionProviderWithControls.java @@ -12,8 +12,8 @@ import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.datamining.shared.DimensionIdentifier; -import com.sap.sailing.datamining.shared.QueryDefinition; -import com.sap.sailing.datamining.shared.impl.SimpleQueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; +import com.sap.sailing.datamining.shared.impl.QueryDefinitionDeprecatedImpl; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -65,8 +65,8 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition } @Override - public QueryDefinition getQueryDefinition() { - SimpleQueryDefinition queryDTO = new SimpleQueryDefinition(LocaleInfo.getCurrentLocale().getLocaleName(), groupBySelectionPanel.getGrouperType(), + public QueryDefinitionDeprecated getQueryDefinition() { + QueryDefinitionDeprecatedImpl queryDTO = new QueryDefinitionDeprecatedImpl(LocaleInfo.getCurrentLocale().getLocaleName(), groupBySelectionPanel.getGrouperType(), statisticProvider.getStatisticType(), statisticProvider.getAggregatorType(), statisticProvider.getDataType()); @@ -87,7 +87,7 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition } @Override - public void applyQueryDefinition(QueryDefinition queryDefinition) { + public void applyQueryDefinition(QueryDefinitionDeprecated queryDefinition) { setBlockChangeNotification(true); selectionProvider.applySelection(queryDefinition); groupBySelectionPanel.applyQueryDefinition(queryDefinition); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/RefreshingSelectionTablesPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/RefreshingSelectionTablesPanel.java index 78f58c4bd4f..a506e751c97 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/RefreshingSelectionTablesPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/RefreshingSelectionTablesPanel.java @@ -23,7 +23,7 @@ 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.sap.sailing.datamining.shared.DimensionIdentifier; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.dto.BoatClassDTO; @@ -139,7 +139,7 @@ public class RefreshingSelectionTablesPanel implements SelectionProvider> selectionEntry : queryDefinition.getSelection().entrySet()) { SelectionTable selectionTable = tablesMappedByDimension.get(selectionEntry.getKey()); selectionTable.setSelection((Iterable) selectionEntry.getValue()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/RestrictedGroupingProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/RestrictedGroupingProvider.java index 9d51d5ebbed..eeb5044ea14 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/RestrictedGroupingProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/RestrictedGroupingProvider.java @@ -14,9 +14,9 @@ import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ValueListBox; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.datamining.shared.QueryDefinition; import com.sap.sailing.datamining.shared.DimensionIdentifier; import com.sap.sailing.datamining.shared.DimensionIdentifier.OrdinalComparator; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sailing.gwt.ui.client.shared.components.SimpleValueListBox; @@ -105,7 +105,7 @@ public class RestrictedGroupingProvider implements GroupingProvider { } @Override - public void applyQueryDefinition(QueryDefinition queryDefinition) { + public void applyQueryDefinition(QueryDefinitionDeprecated queryDefinition) { if (queryDefinition.getGrouperType() == GrouperType.Dimensions) { int index = 0; for (DimensionIdentifier dimension : queryDefinition.getDimensionsToGroupBy()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/SelectionTable.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/SelectionTable.java index 3664a7660b0..eacf0cc25da 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/SelectionTable.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/client/selection/SelectionTable.java @@ -13,7 +13,7 @@ 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.datamining.shared.DimensionIdentifier; -import com.sap.sailing.gwt.ui.client.shared.panels.AbstractFilterablePanel; +import com.sap.sse.gwt.client.panels.AbstractFilterablePanel; public abstract class SelectionTable extends FlowPanel { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractLeaderboardViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractLeaderboardViewer.java index 7efacc69f49..9dd56e6f9ca 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractLeaderboardViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractLeaderboardViewer.java @@ -11,6 +11,7 @@ import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; +import com.sap.sailing.gwt.ui.client.DebugIdHelper; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; @@ -20,8 +21,8 @@ import com.sap.sse.gwt.client.player.Timer; /** * A base class for a leaderboard viewer. + * * @author Frank Mittag (c163874) - * */ public abstract class AbstractLeaderboardViewer extends SimplePanel { protected final StringMessages stringMessages; @@ -49,69 +50,62 @@ public abstract class AbstractLeaderboardViewer extends SimplePanel { protected FlowPanel createViewerPanel() { FlowPanel mainPanel = new FlowPanel(); mainPanel.setSize("100%", "100%"); - getElement().getStyle().setMarginLeft(12, Unit.PX); getElement().getStyle().setMarginRight(12, Unit.PX); - - if(!hideToolbar) { + if (!hideToolbar) { componentsNavigationPanel = new FlowPanel(); componentsNavigationPanel.addStyleName(STYLE_VIEWER_TOOLBAR); mainPanel.add(componentsNavigationPanel); } - return mainPanel; } protected void addComponentToNavigationMenu(final Component component, boolean isCheckboxEnabled, String componentDisplayName, final boolean hasSettingsWhenComponentIsInvisible) { - if(hideToolbar) { - return; - } - - final String componentName = componentDisplayName != null ? componentDisplayName : component.getLocalizedShortName(); - final CheckBox checkBox= new CheckBox(componentName); - final Button settingsButton = new Button(""); - - checkBox.getElement().getStyle().setFloat(Style.Float.LEFT); - - checkBox.setEnabled(isCheckboxEnabled); - checkBox.setValue(component.isVisible()); - checkBox.setTitle(stringMessages.showHideComponent(componentName)); - checkBox.addStyleName(STYLE_VIEWER_TOOLBAR_INNERELEMENT); - - checkBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent newValue) { - boolean visible = checkBox.getValue(); - component.setVisible(visible); - - if (visible && component instanceof TimeListener) { - // trigger the component to update its data - ((TimeListener) component).timeChanged(timer.getTime(), null); - } - if(component.hasSettings() && !hasSettingsWhenComponentIsInvisible) { - settingsButton.setEnabled(visible); - } - } - }); - - componentsNavigationPanel.add(checkBox); - - if(component.hasSettings()) { - settingsButton.addClickHandler(new ClickHandler() { + if (!hideToolbar) { + final String componentName = componentDisplayName != null ? componentDisplayName : component.getLocalizedShortName(); + final String debugIdPrefix = DebugIdHelper.createDebugId(componentName); + final CheckBox checkBox = new CheckBox(componentName); + checkBox.ensureDebugId(debugIdPrefix + "DisplayCheckBox"); + final Button settingsButton = new Button(""); + settingsButton.ensureDebugId(debugIdPrefix + "SettingsButton"); + checkBox.getElement().getStyle().setFloat(Style.Float.LEFT); + checkBox.setEnabled(isCheckboxEnabled); + checkBox.setValue(component.isVisible()); + checkBox.setTitle(stringMessages.showHideComponent(componentName)); + checkBox.addStyleName(STYLE_VIEWER_TOOLBAR_INNERELEMENT); + checkBox.addValueChangeHandler(new ValueChangeHandler() { @Override - public void onClick(ClickEvent event) { - new SettingsDialog(component, stringMessages).show(); - } + public void onValueChange(ValueChangeEvent newValue) { + boolean visible = checkBox.getValue(); + component.setVisible(visible); + if (visible && component instanceof TimeListener) { + // trigger the component to update its data + ((TimeListener) component).timeChanged(timer.getTime(), null); + } + if (component.hasSettings() && !hasSettingsWhenComponentIsInvisible) { + settingsButton.setEnabled(visible); + } + } }); - } - - settingsButton.setEnabled(component.hasSettings() && hasSettingsWhenComponentIsInvisible); - settingsButton.addStyleName(STYLE_VIEWER_TOOLBAR_SETTINGS_BUTTON); - settingsButton.getElement().getStyle().setFloat(Style.Float.LEFT); - settingsButton.setTitle(stringMessages.settingsForComponent(componentName)); - - componentsNavigationPanel.add(settingsButton); + componentsNavigationPanel.add(checkBox); + if (component.hasSettings()) { + settingsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + SettingsDialog dialog = new SettingsDialog(component, + stringMessages); + dialog.ensureDebugId(debugIdPrefix + "SettingsDialog"); + dialog.show(); + } + }); + } + settingsButton.setEnabled(component.hasSettings() && hasSettingsWhenComponentIsInvisible); + settingsButton.addStyleName(STYLE_VIEWER_TOOLBAR_SETTINGS_BUTTON); + settingsButton.getElement().getStyle().setFloat(Style.Float.LEFT); + settingsButton.setTitle(stringMessages.settingsForComponent(componentName)); + componentsNavigationPanel.add(settingsButton); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractRaceColumnSelection.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractRaceColumnSelection.java index 9a066ca21c7..fde8e840039 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractRaceColumnSelection.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractRaceColumnSelection.java @@ -5,7 +5,7 @@ import java.util.List; import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sse.common.Util; public abstract class AbstractRaceColumnSelection implements RaceColumnSelection { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DetailTypeColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DetailTypeColumn.java index 83905157283..5ad8712e6cb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DetailTypeColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DetailTypeColumn.java @@ -25,8 +25,9 @@ public abstract class DetailTypeColumn, Renderin T get(LeaderboardRowDTO row); } - protected DetailTypeColumn(DetailType detailType, LegDetailField field, Cell cell, String headerStyle, String columnStyle) { - super(cell, detailType.getDefaultSortingOrder()); + protected DetailTypeColumn(DetailType detailType, LegDetailField field, Cell cell, + String headerStyle, String columnStyle, DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { + super(cell, detailType.getDefaultSortingOrder(), displayedLeaderboardRowsProvider); setHorizontalAlignment(ALIGN_CENTER); this.title = DetailTypeFormatter.format(detailType); this.unit = DetailTypeFormatter.getUnit(detailType).isEmpty() ? "" : "[" + DetailTypeFormatter.getUnit(detailType) + "]"; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DialogBoxExt.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DialogBoxExt.java index 4abd972eb15..80c191d8e72 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DialogBoxExt.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DialogBoxExt.java @@ -1,9 +1,9 @@ package com.sap.sailing.gwt.ui.leaderboard; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.safehtml.shared.SafeHtml; -import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.HTML; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DisplayedLeaderboardRowsProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DisplayedLeaderboardRowsProvider.java new file mode 100755 index 00000000000..6bc5947128f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DisplayedLeaderboardRowsProvider.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.ui.leaderboard; + +import java.util.Collection; + +import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; +import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; + +/** + * A {@link LeaderboardPanel} does not always display all rows available. One reason for filtering can be a filter set + * explicitly by the user. Another can be that the leaderboard panel is configured to only show entries for one + * particular race. Other UI controls that need to know which rows are being shown shall not need to see the entire + * {@link LeaderboardPanel} interface. Therefore, this interface provides the capabilities needed. + * + * @author Axel Uhl (D043530) + * + */ +public interface DisplayedLeaderboardRowsProvider { + /** + * Extracts the rows to display of the leaderboard. These are all {@link AbstractLeaderboardDTO#rows + * rows} in case {@link #preSelectedRace} is null, or only the rows of the competitors who scored in + * the race identified by {@link #preSelectedRace} otherwise. + */ + public Collection getRowsToDisplay(); +} 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 ca2f0990a75..a27387f278f 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 @@ -48,8 +48,8 @@ public abstract class ExpandableSortableColumn extends SortableColumn cell, SortingOrder preferredSortingOrder, StringMessages stringConstants, String detailHeaderStyle, String detailColumnStyle, - List detailSelection) { - super(cell, preferredSortingOrder); + List detailSelection, DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { + super(cell, preferredSortingOrder, displayedLeaderboardRowsProvider); this.enableExpansion = enableExpansion; this.leaderboardPanel = leaderboardPanel; this.detailSelection = detailSelection; @@ -148,6 +148,7 @@ public abstract class ExpandableSortableColumn extends SortableColumn table = getLeaderboardPanel().getLeaderboardTable(); @@ -170,12 +171,12 @@ public abstract class ExpandableSortableColumn extends SortableColumn column : getAllVisibleChildren()) { - column.updateMinMax(getLeaderboardPanel().getLeaderboard()); + column.updateMinMax(); getLeaderboardPanel().insertColumn(insertIndex++, column); } getLeaderboardPanel().getLeaderboardTable().redraw(); } - getLeaderboardPanel().getBusyIndicator().setBusy(false); + getLeaderboardPanel().getBusyIndicator().setBusy(oldBusyState); setTogglingInProcess(false); } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleDetailTypeColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleDetailTypeColumn.java index d4fbc480414..689671b9b52 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleDetailTypeColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleDetailTypeColumn.java @@ -5,7 +5,6 @@ import com.google.gwt.cell.client.TextCell; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.sap.sailing.domain.common.DetailType; -import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; @@ -17,9 +16,11 @@ public class FormattedDoubleDetailTypeColumn extends DetailTypeColumn field, String headerStyle, String columnStyle) { - super(detailType, field, new TextCell(), headerStyle, columnStyle); + public FormattedDoubleDetailTypeColumn(DetailType detailType, LegDetailField field, String headerStyle, + String columnStyle, DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { + super(detailType, field, new TextCell(), headerStyle, columnStyle, displayedLeaderboardRowsProvider); formatter = createNumberFormatter(detailType); this.minMaxRenderer = createMinMaxRenderer(); } @@ -37,8 +38,8 @@ public class FormattedDoubleDetailTypeColumn extends DetailTypeColumn>() { - @Override - public void onSuccess(Pair leaderboardNameAndType) { - if (leaderboardNameAndType != null && leaderboardName.equals(leaderboardNameAndType.getA())) { - Window.setTitle(leaderboardName); - leaderboardType = leaderboardNameAndType.getB(); - createUI(showRaceDetails, embedded, hideToolbar); - } else { - RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard())); - } - } - - @Override - public void onFailure(Throwable t) { - reportError("Error trying to obtain list of leaderboard names: " + t.getMessage()); - } - }); + sailingService.checkLeaderboardName(leaderboardName, new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onSuccess(Util.Pair leaderboardNameAndType) { + if (leaderboardNameAndType != null && leaderboardName.equals(leaderboardNameAndType.getA())) { + Window.setTitle(leaderboardName); + leaderboardType = leaderboardNameAndType.getB(); + createUI(showRaceDetails, embedded, hideToolbar); + } else { + RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard())); + } + } + + @Override + public void onFailure(Throwable t) { + reportError("Error trying to obtain list of leaderboard names: " + t.getMessage()); + } + })); } else { RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard())); } @@ -152,33 +156,34 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { ScrollPanel contentScrollPanel = new ScrollPanel(); long delayBetweenAutoAdvancesInMilliseconds = 3000l; final RaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); - Timer timer = new Timer(PlayModes.Replay, delayBetweenAutoAdvancesInMilliseconds); + // make a single live request as the default but don't continue to play by default + Timer timer = new Timer(PlayModes.Live, PlayStates.Paused, delayBetweenAutoAdvancesInMilliseconds); final LeaderboardSettings leaderboardSettings = createLeaderboardSettingsFromURLParameters(Window.Location.getParameterMap()); if (leaderboardSettings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { - timer.setPlayMode(PlayModes.Live); // the leaderboard, viewed via the entry point, always goes "live" - } + timer.setPlayMode(PlayModes.Live); // the leaderboard, viewed via the entry point, goes "live" and "playing" if an auto-refresh + } // interval has been specified boolean autoExpandLastRaceColumn = GwtHttpRequestUtils.getBooleanParameter(PARAM_AUTO_EXPAND_LAST_RACE_COLUMN, false); boolean showCharts = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_CHARTS, false); boolean showOverallLeaderboard = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_OVERALL_LEADERBOARD, false); boolean showSeriesLeaderboards = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_SERIES_LEADERBOARDS, false); String chartDetailParam = GwtHttpRequestUtils.getStringParameter(PARAM_CHART_DETAIL, null); DetailType chartDetailType; - if(chartDetailParam != null && (DetailType.REGATTA_RANK.name().equals(chartDetailParam) || DetailType.OVERALL_RANK.name().equals(chartDetailParam) || + if (chartDetailParam != null && (DetailType.REGATTA_RANK.name().equals(chartDetailParam) || DetailType.OVERALL_RANK.name().equals(chartDetailParam) || DetailType.REGATTA_TOTAL_POINTS.name().equals(chartDetailParam))) { chartDetailType = DetailType.valueOf(chartDetailParam); } else { chartDetailType = leaderboardType.isMetaLeaderboard() ? DetailType.OVERALL_RANK : DetailType.REGATTA_RANK; } - Widget leaderboardViewer; + final Widget leaderboardViewer; if (leaderboardType.isMetaLeaderboard()) { leaderboardViewer = new MetaLeaderboardViewer(sailingService, new AsyncActionsExecutor(), - leaderboardSettings, null, preselectedRace, leaderboardGroupName, leaderboardName, this, stringMessages, userAgent, - showRaceDetails, hideToolbar, autoExpandLastRaceColumn, showCharts, chartDetailType, showSeriesLeaderboards); + timer, leaderboardSettings, null, preselectedRace, leaderboardGroupName, leaderboardName, this, stringMessages, + userAgent, showRaceDetails, hideToolbar, autoExpandLastRaceColumn, showCharts, chartDetailType, showSeriesLeaderboards); } else { leaderboardViewer = new LeaderboardViewer(sailingService, new AsyncActionsExecutor(), - leaderboardSettings, preselectedRace, leaderboardGroupName, leaderboardName, this, stringMessages, userAgent, - showRaceDetails, hideToolbar, autoExpandLastRaceColumn, showCharts, chartDetailType, showOverallLeaderboard); + timer, leaderboardSettings, preselectedRace, leaderboardGroupName, leaderboardName, this, stringMessages, + userAgent, showRaceDetails, hideToolbar, autoExpandLastRaceColumn, showCharts, chartDetailType, showOverallLeaderboard); } contentScrollPanel.setWidget(leaderboardViewer); mainPanel.add(contentScrollPanel); 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 81102f1937b..725f1ae2049 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 @@ -66,8 +66,6 @@ import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.domain.common.dto.LegEntryDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.actions.GetLeaderboardByNameAction; import com.sap.sailing.gwt.ui.client.Collator; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; @@ -85,7 +83,9 @@ import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher; import com.sap.sailing.gwt.ui.leaderboard.DetailTypeColumn.LegDetailField; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; import com.sap.sse.gwt.client.player.PlayStateListener; @@ -101,7 +101,7 @@ import com.sap.sse.gwt.client.useragent.UserAgentDetails; * @author Axel Uhl (D043530) * */ -public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayStateListener, +public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayStateListener, DisplayedLeaderboardRowsProvider, Component, IsEmbeddableComponent, CompetitorSelectionChangeListener, LeaderboardFetcher { public static final String LOAD_LEADERBOARD_DATA_CATEGORY = "loadLeaderboardData"; private static final int RANK_COLUMN_INDEX = 0; @@ -134,7 +134,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS /** * The leaderboard name is used to - * {@link SailingServiceAsync#getLeaderboardByName(String, java.util.Date, String[], String, com.google.gwt.user.client.rpc.AsyncCallback) + * {@link SailingServiceAsync#getLeaderboardByName(String, java.util.Date, String[], boolean, String, com.google.gwt.user.client.rpc.AsyncCallback) * obtain the leaderboard contents} from the server. It may change in case the leaderboard is renamed. */ private String leaderboardName; @@ -334,12 +334,17 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS return resources.settingsIcon(); } - public void updateSettings(LeaderboardSettings newSettings) { + public void updateSettings(final LeaderboardSettings newSettings) { + boolean oldShallAddOverallDetails = shallAddOverallDetails(); + if (newSettings.getOverallDetailsToShow() != null) { + selectedOverallDetailColumns.clear(); + selectedOverallDetailColumns.addAll(newSettings.getOverallDetailsToShow()); + } if (!newSettings.isUpdateUponPlayStateChange() || !currentlyHandlingPlayStateChange) { settingsUpdatedExplicitly = true; } setShowAddedScores(newSettings.isShowAddedScores()); - List> columnsToExpandAgain = new ArrayList>(); + final List> columnsToExpandAgain = new ArrayList>(); for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { Column c = getLeaderboardTable().getColumn(i); if (c instanceof ExpandableSortableColumn) { @@ -365,10 +370,6 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS selectedRaceDetails.clear(); selectedRaceDetails.addAll(newSettings.getRaceDetailsToShow()); } - if (newSettings.getOverallDetailsToShow() != null) { - selectedOverallDetailColumns.clear(); - selectedOverallDetailColumns.addAll(newSettings.getOverallDetailsToShow()); - } // update strategy for determining the race columns to show; if settings' race columns to show is null, use the // previously selected race columns / number of race columns for the new configuration Iterable oldNamesOfRaceColumnsToShow = null; @@ -413,26 +414,66 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS setRaceColumnSelectionToLastNStrategy(newSettings.getNumberOfLastRacesToShow()); break; } - setAutoExpandPreSelectedRace(false); // avoid expansion during updateLeaderboard(...); will expand later if it - // was expanded before - // update leaderboard after settings panel column selection change - updateLeaderboard(leaderboard); - setAutoExpandPreSelectedRace(newSettings.isAutoExpandPreSelectedRace()); + + final boolean oldBusyState = getBusyIndicator().isBusy(); + getBusyIndicator().setBusy(true); + Runnable doWhenNecessaryDetailHasBeenLoaded = new Runnable() { + @Override + public void run() { + setAutoExpandPreSelectedRace(false); // avoid expansion during updateLeaderboard(...); will expand later + // if it was expanded before + // update leaderboard after settings panel column selection change + updateLeaderboard(leaderboard); + setAutoExpandPreSelectedRace(newSettings.isAutoExpandPreSelectedRace()); - if (newSettings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { - timer.setRefreshInterval(newSettings.getDelayBetweenAutoAdvancesInMilliseconds()); - } - for (ExpandableSortableColumn expandableSortableColumn : columnsToExpandAgain) { - expandableSortableColumn.toggleExpansion(); - } - if (newSettings.getNameOfRaceToSort() != null) { - final RaceColumn raceColumnByRaceName = getRaceColumnByRaceName(newSettings.getNameOfRaceToSort()); - if (raceColumnByRaceName != null) { - getLeaderboardTable().sortColumn(raceColumnByRaceName, /* ascending */true); + if (newSettings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { + timer.setRefreshInterval(newSettings.getDelayBetweenAutoAdvancesInMilliseconds()); + } + for (ExpandableSortableColumn expandableSortableColumn : columnsToExpandAgain) { + expandableSortableColumn.toggleExpansion(); + } + if (newSettings.getNameOfRaceToSort() != null) { + final RaceColumn raceColumnByRaceName = getRaceColumnByRaceName(newSettings + .getNameOfRaceToSort()); + if (raceColumnByRaceName != null) { + getLeaderboardTable().sortColumn(raceColumnByRaceName, /* ascending */true); + } + } + getBusyIndicator().setBusy(oldBusyState); } + }; + if (oldShallAddOverallDetails == shallAddOverallDetails() || oldShallAddOverallDetails || getLeaderboard().hasOverallDetails()) { + doWhenNecessaryDetailHasBeenLoaded.run(); + } else { // meaning that now the details need to be loaded from the server + updateLeaderboardAndRun(doWhenNecessaryDetailHasBeenLoaded); } } + /** + * @param callWhenExpansionDataIsLoaded + */ + private void updateLeaderboardAndRun(final Runnable callWhenExpansionDataIsLoaded) { + final LeaderboardDTO previousLeaderboard = getLeaderboard(); + getSailingService().getLeaderboardByName(getLeaderboardName(), + timer.getPlayMode() == PlayModes.Live ? null : getLeaderboardDisplayDate(), + /* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), + shallAddOverallDetails(), previousLeaderboard.getId(), new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onSuccess(IncrementalOrFullLeaderboardDTO result) { + updateLeaderboard(result.getLeaderboardDTO(previousLeaderboard)); + callWhenExpansionDataIsLoaded.run(); + } + + @Override + public void onFailure(Throwable caught) { + getErrorReporter().reportError( + stringMessages.errorTryingToObtainLeaderboardContents(caught.getMessage()), + true /* silentMode */); + } + })); + } + private void setRaceColumnSelectionToLastNStrategy(final Integer numberOfLastRacesToShow) { raceColumnSelection = new LastNRacesColumnSelection(numberOfLastRacesToShow, getRaceTimesInfoProvider()); if (timer.getPlayState() != Timer.PlayStates.Playing) { @@ -479,12 +520,12 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private final CompetitorColumnBase base; protected CompetitorColumn(CompetitorColumnBase base) { - super(base.getCell(getLeaderboard()), SortingOrder.ASCENDING); + super(base.getCell(getLeaderboard()), SortingOrder.ASCENDING, LeaderboardPanel.this); this.base = base; } public CompetitorColumn(CompositeCell compositeCell, CompetitorColumnBase base) { - super(compositeCell, SortingOrder.ASCENDING); + super(compositeCell, SortingOrder.ASCENDING, LeaderboardPanel.this); this.base = base; } @@ -536,7 +577,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private final CompetitorFetcher competitorFetcher; protected SailIDColumn(CompetitorFetcher competitorFetcher) { - super(new TextCell(), SortingOrder.ASCENDING); + super(new TextCell(), SortingOrder.ASCENDING, LeaderboardPanel.this); this.competitorFetcher = competitorFetcher; } @@ -596,7 +637,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS public RaceColumn(RaceColumnDTO race, boolean enableExpansion, Cell cell, SortingOrder preferredSortingOrder, String headerStyle, String columnStyle) { super(LeaderboardPanel.this, enableExpansion, cell, preferredSortingOrder, stringMessages, - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, selectedRaceDetails); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, selectedRaceDetails, LeaderboardPanel.this); setHorizontalAlignment(ALIGN_CENTER); this.race = race; this.headerStyle = headerStyle; @@ -637,7 +678,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS LeaderboardEntryDTO entryBefore = object.fieldsByRaceColumnName.get(raceColumn.getName()); if (entryBefore.totalPoints != null) { addedScores += entryBefore.totalPoints; - } + } if (raceColumn.getName().equals(getRaceColumnName())) { break; // we've reached the current column - stop here } @@ -665,9 +706,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS boolean isLive = isLive(entry.fleet); String textColor = isLive ? IS_LIVE_TEXT_COLOR : DEFAULT_TEXT_COLOR; - String totalOrAddedPointsAsText = isShowAddedScores() ? scoreFormat.format(computeAddedScores(object)) + String totalOrAddedPointsAsText = isShowAddedScores() ? (entry.totalPoints != null ? scoreFormat.format(computeAddedScores(object)) : "") : entry.totalPoints == null ? "" : scoreFormat.format(entry.totalPoints); - String netOrAddedPointsAsText = isShowAddedScores() ? scoreFormat.format(computeAddedScores(object)) + String netOrAddedPointsAsText = isShowAddedScores() ? (entry.netPoints != null ? scoreFormat.format(computeAddedScores(object)) : "") : entry.netPoints == null ? "" : scoreFormat.format(entry.netPoints); if (entry.fleet != null && entry.fleet.getColor() != null) { @@ -711,7 +752,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS if (isShowAddedScores()) { double o1AddedScore = computeAddedScores(o1); double o2AddedScore = computeAddedScores(o2); - double result = o1AddedScore == 0. ? o2AddedScore == 0. ? 0. : isAscending() ? 1. : -1. : o2AddedScore == 0. ? isAscending() ? -1. : 1. : o1AddedScore - o2AddedScore; + double result = o1AddedScore == 0. ? o2AddedScore == 0. ? 0. : isAscending() ? 1. : -1. : o2AddedScore == 0. ? isAscending() ? 1. : -1. : o2AddedScore - o1AddedScore; return result > 0 ? 1 : result < 0 ? -1 : 0; } else { List competitorsFromBestToWorst = getLeaderboard().getCompetitorsFromBestToWorst( @@ -745,7 +786,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS DetailType.RACE_TIME_TRAVELED, DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS, DetailType.NUMBER_OF_MANEUVERS, DetailType.DISPLAY_LEGS, DetailType.CURRENT_LEG, - DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS, + DetailType.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS, + DetailType.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS, DetailType.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START, DetailType.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START, DetailType.DISTANCE_TO_START_AT_RACE_START, DetailType.SPEED_OVER_GROUND_AT_RACE_START, DetailType.SPEED_OVER_GROUND_WHEN_PASSING_START, @@ -786,24 +828,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS if (getLeaderboard().getLegCount(getRaceColumnName()) != -1) { callWhenExpansionDataIsLoaded.run(); } else { - final LeaderboardDTO previousLeaderboard = getLeaderboard(); - getSailingService().getLeaderboardByName(getLeaderboardName(), - timer.getPlayMode() == PlayModes.Live ? null : getLeaderboardDisplayDate(), - /* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), - previousLeaderboard.getId(), new AsyncCallback() { - @Override - public void onSuccess(IncrementalOrFullLeaderboardDTO result) { - updateLeaderboard(result.getLeaderboardDTO(previousLeaderboard)); - callWhenExpansionDataIsLoaded.run(); - } - - @Override - public void onFailure(Throwable caught) { - getErrorReporter().reportError( - stringMessages.errorTryingToObtainLeaderboardContents(caught.getMessage()), - true /* silentMode */); - } - }); + updateLeaderboardAndRun(callWhenExpansionDataIsLoaded); } } @@ -814,47 +839,50 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS Map> result = new HashMap>(); result.put(DetailType.RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL, new TimeSinceLastGpsFixColumn(DetailType.RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL, - new RaceRatioBetweenTimeSinceLastPositionFixAndAverageSamplingInterval(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + new RaceRatioBetweenTimeSinceLastPositionFixAndAverageSamplingInterval(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.RACE_DISTANCE_TRAVELED, new FormattedDoubleDetailTypeColumn(DetailType.RACE_DISTANCE_TRAVELED, new RaceDistanceTraveledInMeters(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, new FormattedDoubleDetailTypeColumn(DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, new RaceAverageSpeedInKnots(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, new FormattedDoubleDetailTypeColumn(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, new RaceGapToLeaderInSeconds(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, new FormattedDoubleDetailTypeColumn(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, new CurrentSpeedOverGroundInKnots(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS, new FormattedDoubleDetailTypeColumn(DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS, new RaceDistanceToLeaderInMeters(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); - result.put(DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS, - new FormattedDoubleDetailTypeColumn(DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS, new RaceAverageCrossTrackErrorInMeters(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); + result.put(DetailType.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS, + new FormattedDoubleDetailTypeColumn(DetailType.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS, new RaceAverageAbsoluteCrossTrackErrorInMeters(), + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); + result.put(DetailType.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS, + new FormattedDoubleDetailTypeColumn(DetailType.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS, new RaceAverageSignedCrossTrackErrorInMeters(), + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START, new FormattedDoubleDetailTypeColumn(DetailType.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START, new DistanceToStartFiveSecondsBeforeStartInMeters(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START, new FormattedDoubleDetailTypeColumn(DetailType.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START, new SpeedFiveSecondsBeforeStartInKnots(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.DISTANCE_TO_START_AT_RACE_START, new FormattedDoubleDetailTypeColumn(DetailType.DISTANCE_TO_START_AT_RACE_START, new DistanceToStartAtRaceStartInMeters(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.SPEED_OVER_GROUND_AT_RACE_START, new FormattedDoubleDetailTypeColumn(DetailType.SPEED_OVER_GROUND_AT_RACE_START, new SpeedOverGroundAtRaceStartInKnots(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.SPEED_OVER_GROUND_WHEN_PASSING_START, new FormattedDoubleDetailTypeColumn(DetailType.SPEED_OVER_GROUND_WHEN_PASSING_START, new SpeedOverGroundWhenPassingStartInKnots(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS, new FormattedDoubleDetailTypeColumn(DetailType.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS, new DistanceToStarboardSideOfStartLineInMeters(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.START_TACK, new StartingTackColumn(new TackWhenStarting(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); result.put(DetailType.NUMBER_OF_MANEUVERS, getManeuverCountRaceColumn()); result.put(DetailType.CURRENT_LEG, - new FormattedDoubleDetailTypeColumn(DetailType.CURRENT_LEG, new CurrentLeg(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + new FormattedDoubleDetailTypeColumn(DetailType.CURRENT_LEG, new CurrentLeg(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); result.put(DetailType.RACE_TIME_TRAVELED, getTimeTraveledRaceColumn()); return result; @@ -868,7 +896,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private ManeuverCountRaceColumn getManeuverCountRaceColumn() { return new ManeuverCountRaceColumn(getLeaderboardPanel(), this, stringMessages, LeaderboardPanel.this.selectedManeuverDetails, LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, - LEG_DETAIL_COLUMN_HEADER_STYLE, LEG_DETAIL_COLUMN_STYLE); + LEG_DETAIL_COLUMN_HEADER_STYLE, LEG_DETAIL_COLUMN_STYLE, LeaderboardPanel.this); } @Override @@ -970,17 +998,34 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } /** - * Fetches the average cross-track error for the race + * Fetches the average absolute (distance always counted as positive, no matter whether left or right) cross-track error for the race * * @author Axel Uhl (D043530) */ - private class RaceAverageCrossTrackErrorInMeters implements LegDetailField { + private class RaceAverageAbsoluteCrossTrackErrorInMeters implements LegDetailField { @Override public Double get(LeaderboardRowDTO row) { Double result = null; LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); if (fieldsForRace != null) { - result = fieldsForRace.averageCrossTrackErrorInMeters; + result = fieldsForRace.averageAbsoluteCrossTrackErrorInMeters; + } + return result; + } + } + + /** + * Fetches the average signed (right course side is positive, left course side is negative) cross-track error for the race + * + * @author Axel Uhl (D043530) + */ + private class RaceAverageSignedCrossTrackErrorInMeters implements LegDetailField { + @Override + public Double get(LeaderboardRowDTO row) { + Double result = null; + LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); + if (fieldsForRace != null) { + result = fieldsForRace.averageSignedCrossTrackErrorInMeters; } return result; } @@ -1227,7 +1272,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private final String columnStyle; protected TotalsColumn(String columnStyle) { - super(new TextCell(), SortingOrder.ASCENDING); + super(new TextCell(), SortingOrder.ASCENDING, LeaderboardPanel.this); this.columnStyle = columnStyle; setHorizontalAlignment(ALIGN_CENTER); } @@ -1271,12 +1316,12 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS protected class CarryColumn extends SortableColumn { public CarryColumn() { - super(new TextCell(), SortingOrder.ASCENDING); + super(new TextCell(), SortingOrder.ASCENDING, LeaderboardPanel.this); setSortable(true); } protected CarryColumn(EditTextCell editTextCell) { - super(editTextCell, SortingOrder.ASCENDING); + super(editTextCell, SortingOrder.ASCENDING, LeaderboardPanel.this); setSortable(true); } @@ -1311,7 +1356,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private class TotalRankColumn extends SortableColumn { public TotalRankColumn() { - super(new TextCell(), SortingOrder.ASCENDING); + super(new TextCell(), SortingOrder.ASCENDING, LeaderboardPanel.this); setHorizontalAlignment(ALIGN_CENTER); setSortable(true); } @@ -1342,7 +1387,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private class StartingTackColumn extends DetailTypeColumn { public StartingTackColumn(LegDetailField field, String headerStyle, String columnStyle) { - super(DetailType.START_TACK, field, new TextCell(), headerStyle, columnStyle); + super(DetailType.START_TACK, field, new TextCell(), headerStyle, columnStyle, LeaderboardPanel.this); } @Override @@ -1365,7 +1410,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS String leaderboardName, ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails) { this(sailingService, asyncActionsExecutor, settings, preSelectedRace, competitorSelectionProvider, new Timer( - PlayModes.Replay, /* delayBetweenAutoAdvancesInMilliseconds */3000l), leaderboardGroupName, + // perform the first request as "live" but don't by default auto-play + PlayModes.Live, PlayStates.Paused, /* delayBetweenAutoAdvancesInMilliseconds */3000l), leaderboardGroupName, leaderboardName, errorReporter, stringMessages, userAgent, showRaceDetails, /* optionalRaceTimesInfoProvider */ null, /* autoExpandLastRaceColumn */ false, /* adjustTimerDelay */ true); } @@ -1435,7 +1481,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS TOTAL_COLUMN_STYLE = tableResources.cellTableStyle().cellTableTotalColumn(); leaderboardTable = new SortedCellTableWithStylableHeaders( /* pageSize */10000, tableResources); - leaderboardTable.ensureDebugId("LeaderboardTable"); + leaderboardTable.ensureDebugId("LeaderboardCellTable"); getLeaderboardTable().setWidth("100%"); leaderboardSelectionModel = new MultiSelectionModel(); leaderboardSelectionModel.addSelectionChangeHandler(new Handler() { @@ -1458,7 +1504,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS leaderboardTable.setSelectionModel(leaderboardSelectionModel); } setShowAddedScores(settings.isShowAddedScores()); - loadCompleteLeaderboard(getLeaderboardDisplayDate()); + if (timer.isInitialized()) { + loadCompleteLeaderboard(getLeaderboardDisplayDate()); + } if (this.preSelectedRace == null) { isEmbedded = false; @@ -1470,7 +1518,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS // the information panel informationPanel = new FlowPanel(); - informationPanel.setStyleName(STYLE_LEADERBOARD_INFO); + informationPanel.setStyleName(STYLE_LEADERBOARD_INFO); scoreCorrectionLastUpdateTimeLabel = new Label(""); scoreCorrectionCommentLabel = new Label(""); informationPanel.add(scoreCorrectionCommentLabel); @@ -1485,8 +1533,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS // the toolbar panel DockPanel toolbarPanel = new DockPanel(); - toolbarPanel.setStyleName(STYLE_LEADERBOARD_TOOLBAR); + toolbarPanel.ensureDebugId("ToolbarPanel"); + toolbarPanel.setStyleName(STYLE_LEADERBOARD_TOOLBAR); busyIndicator = new SimpleBusyIndicator(false, 0.8f); + busyIndicator.ensureDebugId("BusyIndicator"); if (!isEmbedded) { toolbarPanel.add(informationPanel, DockPanel.WEST); toolbarPanel.add(busyIndicator, DockPanel.WEST); @@ -1507,12 +1557,14 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS pauseIcon = resources.autoRefreshEnabledIcon(); playIcon = resources.autoRefreshDisabledIcon(); refreshAndSettingsPanel = new HorizontalPanel(); + refreshAndSettingsPanel.ensureDebugId("RefreshAndSettingsPanel"); refreshAndSettingsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); FlowPanel refreshPanel = new FlowPanel(); refreshPanel.addStyleName("refreshPanel"); toolbarPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT); toolbarPanel.addStyleName("refreshAndSettings"); playPause = new Anchor(getPlayPauseImgHtml(timer.getPlayState())); + playPause.ensureDebugId("PlayAndPauseAnchor"); playPause.addClickHandler(playPauseHandler); playStateChanged(timer.getPlayState(), timer.getPlayMode()); refreshPanel.add(playPause); @@ -1551,14 +1603,14 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS Map> result = new HashMap>(); result.put(DetailType.TOTAL_DISTANCE_TRAVELED, new FormattedDoubleDetailTypeColumn(DetailType.TOTAL_DISTANCE_TRAVELED, - new TotalDistanceTraveledInMetersField(), RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); + new TotalDistanceTraveledInMetersField(), RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE, this)); result.put(DetailType.TOTAL_AVERAGE_SPEED_OVER_GROUND, new FormattedDoubleDetailTypeColumn(DetailType.TOTAL_AVERAGE_SPEED_OVER_GROUND, - new TotalAverageSpeedOverGroundField(), RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); + new TotalAverageSpeedOverGroundField(), RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE, this)); result.put(DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS, new MaxSpeedOverallColumn(RACE_COLUMN_HEADER_STYLE, - RACE_COLUMN_STYLE)); + RACE_COLUMN_STYLE, this)); result.put(DetailType.TOTAL_TIME_SAILED_IN_SECONDS, createOverallTimeTraveledColumn()); return result; @@ -1638,7 +1690,16 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } private void setDelayInMilliseconds(long delayInMilliseconds) { + // If the timer is currently in a mode that causes null to be used as the leaderboard request time stamp, + // don't let a live play delay change cause another load request by unregistering this panel as timer + // listener before and re-registering it after adjusting the live play delay. + if (useNullAsTimePoint()) { + timer.removeTimeListener(this); + } timer.setLivePlayDelayInMillis(delayInMilliseconds); + if (useNullAsTimePoint()) { + timer.addTimeListener(this); + } } private boolean isAutoExpandPreSelectedRace() { @@ -1747,10 +1808,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private void loadCompleteLeaderboard(Date date) { if (needsDataLoading()) { GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(sailingService, - getLeaderboardName(), timer.getPlayMode() == PlayModes.Live ? null : date, + getLeaderboardName(), useNullAsTimePoint() ? null : date, /* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), - /* previousLeaderboard */ getLeaderboard(), timer, errorReporter, stringMessages); - asyncActionsExecutor.execute(getLeaderboardByNameAction, LOAD_LEADERBOARD_DATA_CATEGORY, + shallAddOverallDetails(), /* previousLeaderboard */ getLeaderboard(), timer, errorReporter, stringMessages); + this.asyncActionsExecutor.execute(getLeaderboardByNameAction, LOAD_LEADERBOARD_DATA_CATEGORY, new AsyncCallback() { @Override public void onSuccess(LeaderboardDTO result) { @@ -1771,6 +1832,16 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } } + /** + * In {@link PlayModes#Live live mode}, when {@link #loadCompleteLeaderboard(Date) loading the leaderboard contents}, null + * is used as time point. The condition for this is encapsulated in this method so others can find out. For example, when a time change + * is signaled due to local offset / delay adjustments, no additional call to {@link #loadCompleteLeaderboard(Date)} would be required + * as null will be passed in any case, not being affected by local time offsets. + */ + private boolean useNullAsTimePoint() { + return timer.getPlayMode() == PlayModes.Live; + } + private boolean needsDataLoading() { return isVisible(); } @@ -1792,6 +1863,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS return namesOfExpandedRaces; } + private boolean shallAddOverallDetails() { + return !selectedOverallDetailColumns.isEmpty(); + } + /** * Assigns leaderboard to {@link #leaderboard} and updates the UI accordingly. Also updates the min/max * values on the columns. @@ -1812,7 +1887,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } adjustDelayToLive(); getData().getList().clear(); - getData().getList().addAll(getRowsToDisplay(leaderboard)); + getData().getList().addAll(getRowsToDisplay()); RaceColumn lastRaceColumn = null; for (int i=getLeaderboardTable().getColumnCount()-1; i>=0; i--) { if (getLeaderboardTable().getColumn(i) instanceof RaceColumn) { @@ -1822,7 +1897,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { SortableColumn c = (SortableColumn) getLeaderboardTable().getColumn(i); - c.updateMinMax(leaderboard); + c.updateMinMax(); // Toggle pre-selected race, if the setting is set and it isn't open yet, or the last race column if that was requested if ((!autoExpandPerformedOnce && isAutoExpandPreSelectedRace() && c instanceof RaceColumn && ((RaceColumn) c).getRace().hasTrackedRace(preSelectedRace)) || @@ -1858,16 +1933,16 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS scoreCorrectionLastUpdateTimeLabel.setText(""); } - List> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis()); + List> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis()); boolean hasLiveRace = !liveRaces.isEmpty(); if (hasLiveRace) { String liveRaceText = ""; if(liveRaces.size() == 1) { - Pair liveRace = liveRaces.get(0); + com.sap.sse.common.Util.Pair liveRace = liveRaces.get(0); liveRaceText = stringMessages.raceIsLive("'" + liveRace.getA().getRaceColumnName() + "'"); } else { String raceNames = ""; - for(Pair liveRace: liveRaces) { + for(com.sap.sse.common.Util.Pair liveRace: liveRaces) { raceNames += "'" + liveRace.getA().getRaceColumnName() + "', "; } // remove last ", " @@ -1949,19 +2024,20 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * rows} in case {@link #preSelectedRace} is null, or only the rows of the competitors who scored in * the race identified by {@link #preSelectedRace} otherwise. */ - private Collection getRowsToDisplay(LeaderboardDTO leaderboard) { + @Override + public Collection getRowsToDisplay() { Collection result; Iterable allFilteredCompetitors = competitorSelectionProvider.getFilteredCompetitors(); result = new ArrayList(); if (preSelectedRace == null) { for (CompetitorDTO competitor : leaderboard.rows.keySet()) { - if(Util.contains(allFilteredCompetitors, competitor)) { + if (Util.contains(allFilteredCompetitors, competitor)) { result.add(leaderboard.rows.get(competitor)); } } } else { for (CompetitorDTO competitorInPreSelectedRace : getCompetitors(preSelectedRace)) { - if(Util.contains(allFilteredCompetitors, competitorInPreSelectedRace)) { + if (Util.contains(allFilteredCompetitors, competitorInPreSelectedRace)) { result.add(leaderboard.rows.get(competitorInPreSelectedRace)); } } @@ -2491,7 +2567,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS public boolean hasBusyIndicator() { return true; } - + private Iterable getSelectedRows() { ArrayList selectedRows = new ArrayList(); for (LeaderboardRowDTO row : getData().getList()) { @@ -2504,7 +2580,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS @Override public void competitorsListChanged(Iterable competitors) { - timeChanged(timer.getTime(), null); + if (timer.isInitialized()) { + timeChanged(timer.getTime(), null); + } } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java index fdcceeb5170..fdde24baac5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java @@ -21,11 +21,12 @@ import com.google.gwt.user.client.ui.RadioButton; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.gwt.ui.client.DebugIdHelper; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.controls.IntegerBox; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; @@ -90,19 +91,19 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone dialogPanel.add(createTimingDetailsPanel(dialog)); return dialogPanel; } - + private FlowPanel createManeuverDetailsPanel(DataEntryDialog dialog) { FlowPanel meneuverPanel = new FlowPanel(); + meneuverPanel.ensureDebugId("ManeuverSettingsPanel"); meneuverPanel.add(dialog.createHeadline(stringMessages.maneuverTypes(), true)); meneuverPanel.addStyleName("SettingsDialogComponent"); FlowPanel meneuverContent = new FlowPanel(); meneuverContent.addStyleName("dialogInnerContent"); List currentMeneuverDetailSelection = maneuverDetailSelection; for (DetailType detailType : ManeuverCountRaceColumn.getAvailableManeuverDetailColumnTypes()) { - CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(detailType)); - dialog.addTooltip(checkbox, DetailTypeFormatter.getTooltip(detailType)); - checkbox.setValue(currentMeneuverDetailSelection.contains(detailType)); - maneuverDetailCheckboxes.put(detailType, checkbox); + CheckBox checkbox = createAndRegisterCheckbox(dialog, detailType, + currentMeneuverDetailSelection.contains(detailType), maneuverDetailCheckboxes); + meneuverContent.add(checkbox); } meneuverPanel.add(meneuverContent); @@ -111,7 +112,9 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone private FlowPanel createTimingDetailsPanel(DataEntryDialog dialog) { FlowPanel timingPanel = new FlowPanel(); + timingPanel.ensureDebugId("TimingSettingsPanel"); refreshIntervalInSecondsBox = dialog.createLongBox(delayBetweenAutoAdvancesInMilliseconds / 1000l, 4); + refreshIntervalInSecondsBox.ensureDebugId("RefreshIntervalLongBox"); timingPanel.add(dialog.createHeadline(stringMessages.timing(), true)); timingPanel.addStyleName("SettingsDialogComponent"); @@ -132,6 +135,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone private FlowPanel createRaceDetailPanel(DataEntryDialog dialog) { FlowPanel raceDetailDialog = new FlowPanel(); + raceDetailDialog.ensureDebugId("RaceDetailsSettingsPanel"); raceDetailDialog.add(dialog.createHeadline(stringMessages.raceDetailsToShow(), true)); raceDetailDialog.addStyleName("SettingsDialogComponent"); int detailCountInCurrentFlowPanel = 0; @@ -143,10 +147,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone raceDetailDialogContent.addStyleName("dialogInnerContent"); raceDetailDialog.add(raceDetailDialogContent); } - CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type)); - checkbox.setValue(currentRaceDetailSelection.contains(type)); - dialog.addTooltip(checkbox, DetailTypeFormatter.getTooltip(type)); - raceDetailCheckboxes.put(type, checkbox); + CheckBox checkbox = createAndRegisterCheckbox(dialog, type, currentRaceDetailSelection.contains(type), + raceDetailCheckboxes); raceDetailDialogContent.add(checkbox); detailCountInCurrentFlowPanel++; } @@ -163,16 +165,15 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone private FlowPanel createOverallDetailPanel(DataEntryDialog dialog) { FlowPanel overallDetailDialog = new FlowPanel(); + overallDetailDialog.ensureDebugId("OverallDetailsSettingsPanel"); overallDetailDialog.add(dialog.createHeadline(stringMessages.overallDetailsToShow(), true)); overallDetailDialog.addStyleName("SettingsDialogComponent overallDetailSettings"); FlowPanel overallDetailDialogContent = new FlowPanel(); overallDetailDialogContent.addStyleName("dialogInnerContent"); List currentOverallDetailSelection = overallDetailSelection; for (DetailType type : LeaderboardPanel.getAvailableOverallDetailColumnTypes()) { - CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type)); - dialog.addTooltip(checkbox, DetailTypeFormatter.getTooltip(type)); - checkbox.setValue(currentOverallDetailSelection.contains(type)); - overallDetailCheckboxes.put(type, checkbox); + CheckBox checkbox = createAndRegisterCheckbox(dialog, type, currentOverallDetailSelection.contains(type), + overallDetailCheckboxes); overallDetailDialogContent.add(checkbox); } overallDetailDialog.add(overallDetailDialogContent); @@ -181,6 +182,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone private FlowPanel createLegDetailsPanel(DataEntryDialog dialog) { FlowPanel legDetailsToShow = new FlowPanel(); + legDetailsToShow.ensureDebugId("LegDetailsSettingsPanel"); legDetailsToShow.add(dialog.createHeadline(stringMessages.legDetailsToShow(), true)); legDetailsToShow.addStyleName("SettingsDialogComponent"); FlowPanel legDetailsContent = null; @@ -192,10 +194,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone legDetailsContent.addStyleName("dialogInnerContent"); legDetailsToShow.add(legDetailsContent); } - CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type)); - dialog.addTooltip(checkbox, DetailTypeFormatter.getTooltip(type)); - checkbox.setValue(currentLegDetailSelection.contains(type)); - legDetailCheckboxes.put(type, checkbox); + CheckBox checkbox = createAndRegisterCheckbox(dialog, type, currentLegDetailSelection.contains(type), + legDetailCheckboxes); legDetailsContent.add(checkbox); detailCountInCurrentFlowPanel++; } @@ -204,6 +204,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone private FlowPanel createSelectedRacesPanel(DataEntryDialog dialog) { FlowPanel selectedRacesPanel = new FlowPanel(); + selectedRacesPanel.ensureDebugId("RaceSelectionSettingsPanel"); selectedRacesPanel.addStyleName("SettingsDialogComponent"); selectedRacesPanel.add(dialog.createHeadline(stringMessages.selectedRaces(), true)); // race selection strategy elements @@ -218,12 +219,16 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone int racesCount = raceAllRaceColumns.size(); if (racesCount > 0) { final FlowPanel explicitRaceSelectionContent = new FlowPanel(); + explicitRaceSelectionContent.ensureDebugId("ExplicitRaceSelectionPanel"); final FlowPanel lastNRacesSelectionContent = new FlowPanel(); + lastNRacesSelectionContent.ensureDebugId("MostCurrentRacesSelectionPanel"); + String radioButtonGroupName = "raceSelectionStrategyGroup"; Label raceSelectionWayLabel = new Label(stringMessages.chooseTheWayYouSelectRaces() + ":"); raceSelectionWayLabel.getElement().getStyle().setPaddingRight(5, Unit.PX); racesSelectionStrategyPanel.add(raceSelectionWayLabel); explicitRaceColumnSelectionRadioBtn = dialog.createRadioButton(radioButtonGroupName, stringMessages.selectFromAllRaces()); + explicitRaceColumnSelectionRadioBtn.ensureDebugId("ExplicitRaceSelectionRadioButton"); racesSelectionStrategyPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); explicitRaceColumnSelectionRadioBtn.setValue(activeRaceColumnSelectionStrategy == RaceColumnSelectionStrategies.EXPLICIT); explicitRaceColumnSelectionRadioBtn.addClickHandler(new ClickHandler() { @@ -246,8 +251,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone } Grid grid = new Grid(rowCount, maxRacesPerRow); for (RaceColumnDTO expandableSortableColumn : raceAllRaceColumns) { - CheckBox checkbox = dialog.createCheckbox(expandableSortableColumn.getRaceColumnName()); - checkbox.setValue(Util.contains(raceColumnSelection, expandableSortableColumn)); + CheckBox checkbox = createCheckbox(dialog, expandableSortableColumn.getRaceColumnName(), + Util.contains(raceColumnSelection, expandableSortableColumn), null); raceColumnCheckboxes.put(expandableSortableColumn, checkbox); grid.setWidget(rowIndex, columnIndex++, checkbox); if(columnIndex == maxRacesPerRow) { @@ -260,6 +265,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone selectedRacesContent.add(explicitRaceSelectionContent); lastNRacesColumnSelectionRadioBtn = dialog.createRadioButton(radioButtonGroupName, stringMessages.selectANumberOfRaces()); + lastNRacesColumnSelectionRadioBtn.ensureDebugId("MostCurrentRacesSelectionRadioButton"); lastNRacesColumnSelectionRadioBtn.setValue(activeRaceColumnSelectionStrategy == RaceColumnSelectionStrategies.LAST_N); lastNRacesColumnSelectionRadioBtn.addClickHandler(new ClickHandler() { @Override @@ -279,6 +285,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone numberOfLastRacesLabel.getElement().getStyle().setPaddingRight(10, Unit.PX); hPanel.add(numberOfLastRacesLabel); numberOfLastRacesToShowBox = dialog.createIntegerBox(numberOfLastRacesToShow != null ? numberOfLastRacesToShow : racesCount, 3); + numberOfLastRacesToShowBox.ensureDebugId("NumberOfMostCurrentRacesIntegerBox"); hPanel.add(numberOfLastRacesToShowBox); dialog.alignAllPanelWidgetsVertically(hPanel, HasVerticalAlignment.ALIGN_MIDDLE); lastNRacesSelectionContent.setVisible(activeRaceColumnSelectionStrategy == RaceColumnSelectionStrategies.LAST_N); @@ -288,6 +295,28 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone } return selectedRacesPanel; } + + private CheckBox createAndRegisterCheckbox(DataEntryDialog dialog, DetailType detailType, boolean selected, + Map registry) { + CheckBox checkbox = createCheckbox(dialog, detailType, selected); + registry.put(detailType, checkbox); + return checkbox; + } + + private CheckBox createCheckbox(DataEntryDialog dialog, DetailType detailType, boolean selected) { + CheckBox checkbox = createCheckbox(dialog, DetailTypeFormatter.format(detailType), selected, + DetailTypeFormatter.getTooltip(detailType)); + checkbox.ensureDebugId(DebugIdHelper.createDebugId(detailType) + "CheckBox"); + return checkbox; + } + + private CheckBox createCheckbox(DataEntryDialog dialog, String label, boolean selected, String tooltip) { + CheckBox checkbox = dialog.createCheckbox(label); + checkbox.ensureDebugId(DebugIdHelper.createDebugId(label) + "CheckBox"); + checkbox.setValue(selected); + dialog.addTooltip(checkbox, tooltip); + return checkbox; + } @Override public LeaderboardSettings getResult() { @@ -341,16 +370,18 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone return new Validator() { @Override public String getErrorMessage(LeaderboardSettings valueToValidate) { + final String result; if (valueToValidate.getLegDetailsToShow().isEmpty()) { - return stringMessages.selectAtLeastOneLegDetail(); + result = stringMessages.selectAtLeastOneLegDetail(); } else if (valueToValidate.getDelayBetweenAutoAdvancesInMilliseconds() < 1000) { - return stringMessages.chooseUpdateIntervalOfAtLeastOneSecond(); + result = stringMessages.chooseUpdateIntervalOfAtLeastOneSecond(); } else if (valueToValidate.getActiveRaceColumnSelectionStrategy() == RaceColumnSelectionStrategies.LAST_N && (numberOfLastRacesToShowBox.getValue() == null || numberOfLastRacesToShowBox.getValue() < 0)) { - return stringMessages.numberOfRacesMustBeNonNegativeNumber(); + result = stringMessages.numberOfRacesMustBeNonNegativeNumber(); } else { - return null; + result = null; } + return result; } }; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java old mode 100644 new mode 100755 index a0f9fbc0ba5..92670502c3a --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java @@ -13,8 +13,8 @@ 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.MultiCompetitorLeaderboardChart; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.player.Timer; -import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.useragent.UserAgentDetails; /** @@ -29,12 +29,12 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer { private LeaderboardPanel overallLeaderboardPanel; public LeaderboardViewer(final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, - final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, final String leaderboardGroupName, - String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages, - final UserAgentDetails userAgent, boolean showRaceDetails, boolean hideToolbar, boolean autoExpandLastRaceColumn, - boolean showCharts, DetailType chartDetailType, boolean showOverallLeaderboard) { + final Timer timer, final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, + final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails, boolean hideToolbar, + boolean autoExpandLastRaceColumn, boolean showCharts, DetailType chartDetailType, boolean showOverallLeaderboard) { super(new CompetitorSelectionModel(/* hasMultiSelection */true), asyncActionsExecutor, - new Timer(PlayModes.Replay, /*delayBetweenAutoAdvancesInMilliseconds*/ 3000l), stringMessages, hideToolbar); + timer, stringMessages, hideToolbar); final FlowPanel mainPanel = createViewerPanel(); setWidget(mainPanel); @@ -60,29 +60,29 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer { if(showCharts) { multiCompetitorChart.timeChanged(timer.getTime(), null); } - overallLeaderboardPanel = null; if(showOverallLeaderboard) { - sailingService.getOverallLeaderboardNamesContaining(leaderboardName, new AsyncCallback>() { - @Override - public void onSuccess(List result) { - if(result.size() == 1) { - String overallLeaderboardName = result.get(0); - overallLeaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, - leaderboardSettings, preselectedRace, competitorSelectionProvider, timer, - leaderboardGroupName, overallLeaderboardName, errorReporter, stringMessages, userAgent, - false, /* raceTimesInfoProvider */null, false, /* adjustTimerDelay */ true); - mainPanel.add(overallLeaderboardPanel); - addComponentToNavigationMenu(overallLeaderboardPanel, true, stringMessages.seriesLeaderboard(), - /* hasSettingsWhenComponentIsInvisible*/ true); - } - } - - @Override - public void onFailure(Throwable caught) { - // DO NOTHING - } - }); + sailingService.getOverallLeaderboardNamesContaining(leaderboardName, new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onSuccess(List result) { + if(result.size() == 1) { + String overallLeaderboardName = result.get(0); + overallLeaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, + leaderboardSettings, preselectedRace, competitorSelectionProvider, timer, + leaderboardGroupName, overallLeaderboardName, errorReporter, stringMessages, userAgent, + false, /* raceTimesInfoProvider */null, false, /* adjustTimerDelay */ true); + mainPanel.add(overallLeaderboardPanel); + addComponentToNavigationMenu(overallLeaderboardPanel, true, stringMessages.seriesLeaderboard(), + /* hasSettingsWhenComponentIsInvisible*/ true); + } + } + + @Override + public void onFailure(Throwable caught) { + // DO NOTHING + } + })); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java index 28829a0168e..a66eaea4752 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java @@ -67,10 +67,17 @@ public class LegColumn extends ExpandableSortableColumn { } } - private class AverageCrossTrackErrorInMeters extends AbstractLegDetailField { + private class AverageAbsoluteCrossTrackErrorInMeters extends AbstractLegDetailField { @Override protected Double getFromNonNullEntry(LegEntryDTO entry) { - return entry.averageCrossTrackErrorInMeters; + return entry.averageAbsoluteCrossTrackErrorInMeters; + } + } + + private class AverageSignedCrossTrackErrorInMeters extends AbstractLegDetailField { + @Override + protected Double getFromNonNullEntry(LegEntryDTO entry) { + return entry.averageSignedCrossTrackErrorInMeters; } } @@ -139,7 +146,7 @@ public class LegColumn extends ExpandableSortableColumn { private class ManeuverCountLegDetailsColumn extends FormattedDoubleDetailTypeColumn { public ManeuverCountLegDetailsColumn(String headerStyle, String columnStyle) { - super(DetailType.NUMBER_OF_MANEUVERS, null, headerStyle, columnStyle); + super(DetailType.NUMBER_OF_MANEUVERS, null, headerStyle, columnStyle, getLeaderboardPanel()); } @Override @@ -215,7 +222,7 @@ public class LegColumn extends ExpandableSortableColumn { List legDetailSelection, String headerStyle, String columnStyle, String detailHeaderStyle, String detailColumnStyle) { super(leaderboardPanel, /* expandable */true /* all legs have details */, new TextCell(), preferredSortingOrder, - stringMessages, detailHeaderStyle, detailColumnStyle, legDetailSelection); + stringMessages, detailHeaderStyle, detailColumnStyle, legDetailSelection, leaderboardPanel); setHorizontalAlignment(ALIGN_CENTER); this.raceName = raceName; this.legIndex = legIndex; @@ -230,7 +237,8 @@ public class LegColumn extends ExpandableSortableColumn { DetailType.SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED, DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS, DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS, DetailType.NUMBER_OF_MANEUVERS, DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS, DetailType.VELOCITY_MADE_GOOD_IN_KNOTS, - DetailType.TIME_TRAVELED, DetailType.AVERAGE_CROSS_TRACK_ERROR_IN_METERS, DetailType.RANK_GAIN }; + DetailType.TIME_TRAVELED, DetailType.AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS, + DetailType.AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS, DetailType.RANK_GAIN }; } @Override @@ -240,44 +248,47 @@ public class LegColumn extends ExpandableSortableColumn { Map> result = new HashMap>(); result.put(DetailType.DISTANCE_TRAVELED, - new FormattedDoubleDetailTypeColumn(DetailType.DISTANCE_TRAVELED, new DistanceTraveledInMeters(), detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleDetailTypeColumn(DetailType.DISTANCE_TRAVELED, new DistanceTraveledInMeters(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, - new FormattedDoubleDetailTypeColumn(DetailType.AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, new AverageSpeedOverGroundInKnots(), detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleDetailTypeColumn(DetailType.AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, new AverageSpeedOverGroundInKnots(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS, - new FormattedDoubleDetailTypeColumn(DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS, new CurrentSpeedOverGroundInKnots(), detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleDetailTypeColumn(DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS, new CurrentSpeedOverGroundInKnots(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS, new FormattedDoubleDetailTypeColumn(DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS, new EstimatedTimeToNextWaypointInSeconds(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.GAP_TO_LEADER_IN_SECONDS, - new FormattedDoubleDetailTypeColumn(DetailType.GAP_TO_LEADER_IN_SECONDS, new GapToLeaderInSeconds(), detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleDetailTypeColumn(DetailType.GAP_TO_LEADER_IN_SECONDS, new GapToLeaderInSeconds(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.GAP_CHANGE_SINCE_LEG_START_IN_SECONDS, new FormattedDoubleDetailTypeColumn(DetailType.GAP_CHANGE_SINCE_LEG_START_IN_SECONDS, new GapChangeSinceLegStartInSeconds(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED, new SideToWhichMarkAtLegStartWasRoundedColumn(stringMessages.sideToWhichMarkAtLegStartWasRounded(), - new SideToWhichMarkAtLegStartWasRounded(), detailHeaderStyle, detailColumnStyle, stringMessages)); + new SideToWhichMarkAtLegStartWasRounded(), detailHeaderStyle, detailColumnStyle, stringMessages, leaderboardPanel)); result.put(DetailType.VELOCITY_MADE_GOOD_IN_KNOTS, - new FormattedDoubleDetailTypeColumn(DetailType.VELOCITY_MADE_GOOD_IN_KNOTS, new VelocityMadeGoodInKnots(), detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleDetailTypeColumn(DetailType.VELOCITY_MADE_GOOD_IN_KNOTS, new VelocityMadeGoodInKnots(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS, - new FormattedDoubleDetailTypeColumn(DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS, new WindwardDistanceToGoInMeters(), detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleDetailTypeColumn(DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS, new WindwardDistanceToGoInMeters(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); - result.put(DetailType.RANK_GAIN, new RankGainColumn(stringMessages.rankGain(), new RankGain(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.RANK_GAIN, new RankGainColumn(stringMessages.rankGain(), new RankGain(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.NUMBER_OF_MANEUVERS, new ManeuverCountLegDetailsColumn(detailHeaderStyle, detailColumnStyle)); result.put(DetailType.TIME_TRAVELED, - new FormattedDoubleDetailTypeColumn(DetailType.TIME_TRAVELED, new TimeTraveledInSeconds(), detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleDetailTypeColumn(DetailType.TIME_TRAVELED, new TimeTraveledInSeconds(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); - result.put(DetailType.AVERAGE_CROSS_TRACK_ERROR_IN_METERS, - new FormattedDoubleDetailTypeColumn(DetailType.AVERAGE_CROSS_TRACK_ERROR_IN_METERS, new AverageCrossTrackErrorInMeters(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS, + new FormattedDoubleDetailTypeColumn(DetailType.AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS, new AverageAbsoluteCrossTrackErrorInMeters(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); + + result.put(DetailType.AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS, + new FormattedDoubleDetailTypeColumn(DetailType.AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS, new AverageSignedCrossTrackErrorInMeters(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java index d5706ded6dc..4c615dcb65a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java @@ -12,7 +12,6 @@ import com.google.gwt.user.cellview.client.Header; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.InvertibleComparator; import com.sap.sailing.domain.common.ManeuverType; -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; @@ -87,9 +86,9 @@ public class ManeuverCountRaceColumn extends ExpandableSortableColumn im public ManeuverCountRaceColumn(LeaderboardPanel leaderboardPanel, RaceNameProvider raceNameProvider, StringMessages stringConstants, List maneuverDetailSelection, String headerStyle, - String columnStylee, String detailHeaderStyle, String detailColumnStyle) { + String columnStylee, String detailHeaderStyle, String detailColumnStyle, DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { super(leaderboardPanel, /* expandable */true /* all legs have details */, new TextCell(), DetailType.NUMBER_OF_MANEUVERS.getDefaultSortingOrder(), - stringConstants, detailHeaderStyle, detailColumnStyle, maneuverDetailSelection); + stringConstants, detailHeaderStyle, detailColumnStyle, maneuverDetailSelection, displayedLeaderboardRowsProvider); setHorizontalAlignment(ALIGN_CENTER); this.stringMessages = stringConstants; this.raceNameProvider = raceNameProvider; @@ -229,8 +228,8 @@ public class ManeuverCountRaceColumn extends ExpandableSortableColumn im } @Override - protected void updateMinMax(LeaderboardDTO leaderboard) { - minmaxRenderer.updateMinMax(leaderboard.rows.values()); + protected void updateMinMax() { + minmaxRenderer.updateMinMax(getDisplayedLeaderboardRowsProvider()); } @Override @@ -238,24 +237,24 @@ public class ManeuverCountRaceColumn extends ExpandableSortableColumn im LeaderboardPanel leaderboardPanel, StringMessages stringMessages, String detailHeaderStyle, String detailColumnStyle) { Map> result = new HashMap>(); - result.put(DetailType.TACK, new FormattedDoubleDetailTypeColumn(DetailType.TACK, new NumberOfTacks(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.TACK, new FormattedDoubleDetailTypeColumn(DetailType.TACK, new NumberOfTacks(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); - result.put(DetailType.JIBE, new FormattedDoubleDetailTypeColumn(DetailType.JIBE, new NumberOfJibes(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.JIBE, new FormattedDoubleDetailTypeColumn(DetailType.JIBE, new NumberOfJibes(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.PENALTY_CIRCLE, - new FormattedDoubleDetailTypeColumn(DetailType.PENALTY_CIRCLE, new NumberOfPenaltyCircles(), detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleDetailTypeColumn(DetailType.PENALTY_CIRCLE, new NumberOfPenaltyCircles(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.AVERAGE_TACK_LOSS_IN_METERS, new FormattedDoubleDetailTypeColumn(DetailType.AVERAGE_TACK_LOSS_IN_METERS, new AverageTackLossInMeters(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.AVERAGE_JIBE_LOSS_IN_METERS, new FormattedDoubleDetailTypeColumn(DetailType.AVERAGE_JIBE_LOSS_IN_METERS, new AverageJibeLossInMeters(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.AVERAGE_MANEUVER_LOSS_IN_METERS, new FormattedDoubleDetailTypeColumn(DetailType.AVERAGE_MANEUVER_LOSS_IN_METERS, new AverageManeuverLossInMeters(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MaxSpeedOverallColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MaxSpeedOverallColumn.java index d149daa1829..1a216fb7480 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MaxSpeedOverallColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MaxSpeedOverallColumn.java @@ -20,8 +20,8 @@ public class MaxSpeedOverallColumn extends FormattedDoubleDetailTypeColumn { } } - public MaxSpeedOverallColumn(String headerStyle, String columnStyle) { - super(DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS, new MaxSpeedOverallField(), headerStyle, columnStyle); + public MaxSpeedOverallColumn(String headerStyle, String columnStyle, DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { + super(DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS, new MaxSpeedOverallField(), headerStyle, columnStyle, displayedLeaderboardRowsProvider); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java index b9b900c9ce9..e4cdbfc7580 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java @@ -12,7 +12,6 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorLeaderboardChart; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.Timer; -import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.useragent.UserAgentDetails; /** @@ -27,39 +26,31 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer { private final MultiCompetitorLeaderboardChart multiCompetitorChart; public MetaLeaderboardViewer(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RaceIdentifier preselectedRace, String leaderboardGroupName, - String metaLeaderboardName, ErrorReporter errorReporter, StringMessages stringMessages, - UserAgentDetails userAgent, boolean showRaceDetails, boolean hideToolbar, boolean autoExpandLastRaceColumn, - boolean showCharts, DetailType chartDetailType, boolean showSeriesLeaderboards) { - super(new CompetitorSelectionModel(/* hasMultiSelection */true), asyncActionsExecutor, - new Timer(PlayModes.Replay, /*delayBetweenAutoAdvancesInMilliseconds*/ 3000l), stringMessages, hideToolbar); - + Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RaceIdentifier preselectedRace, + String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, + StringMessages stringMessages, UserAgentDetails userAgent, boolean showRaceDetails, boolean hideToolbar, + boolean autoExpandLastRaceColumn, boolean showCharts, DetailType chartDetailType, boolean showSeriesLeaderboards) { + super(new CompetitorSelectionModel(/* hasMultiSelection */true), asyncActionsExecutor, timer, stringMessages, hideToolbar); FlowPanel mainPanel = createViewerPanel(); setWidget(mainPanel); - final Label overallStandingsLabel = new Label(stringMessages.overallStandings()); overallStandingsLabel.setStyleName("leaderboardHeading"); - metaLeaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderboardSettings, preselectedRace, competitorSelectionProvider, timer, leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages, userAgent, showRaceDetails, /* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */ true); - multiCompetitorChart = new MultiCompetitorLeaderboardChart(sailingService, asyncActionsExecutor, metaLeaderboardName, chartDetailType, competitorSelectionProvider, timer, stringMessages, errorReporter); multiCompetitorChart.setVisible(showCharts); multiCompetitorChart.getElement().getStyle().setMarginTop(10, Unit.PX); multiCompetitorChart.getElement().getStyle().setMarginBottom(10, Unit.PX); - multiLeaderboardPanel = new MultiLeaderboardPanel(sailingService, metaLeaderboardName, asyncActionsExecutor, timer, preselectedLeaderboardName, preselectedRace, errorReporter, stringMessages, userAgent, showRaceDetails, autoExpandLastRaceColumn); multiLeaderboardPanel.setVisible(showSeriesLeaderboards); - mainPanel.add(metaLeaderboardPanel); mainPanel.add(multiCompetitorChart); mainPanel.add(multiLeaderboardPanel); - addComponentToNavigationMenu(metaLeaderboardPanel, false, stringMessages.seriesLeaderboard(), /* hasSettingsWhenComponentIsInvisible*/ true); addComponentToNavigationMenu(multiCompetitorChart, true, null, /* hasSettingsWhenComponentIsInvisible*/ true); addComponentToNavigationMenu(multiLeaderboardPanel, true , stringMessages.regattaLeaderboards(), /* hasSettingsWhenComponentIsInvisible*/ false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MinMaxRenderer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MinMaxRenderer.java index a26240ea474..af57981af62 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MinMaxRenderer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MinMaxRenderer.java @@ -1,6 +1,5 @@ package com.sap.sailing.gwt.ui.leaderboard; -import java.util.Collection; import java.util.Comparator; import com.google.gwt.cell.client.Cell.Context; @@ -51,7 +50,7 @@ public class MinMaxRenderer { /** * Gets the percentage of a {@link LeaderboardRowDTO}. If no minimum or maximum value was set by calling - * {@link MinMaxRenderer#updateMinMax(Collection)} before zero is returned. + * {@link MinMaxRenderer#updateMinMax(DisplayedLeaderboardRowsProvider)} before zero is returned. * * @param row * The row to get the percentage for. @@ -82,13 +81,13 @@ public class MinMaxRenderer { /** * Updates the {@link MinMaxRenderer#minimumValue} and {@link MinMaxRenderer#maximumValue}. * - * @param values + * @param displayedLeaderboardRowsProvider * The values of {@link LeaderboardRowDTO}s to determine the minimum and maximum values for. */ - public void updateMinMax(Collection values) { + public void updateMinMax(DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { LeaderboardRowDTO minimumRow = null; LeaderboardRowDTO maximumRow = null; - for (LeaderboardRowDTO row : values) { + for (LeaderboardRowDTO row : displayedLeaderboardRowsProvider.getRowsToDisplay()) { if (valueProvider.getDoubleValue(row) != null && (minimumRow == null || comparator.compare(minimumRow, row) > 0)) { minimumRow = row; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java index 232b437b6ce..0d2e6a8bf93 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java @@ -17,13 +17,13 @@ import com.google.gwt.user.client.ui.TabPanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.AbstractLazyComponent; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.Timer; @@ -50,7 +50,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent> leaderboardNamesAndDisplayNames; + private final List> leaderboardNamesAndDisplayNames; private final Map leaderboardNamesAndSettings; private TabPanel leaderboardsTabPanel; @@ -73,7 +73,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent>(); + leaderboardNamesAndDisplayNames = new ArrayList>(); leaderboardNamesAndSettings = new HashMap(); } @@ -142,7 +142,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent> newLeaderboardNamesAndDisplayNames) { + public void setLeaderboardNames(List> newLeaderboardNamesAndDisplayNames) { leaderboardNamesAndDisplayNames.clear(); leaderboardNamesAndDisplayNames.addAll(newLeaderboardNamesAndDisplayNames); @@ -150,10 +150,10 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent>>() { + sailingService.getLeaderboardsNamesOfMetaleaderboard(metaLeaderboardName, new AsyncCallback>>() { @Override - public void onSuccess(List> leaderboardNamesAndDisplayNames) { + public void onSuccess(List> leaderboardNamesAndDisplayNames) { setLeaderboardNames(leaderboardNamesAndDisplayNames); } @@ -169,7 +169,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent leaderboardNameAndDisplayName : leaderboardNamesAndDisplayNames) { + for (Util.Pair leaderboardNameAndDisplayName : leaderboardNamesAndDisplayNames) { FlowPanel tabFlowPanel = new FlowPanel(); leaderboardsTabPanel.add(tabFlowPanel, leaderboardNameAndDisplayName.getB(), false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/OverallTimeTraveledColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/OverallTimeTraveledColumn.java index 0ece5c3151f..991b07cb5a2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/OverallTimeTraveledColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/OverallTimeTraveledColumn.java @@ -12,7 +12,6 @@ import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.Header; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.InvertibleComparator; -import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; @@ -34,7 +33,7 @@ public class OverallTimeTraveledColumn extends ExpandableSortableColumn String detailColumnStyle) { super(leaderboardPanel, /* expandable */true, new TextCell(), DETAIL_TYPE.getDefaultSortingOrder(), stringMessages, detailHeaderStyle, detailColumnStyle, - Arrays.asList(DetailType.TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS, DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS)); + Arrays.asList(DetailType.TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS, DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS), leaderboardPanel); setHorizontalAlignment(ALIGN_CENTER); this.stringMessages = stringMessages; this.columnStyle = columnStyle; @@ -55,13 +54,13 @@ public class OverallTimeTraveledColumn extends ExpandableSortableColumn result.put(DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, new TotalTimeColumn(DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, new TotalTimeSailedUpwindInSeconds(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS, new TotalTimeColumn(DetailType.TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS, new TotalTimeSailedDownwindInSeconds(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS, new TotalTimeColumn(DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS, new TotalTimeSailedReachingInSeconds(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); return result; } @@ -141,8 +140,8 @@ public class OverallTimeTraveledColumn extends ExpandableSortableColumn } @Override - protected void updateMinMax(LeaderboardDTO leaderboard) { - minmaxRenderer.updateMinMax(leaderboard.rows.values()); + protected void updateMinMax() { + minmaxRenderer.updateMinMax(getDisplayedLeaderboardRowsProvider()); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/RankGainColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/RankGainColumn.java index df8418512b8..4955b38f831 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/RankGainColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/RankGainColumn.java @@ -12,8 +12,9 @@ import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; public class RankGainColumn extends DetailTypeColumn { private static final LeaderboardResources leaderboardResources = GWT.create(LeaderboardResources.class); - public RankGainColumn(String title, LegDetailField field, String headerStyle, String columnStyle) { - super(DetailType.RANK_GAIN, field, new RankGainCell(), headerStyle, columnStyle); + public RankGainColumn(String title, LegDetailField field, String headerStyle, String columnStyle, + DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { + super(DetailType.RANK_GAIN, field, new RankGainCell(), headerStyle, columnStyle, displayedLeaderboardRowsProvider); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java index d3a2d0fc004..113e14d586e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java @@ -20,6 +20,10 @@ public class ScoringSchemeTypeFormatter { return stringMessages.scoringSchemeHighPointFirstGetsTen(); case LOW_POINT_WINNER_GETS_ZERO: return stringMessages.scoringSchemeLowPointWinnerGetsZero(); + case HIGH_POINT_WINNER_GETS_FIVE: + return stringMessages.scoringSchemeWinnerGetsFive(); + case HIGH_POINT_WINNER_GETS_SIX: + return stringMessages.scoringSchemeWinnerGetsSix(); } return null; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SideToWhichMarkAtLegStartWasRoundedColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SideToWhichMarkAtLegStartWasRoundedColumn.java index 10587b70f99..39b0cd488c2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SideToWhichMarkAtLegStartWasRoundedColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SideToWhichMarkAtLegStartWasRoundedColumn.java @@ -9,8 +9,10 @@ import com.sap.sailing.gwt.ui.client.StringMessages; public class SideToWhichMarkAtLegStartWasRoundedColumn extends DetailTypeColumn { private final StringMessages stringMessages; - public SideToWhichMarkAtLegStartWasRoundedColumn(String title, LegDetailField field, String headerStyle, String columnStyle, StringMessages stringMessages) { - super(DetailType.SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED, field, new TextCell(), headerStyle, columnStyle); + public SideToWhichMarkAtLegStartWasRoundedColumn(String title, LegDetailField field, + String headerStyle, String columnStyle, StringMessages stringMessages, + DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { + super(DetailType.SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED, field, new TextCell(), headerStyle, columnStyle, displayedLeaderboardRowsProvider); this.stringMessages = stringMessages; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumn.java index 89d3400add6..8d13ff79bec 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumn.java @@ -5,18 +5,28 @@ import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.Header; import com.sap.sailing.domain.common.InvertibleComparator; import com.sap.sailing.domain.common.SortingOrder; -import com.sap.sailing.domain.common.dto.LeaderboardDTO; public abstract class SortableColumn extends Column { private SortingOrder preferredSortingOrder; + private final DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider; - protected SortableColumn(Cell cell, SortingOrder preferredSortingOrder) { + protected SortableColumn(Cell cell, SortingOrder preferredSortingOrder, + DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { super(cell); setSortable(true); this.preferredSortingOrder = preferredSortingOrder; + this.displayedLeaderboardRowsProvider = displayedLeaderboardRowsProvider; } - protected void updateMinMax(LeaderboardDTO leaderboard) {} + /** + * Based on the rows returned by the {@link DisplayedLeaderboardRowsProvider} instance passed to the + * constructor, updates the min/max values for this column. + */ + protected void updateMinMax() {} + + protected DisplayedLeaderboardRowsProvider getDisplayedLeaderboardRowsProvider() { + return displayedLeaderboardRowsProvider; + } public abstract InvertibleComparator getComparator(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeSinceLastGpsFixColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeSinceLastGpsFixColumn.java index 36b94682620..e7f02220411 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeSinceLastGpsFixColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeSinceLastGpsFixColumn.java @@ -6,8 +6,8 @@ public class TimeSinceLastGpsFixColumn extends FormattedDoubleDetailTypeColumn { public TimeSinceLastGpsFixColumn(DetailType detailType, com.sap.sailing.gwt.ui.leaderboard.DetailTypeColumn.LegDetailField field, String headerStyle, - String columnStyle) { - super(detailType, field, headerStyle, columnStyle); + String columnStyle, DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { + super(detailType, field, headerStyle, columnStyle, displayedLeaderboardRowsProvider); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java index 046b4a978b3..89653b954dd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java @@ -12,7 +12,6 @@ import com.google.gwt.user.cellview.client.Header; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.InvertibleComparator; import com.sap.sailing.domain.common.LegType; -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; @@ -36,7 +35,7 @@ public class TimeTraveledRaceColumn extends ExpandableSortableColumn imp String detailHeaderStyle, String detailColumnStyle) { super(leaderboardPanel, /* expandable */true, new TextCell(), DETAIL_TYPE.getDefaultSortingOrder(), stringMessages, detailHeaderStyle, detailColumnStyle, - Arrays.asList(DetailType.RACE_TIME_TRAVELED_UPWIND, DetailType.RACE_TIME_TRAVELED_DOWNWIND, DetailType.RACE_TIME_TRAVELED_REACHING)); + Arrays.asList(DetailType.RACE_TIME_TRAVELED_UPWIND, DetailType.RACE_TIME_TRAVELED_DOWNWIND, DetailType.RACE_TIME_TRAVELED_REACHING), leaderboardPanel); setHorizontalAlignment(ALIGN_CENTER); this.raceNameProvider = raceNameProvider; this.stringMessages = stringMessages; @@ -88,13 +87,13 @@ public class TimeTraveledRaceColumn extends ExpandableSortableColumn imp result.put(DetailType.RACE_TIME_TRAVELED_UPWIND, new FormattedDoubleDetailTypeColumn(DetailType.RACE_TIME_TRAVELED_UPWIND, new RaceTimeTraveledUpwindInSeconds(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.RACE_TIME_TRAVELED_DOWNWIND, new FormattedDoubleDetailTypeColumn(DetailType.RACE_TIME_TRAVELED_DOWNWIND, new RaceTimeTraveledDownwindInSeconds(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.RACE_TIME_TRAVELED_REACHING, new FormattedDoubleDetailTypeColumn(DetailType.RACE_TIME_TRAVELED_REACHING, new RaceTimeTraveledReachingInSeconds(), - detailHeaderStyle, detailColumnStyle)); + detailHeaderStyle, detailColumnStyle, leaderboardPanel)); return result; } @@ -172,8 +171,8 @@ public class TimeTraveledRaceColumn extends ExpandableSortableColumn imp } @Override - protected void updateMinMax(LeaderboardDTO leaderboard) { - minmaxRenderer.updateMinMax(leaderboard.rows.values()); + protected void updateMinMax() { + minmaxRenderer.updateMinMax(getDisplayedLeaderboardRowsProvider()); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeColumn.java index 02d5c9e852f..43664d25f80 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeColumn.java @@ -7,8 +7,9 @@ import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; public class TotalTimeColumn extends FormattedDoubleDetailTypeColumn { - protected TotalTimeColumn(DetailType detailType, LegDetailField field, String headerStyle, String columnStyle) { - super(detailType, field, headerStyle, columnStyle); + protected TotalTimeColumn(DetailType detailType, LegDetailField field, String headerStyle, + String columnStyle, DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { + super(detailType, field, headerStyle, columnStyle, displayedLeaderboardRowsProvider); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditScoreDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditScoreDialog.java index 3e9b99e621e..0f92ade7c65 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditScoreDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditScoreDialog.java @@ -8,17 +8,17 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.dialog.DataEntryDialog; -public class EditScoreDialog extends DataEntryDialog> { +public class EditScoreDialog extends DataEntryDialog> { private final ListBox maxPointsBox; private final DoubleBox netPointsBox; private final StringMessages stringMessages; public EditScoreDialog(StringMessages stringMessages, String competitorName, String raceColumnName, - MaxPointsReason oldMaxPointsReason, Double oldNetPoints, DialogCallback> callback) { + MaxPointsReason oldMaxPointsReason, Double oldNetPoints, DialogCallback> callback) { super(stringMessages.correctScore(), stringMessages.correctScoreFor(competitorName, raceColumnName), stringMessages.ok(), stringMessages.cancel(), /* validator */ null, /* animationEnabled */ true, callback); @@ -40,7 +40,7 @@ public class EditScoreDialog extends DataEntryDialog getResult() { + protected Util.Pair getResult() { final MaxPointsReason maxPointsReason; if ("".equals(maxPointsBox.getItemText(maxPointsBox.getSelectedIndex()))) { maxPointsReason = null; @@ -48,7 +48,7 @@ public class EditScoreDialog extends DataEntryDialog(maxPointsReason, netScore); + return new Util.Pair(maxPointsReason, netScore); } @Override 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 90c37b3f746..1f287ee81af 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 @@ -49,8 +49,6 @@ import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.adminconsole.AdminConsoleTableResources; import com.sap.sailing.gwt.ui.client.Collator; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; @@ -65,6 +63,7 @@ import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; import com.sap.sailing.gwt.ui.leaderboard.SortableColumn; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.useragent.UserAgentDetails; @@ -144,7 +143,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { */ private class SuppressedSailIDColumn extends SortableColumn { protected SuppressedSailIDColumn() { - super(new TextCell(), SortingOrder.ASCENDING); + super(new TextCell(), SortingOrder.ASCENDING, EditableLeaderboardPanel.this); } @Override @@ -189,7 +188,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { private final CompetitorColumnBase base; protected SuppressedCompetitorColumn(CompetitorColumnBase base) { - super(base.getCell(getLeaderboard()), SortingOrder.ASCENDING); + super(base.getCell(getLeaderboard()), SortingOrder.ASCENDING, EditableLeaderboardPanel.this); this.base = base; } @@ -339,7 +338,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { getBusyIndicator().setBusy(true); getSailingService().updateLeaderboardMaxPointsReason(getLeaderboardName(), row.competitor.getIdAsString(), raceColumnName, value == null || value.trim().length() == 0 ? null : MaxPointsReason.valueOf(value.trim()), - getLeaderboardDisplayDate(), new AsyncCallback>() { + getLeaderboardDisplayDate(), new AsyncCallback>() { @Override public void onFailure(Throwable t) { getBusyIndicator().setBusy(false); @@ -350,7 +349,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { } @Override - public void onSuccess(Triple newNetAndTotalPointsAndIsCorrected) { + public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) { getBusyIndicator().setBusy(false); row.fieldsByRaceColumnName.get(raceColumnName).reasonForMaxPoints = value == null || value.length() == 0 ? null : MaxPointsReason.valueOf(value.trim()); @@ -465,7 +464,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { 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>() { + new AsyncCallback>() { @Override public void onFailure(Throwable t) { getBusyIndicator().setBusy(false); @@ -476,7 +475,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { } @Override - public void onSuccess(Triple newNetAndTotalPointsAndIsCorrected) { + public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) { getBusyIndicator().setBusy(false); final LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName); leaderboardEntryDTO.netPoints = value == null || value.length() == 0 ? newNetAndTotalPointsAndIsCorrected @@ -537,13 +536,13 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { getWhiteboardOwner().whiteboardProduced(whiteboard); new EditScoreDialog(stringMessages, row.competitor.getName(), raceColumnName, row.fieldsByRaceColumnName.get(raceColumnName).reasonForMaxPoints, - row.fieldsByRaceColumnName.get(raceColumnName).netPoints, new DialogCallback>() { + row.fieldsByRaceColumnName.get(raceColumnName).netPoints, new DialogCallback>() { @Override - public void ok(final Pair editedObject) { + public void ok(final Util.Pair editedObject) { getBusyIndicator().setBusy(true); getSailingService().updateLeaderboardScoreCorrection(getLeaderboardName(), row.competitor.getIdAsString(), raceColumnName, editedObject.getB(), getLeaderboardDisplayDate(), - new AsyncCallback>() { + new AsyncCallback>() { @Override public void onFailure(Throwable t) { getBusyIndicator().setBusy(false); @@ -554,10 +553,10 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { } @Override - public void onSuccess(Triple newNetAndTotalPointsAndIsCorrected) { + public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) { getSailingService().updateLeaderboardMaxPointsReason(getLeaderboardName(), row.competitor.getIdAsString(), raceColumnName, editedObject.getA(), getLeaderboardDisplayDate(), - new AsyncCallback>() { + new AsyncCallback>() { @Override public void onFailure(Throwable t) { getBusyIndicator().setBusy(false); @@ -568,7 +567,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { } @Override - public void onSuccess(Triple newNetAndTotalPointsAndIsCorrected) { + public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) { getBusyIndicator().setBusy(false); final LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName); leaderboardEntryDTO.reasonForMaxPoints = editedObject.getA(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java index 1d5bb8aad83..5db95c5b4ae 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java @@ -4,8 +4,8 @@ import java.util.List; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; @@ -13,6 +13,7 @@ import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; 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.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; @@ -23,7 +24,7 @@ public class LeaderboardEditPage extends AbstractEntryPoint { protected void doOnModuleLoad() { super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); sailingService.getLeaderboardNames(new MarkedAsyncCallback>( new AsyncCallback>() { 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 c096a45d483..4f4a72f13c7 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 @@ -31,13 +31,13 @@ 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.RaceColumnDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO; import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO.ScoreCorrectionEntryDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog { @@ -50,7 +50,7 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog competitorCheckboxes; private final Map raceColumnCheckboxes; - private final Map, CheckBox> cellCheckboxes; + private final Map, CheckBox> cellCheckboxes; private final Grid grid; private final Map raceNameOrNumberChoosers; private final Map officialSailIDChoosers; @@ -74,7 +74,7 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog event) { for (RaceColumnDTO raceColumn : MatchAndApplyScoreCorrectionsDialog.this.leaderboard.getRaceList()) { - cellCheckboxes.get(new Pair(competitor, raceColumn)).setValue(event.getValue()); + cellCheckboxes.get(new Util.Pair(competitor, raceColumn)).setValue(event.getValue()); } } }); @@ -87,16 +87,16 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog event) { for (CompetitorDTO competitor : MatchAndApplyScoreCorrectionsDialog.this.leaderboard.competitors) { - cellCheckboxes.get(new Pair(competitor, raceColumn)).setValue(event.getValue()); + cellCheckboxes.get(new Util.Pair(competitor, raceColumn)).setValue(event.getValue()); } } }); raceColumnCheckboxes.put(raceColumn, checkbox); } - cellCheckboxes = new HashMap, CheckBox>(); + cellCheckboxes = new HashMap, CheckBox>(); for (final CompetitorDTO competitor : leaderboard.competitors) { for (RaceColumnDTO raceColumn : leaderboard.getRaceList()) { - cellCheckboxes.put(new Pair(competitor, raceColumn), createCheckbox(stringMessages.apply())); + cellCheckboxes.put(new Util.Pair(competitor, raceColumn), createCheckbox(stringMessages.apply())); } } allAllCheckbox = createCheckbox(stringMessages.selectAll()); @@ -106,7 +106,7 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog(competitor, raceColumn)).setValue(event.getValue()); + cellCheckboxes.get(new Util.Pair(competitor, raceColumn)).setValue(event.getValue()); } } for (RaceColumnDTO raceColumn : leaderboard.getRaceList()) { @@ -244,7 +244,7 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog key = new Pair(competitor, raceColumn); + Util.Pair key = new Util.Pair(competitor, raceColumn); CheckBox cellCheckbox = cellCheckboxes.get(key); if (cellCheckbox.getValue()) { // apply the score correction of the cell: @@ -357,7 +357,7 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog(competitor, raceColumn))); + cell.add(cellCheckboxes.get(new Util.Pair(competitor, raceColumn))); grid.setWidget(row, column++, cell); } row++; 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 e0c5b211ea3..87bfe3af2ea 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 @@ -12,30 +12,32 @@ import java.util.Set; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.dto.BoatClassDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO; import com.sap.sailing.gwt.ui.shared.ScoreCorrectionProviderDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; import com.sap.sse.gwt.client.dialog.DataEntryDialog; -public class ResultSelectionAndApplyDialog extends DataEntryDialog>> { +public class ResultSelectionAndApplyDialog extends DataEntryDialog>> { /** * a unique and human-readable string key for a eventNameBoatClassNameAndLastModfied pair */ - private final LinkedHashMap>> scoreCorrections; + private final LinkedHashMap>> scoreCorrections; private final ListBox scoreCorrectionListBox; private final ListBox scoreCorrectionProviderListBox; private final BusyIndicator busyIndicator; + private final HTML manage2SailFinalNotice; private final SailingServiceAsync sailingService; private final StringMessages stringMessages; @@ -53,16 +55,20 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog>>(); + this.scoreCorrections = new LinkedHashMap>>(); scoreCorrectionProviderListBox = createListBox(/* isMultipleSelect */ false); scoreCorrectionListBox = createListBox(/* isMultipleSelect */ false); scoreCorrectionListBox.setVisible(false); busyIndicator = new SimpleBusyIndicator(); + manage2SailFinalNotice = new HTML(new SafeHtmlBuilder().appendEscapedLines( + stringMessages.manage2SailFinalNotice()).toSafeHtml()); + scoreCorrectionProviderListBox.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { + manage2SailFinalNotice.setVisible(false); int selectedIndex = scoreCorrectionProviderListBox.getSelectedIndex(); if (selectedIndex > 0) { String selectedProviderName = scoreCorrectionProviderListBox.getItemText(selectedIndex); @@ -119,17 +125,17 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog>> eventNameBoatClassNameAndLastModified = new ArrayList>>(); - for (Entry>> entry : scp.getHasResultsForBoatClassFromDateByEventName().entrySet()) { - for (Pair se : entry.getValue()) { - eventNameBoatClassNameAndLastModified.add(new Pair>(entry.getKey(), se)); + List>> eventNameBoatClassNameAndLastModified = new ArrayList>>(); + for (Entry>> entry : scp.getHasResultsForBoatClassFromDateByEventName().entrySet()) { + for (Util.Pair se : entry.getValue()) { + eventNameBoatClassNameAndLastModified.add(new Util.Pair>(entry.getKey(), se)); } } sortOfficialResultsByRelevance(eventNameBoatClassNameAndLastModified); - for (Pair> pair : eventNameBoatClassNameAndLastModified) { + for (Util.Pair> pair : eventNameBoatClassNameAndLastModified) { String eventName = pair.getA(); - Pair boatClassAndLastModified = pair.getB(); + Util.Pair boatClassAndLastModified = pair.getB(); String scoreCorrectionName = eventName + ", " + boatClassAndLastModified.getA() + ", " + boatClassAndLastModified.getB(); scoreCorrections.put(scoreCorrectionName, pair); @@ -138,15 +144,15 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog>> eventNameBoatClassNameCapturedWhen) { + private void sortOfficialResultsByRelevance(List>> eventNameBoatClassNameCapturedWhen) { final Set lowercaseBoatClassNames = new HashSet(); for (BoatClassDTO boatClass : boatClasses) { lowercaseBoatClassNames.add(boatClass.getName().toLowerCase()); } Collections.sort(eventNameBoatClassNameCapturedWhen, - new Comparator>>() { + new Comparator>>() { @Override - public int compare(Pair> o1, Pair> o2) { + public int compare(Util.Pair> o1, Util.Pair> o2) { int result; // TODO consider looking for longest common substring to handle things like "470 M" vs. // "470 Men" @@ -169,9 +175,9 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog>> { + private static class Validator implements DataEntryDialog.Validator>> { @Override - public String getErrorMessage(Triple> valueToValidate) { + public String getErrorMessage(Util.Triple> valueToValidate) { String errorMessage = null; if(valueToValidate == null) { errorMessage = ""; @@ -180,7 +186,7 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog>> { + private static class Callback implements DialogCallback>> { private final EditableLeaderboardPanel leaderboardPanel; private final SailingServiceAsync sailingService; private final StringMessages stringMessages; @@ -200,7 +206,7 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog> providerNameAndEventNameBoatClassNameCapturedWhen) { + public void ok(Util.Triple> providerNameAndEventNameBoatClassNameCapturedWhen) { final String scoreCorrectionProviderName = providerNameAndEventNameBoatClassNameCapturedWhen.getA(); final String eventName = providerNameAndEventNameBoatClassNameCapturedWhen.getB(); final String boatClassName = providerNameAndEventNameBoatClassNameCapturedWhen.getC().getA(); @@ -231,6 +237,7 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog> getResult() { - Triple> result = null; + protected Util.Triple> getResult() { + Util.Triple> result = null; int selectedProviderIndex = scoreCorrectionProviderListBox.getSelectedIndex(); if (selectedProviderIndex > 0) { String selectedProviderName = scoreCorrectionProviderListBox.getItemText(selectedProviderIndex); int selectedScoreCorrectionIndex = scoreCorrectionListBox.getSelectedIndex(); if(selectedScoreCorrectionIndex > 0) { - Pair> pair = scoreCorrections.get(scoreCorrectionListBox.getValue(selectedScoreCorrectionIndex)); - result = new Triple>(selectedProviderName, pair.getA(), pair.getB()); + Util.Pair> pair = scoreCorrections.get(scoreCorrectionListBox.getValue(selectedScoreCorrectionIndex)); + result = new Util.Triple>(selectedProviderName, pair.getA(), pair.getB()); } } return result; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java index 00706358012..0dafdf114f9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java @@ -29,8 +29,9 @@ import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sailing.domain.common.DataImportProgress; import com.sap.sailing.domain.common.MasterDataImportObjectCreationCount; -import com.sap.sailing.gwt.ui.client.EventRefresher; -import com.sap.sailing.gwt.ui.client.LeaderboardGroupRefresher; +import com.sap.sailing.gwt.ui.client.EventsRefresher; +import com.sap.sailing.gwt.ui.client.LeaderboardGroupsRefresher; +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; @@ -49,20 +50,22 @@ public class MasterDataImportPanel extends VerticalPanel { private SailingServiceAsync sailingService; private CheckBox overrideSwitch; private final RegattaRefresher regattaRefresher; - private final EventRefresher eventRefresher; - private final LeaderboardGroupRefresher leaderboardGroupRefresher; + private final EventsRefresher eventRefresher; + private final LeaderboardsRefresher leaderboardsRefresher; + private final LeaderboardGroupsRefresher leaderboardGroupsRefresher; private CheckBox compressSwitch; private CheckBox exportWindSwitch; private TextBox filterBox; public MasterDataImportPanel(StringMessages stringMessages, SailingServiceAsync sailingService, - RegattaRefresher regattaRefresher, EventRefresher eventRefresher, - LeaderboardGroupRefresher leaderboardGroupRefresher) { + RegattaRefresher regattaRefresher, EventsRefresher eventsRefresher, LeaderboardsRefresher leaderboardsRefresher, + LeaderboardGroupsRefresher leaderboardGroupsRefresher) { this.sailingService = sailingService; this.stringMessages = stringMessages; this.regattaRefresher = regattaRefresher; - this.eventRefresher = eventRefresher; - this.leaderboardGroupRefresher = leaderboardGroupRefresher; + this.eventRefresher = eventsRefresher; + this.leaderboardsRefresher = leaderboardsRefresher; + this.leaderboardGroupsRefresher = leaderboardGroupsRefresher; HorizontalPanel serverAddressPanel = new HorizontalPanel(); serverAddressPanel.add(new Label(stringMessages.importRemoteHost())); @@ -205,7 +208,10 @@ public class MasterDataImportPanel extends VerticalPanel { eventRefresher.fillEvents(); } if (leaderboardGroupsCreated > 0) { - leaderboardGroupRefresher.fillLeaderboardGroups(); + leaderboardGroupsRefresher.fillLeaderboardGroups(); + } + if (leaderboardsCreated > 0) { + leaderboardsRefresher.fillLeaderboards(); } Set overwrittenRegattas = creationCount.getOverwrittenRegattaNames(); showSuccessAlert(leaderboardsCreated, leaderboardGroupsCreated, eventsCreated, regattasCreated, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java index b1c5cf08b5b..3e5a14f9770 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.moxieapps.gwt.highcharts.client.BaseChart; import org.moxieapps.gwt.highcharts.client.Chart; import org.moxieapps.gwt.highcharts.client.Color; import org.moxieapps.gwt.highcharts.client.Point; @@ -57,7 +58,7 @@ public class PolarSheetsChartPanel extends DockLayoutPanel { */ private Chart createPolarSheetChart() { Chart polarSheetChart = new Chart().setType(Series.Type.LINE) - .setLinePlotOptions(new LinePlotOptions().setLineWidth(1)).setZoomType(Chart.ZoomType.X_AND_Y) + .setLinePlotOptions(new LinePlotOptions().setLineWidth(1)).setZoomType(BaseChart.ZoomType.X_AND_Y) .setPolar(true).setHeight100().setWidth100(); polarSheetChart.setChartTitleText(stringMessages.polarSheetChart()); polarSheetChart.getYAxis().setMin(0); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsEntryPoint.java index 8ca80ae3240..acf809620aa 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsEntryPoint.java @@ -9,16 +9,17 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; -import com.sap.sailing.gwt.ui.client.RegattaDisplayer; import com.sap.sailing.gwt.ui.client.RegattaRefresher; +import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.client.SailingService; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sse.gwt.client.EntryPointHelper; public class PolarSheetsEntryPoint extends AbstractEntryPoint implements RegattaRefresher { - private Set regattaDisplayers; + private Set regattaDisplayers; private final SailingServiceAsync sailingService = GWT.create(SailingService.class); @@ -26,9 +27,9 @@ public class PolarSheetsEntryPoint extends AbstractEntryPoint implements Regatta protected void doOnModuleLoad() { super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); - regattaDisplayers = new HashSet(); + regattaDisplayers = new HashSet(); createUI(); fillRegattas(); } @@ -46,7 +47,7 @@ public class PolarSheetsEntryPoint extends AbstractEntryPoint implements Regatta sailingService.getRegattas(new AsyncCallback>() { @Override public void onSuccess(List result) { - for (RegattaDisplayer regattaDisplayer : regattaDisplayers) { + for (RegattasDisplayer regattaDisplayer : regattaDisplayers) { regattaDisplayer.fillRegattas(result); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java index 8d2e64a2821..8efb10f83e3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java @@ -32,7 +32,7 @@ import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.RaceSelectionModel; -import com.sap.sailing.gwt.ui.client.RegattaDisplayer; +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.components.Component; @@ -47,7 +47,7 @@ import com.sap.sailing.gwt.ui.shared.RegattaDTO; * @author d054528 Frederik Petersen * */ -public class PolarSheetsPanel extends SplitLayoutPanel implements RaceSelectionChangeListener, RegattaDisplayer, Component { +public class PolarSheetsPanel extends SplitLayoutPanel implements RaceSelectionChangeListener, RegattasDisplayer, Component { public static final String POLARSHEETS_STYLE = "polarSheets"; 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 93acdf1f2b7..f1e650770ae 100644 --- 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 @@ -33,10 +33,11 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.UserDTO; +import com.sap.sse.gwt.client.EntryPointHelper; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.useragent.UserAgentChecker; -import com.sap.sse.gwt.server.GwtHttpRequestUtils; +import com.sap.sse.gwt.shared.GwtHttpRequestUtils; public class RaceBoardEntryPoint extends AbstractEntryPoint { private RaceDTO selectedRace; @@ -62,9 +63,9 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { protected void doOnModuleLoad() { super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) userManagementService, RemoteServiceMappingConstants.userManagementServiceRemotePath); - registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); - registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) userManagementService, RemoteServiceMappingConstants.userManagementServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath); // read mandatory parameters regattaName = Window.Location.getParameter(PARAM_REGATTA_NAME); @@ -90,6 +91,7 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/); final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false); final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, false); + final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, true /* default*/); raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard, showWindChart, showCompetitorsChart, canReplayWhileLiveIsPossible, autoSelectMedia); @@ -102,7 +104,7 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { @Override public void handleSuccess() { checkUrlParameters(getLeaderboardNamesCallback.getData(), - getLeaderboardGroupByNameCallback.getData(), canReplayWhileLiveIsPossible, getRegattasCallback.getData(), getUserCallback.getData()); + getLeaderboardGroupByNameCallback.getData(), canReplayWhileLiveIsPossible, getRegattasCallback.getData(), getUserCallback.getData(), showMapControls); } @Override public void handleFailure(Throwable t) { @@ -113,7 +115,7 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { new ParallelExecutionHolder(getLeaderboardNamesCallback, getRegattasCallback, getUserCallback) { @Override public void handleSuccess() { - checkUrlParameters(getLeaderboardNamesCallback.getData(), null, canReplayWhileLiveIsPossible, getRegattasCallback.getData(), getUserCallback.getData()); + checkUrlParameters(getLeaderboardNamesCallback.getData(), null, canReplayWhileLiveIsPossible, getRegattasCallback.getData(), getUserCallback.getData(), showMapControls); } @Override public void handleFailure(Throwable t) { @@ -129,7 +131,7 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { userManagementService.getUser(getUserCallback); } - private void checkUrlParameters(List leaderboardNames, LeaderboardGroupDTO leaderboardGroup, boolean canReplayWhileLiveIsPossible, List regattas, UserDTO user) { + private void checkUrlParameters(List leaderboardNames, LeaderboardGroupDTO leaderboardGroup, boolean canReplayWhileLiveIsPossible, List regattas, UserDTO user, boolean showMapControls) { if (!leaderboardNames.contains(leaderboardName)) { createErrorPage(stringMessages.noSuchLeaderboard()); return; @@ -161,7 +163,7 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { Timer timer = new Timer(PlayModes.Replay, 1000l); RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, this, singletonList, 5000l /* requestInterval*/); RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, user, timer, raceSelectionModel, leaderboardName, - leaderboardGroupName, raceboardViewConfig, RaceBoardEntryPoint.this, stringMessages, userAgent, raceTimesInfoProvider); + leaderboardGroupName, raceboardViewConfig, RaceBoardEntryPoint.this, stringMessages, userAgent, raceTimesInfoProvider, showMapControls); raceBoardPanel.fillRegattas(regattas); createRaceBoardInOneScreenMode(raceBoardPanel); } 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 a11507698f5..3f7fe03fe10 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 @@ -8,7 +8,6 @@ import java.util.Map; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.VerticalAlign; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; @@ -36,15 +35,15 @@ 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.RegattaDisplayer; +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.media.MediaSelector; import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart; import com.sap.sailing.gwt.ui.client.shared.charts.WindChart; import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.ComponentViewer; -import com.sap.sailing.gwt.ui.client.media.MediaSelector; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorRaceRankFilter; import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorSelectionProviderFilterContext; @@ -82,7 +81,7 @@ import com.sap.sse.gwt.client.useragent.UserAgentDetails; * @author Frank Mittag, Axel Uhl (d043530) * */ -public class RaceBoardPanel extends SimplePanel implements RegattaDisplayer, RaceSelectionChangeListener { +public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, RaceSelectionChangeListener { private final SailingServiceAsync sailingService; private final MediaServiceAsync mediaService; private final UserDTO user; @@ -135,7 +134,7 @@ public class RaceBoardPanel extends SimplePanel implements RegattaDisplayer, Rac public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, UserDTO theUser, Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, String leaderboardGroupName, RaceBoardViewConfiguration raceboardViewConfiguration, ErrorReporter errorReporter, final StringMessages stringMessages, - UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider) { + UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) { this.sailingService = sailingService; this.mediaService = mediaService; this.user = theUser; @@ -169,13 +168,12 @@ public class RaceBoardPanel extends SimplePanel implements RegattaDisplayer, Rac toolbarPanel.add(viewControlsPanel); leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName); - createOneScreenView(leaderboardName, leaderboardGroupName, mainPanel); + createOneScreenView(leaderboardName, leaderboardGroupName, mainPanel, showMapControls); getElement().getStyle().setMarginLeft(12, Unit.PX); getElement().getStyle().setMarginRight(12, Unit.PX); // add busy indicator to toolbar panel because the embedded leaderboard has no space for it toolbarPanel.add(leaderboardPanel.getBusyIndicator()); - leaderboardPanel.getBusyIndicator().getElement().getStyle().setVerticalAlign(VerticalAlign.MIDDLE); CompetitorsFilterSets loadedCompetitorsFilterSets = loadCompetitorsFilterSets(); if (loadedCompetitorsFilterSets != null) { @@ -208,9 +206,9 @@ public class RaceBoardPanel extends SimplePanel implements RegattaDisplayer, Rac timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); } - private void createOneScreenView(String leaderboardName, String leaderboardGroupName, FlowPanel mainPanel) { + private void createOneScreenView(String leaderboardName, String leaderboardGroupName, FlowPanel mainPanel, boolean showMapControls) { // create the default leaderboard and select the right race - RaceMap raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, competitorSelectionModel, stringMessages); + RaceMap raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, competitorSelectionModel, stringMessages, showMapControls); raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap); raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier)); List> components = new ArrayList>(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardViewConfiguration.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardViewConfiguration.java index d7c9731d1f5..e5ff4373a5d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardViewConfiguration.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardViewConfiguration.java @@ -13,11 +13,12 @@ public class RaceBoardViewConfiguration { private boolean canReplayDuringLiveRaces; private final boolean autoSelectMedia; - public static final String PARAM_VIEW_MODE = "viewMode"; + public static final String PARAM_VIEW_MODE = "viewMode"; public static final String PARAM_VIEW_SHOW_LEADERBOARD = "viewShowLeaderboard"; public static final String PARAM_VIEW_SHOW_NAVIGATION_PANEL = "viewShowNavigationPanel"; public static final String PARAM_VIEW_SHOW_WINDCHART = "viewShowWindChart"; public static final String PARAM_VIEW_SHOW_COMPETITORSCHART = "viewShowCompetitorsChart"; + public static final String PARAM_VIEW_SHOW_MAPCONTROLS = "viewShowMapControls"; public static final String PARAM_VIEW_COMPETITOR_FILTER = "viewCompetitorFilter"; public static final String PARAM_CAN_REPLAY_DURING_LIVE_RACES = "canReplayDuringLiveRaces"; public static final String PARAM_AUTOSELECT_MEDIA = "autoSelectMedia"; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewEntryPoint.java index 48709a3c099..0e787377ca9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewEntryPoint.java @@ -24,6 +24,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.URLEncoder; import com.sap.sailing.gwt.ui.regattaoverview.RegattaRaceStatesComponent.EntryHandler; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; +import com.sap.sse.gwt.client.EntryPointHelper; public class RegattaOverviewEntryPoint extends AbstractEntryPoint { @@ -43,7 +44,7 @@ public class RegattaOverviewEntryPoint extends AbstractEntryPoint { public void doOnModuleLoad() { super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); RootLayoutPanel rootPanel = RootLayoutPanel.get(); containerPanel = new DockLayoutPanel(Unit.PX); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewPanel.java index 3372ab87d22..1e84a977abe 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewPanel.java @@ -202,7 +202,7 @@ public class RegattaOverviewPanel extends SimplePanel { } private CheckBox addLeaderboardEnablerButton() { - final CheckBox checkBox = new CheckBox("Leaderboard"); + final CheckBox checkBox = new CheckBox(stringMessages.leaderboard()); checkBox.getElement().getStyle().setFloat(Style.Float.LEFT); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaRaceStatesSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaRaceStatesSettingsDialogComponent.java index 3eaf988b039..0227d8b9f4c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaRaceStatesSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaRaceStatesSettingsDialogComponent.java @@ -4,8 +4,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; import java.util.Map.Entry; +import java.util.UUID; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; @@ -17,11 +17,11 @@ import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/DataMiningServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/DataMiningServiceImpl.java index 701f33c69ee..f3eeebcd7a4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/DataMiningServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/DataMiningServiceImpl.java @@ -7,11 +7,11 @@ import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; import com.google.gwt.user.server.rpc.RemoteServiceServlet; -import com.sap.sailing.datamining.data.GPSFixWithContext; -import com.sap.sailing.datamining.data.TrackedLegOfCompetitorWithContext; +import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.factories.DataMiningFactory; import com.sap.sailing.datamining.shared.DataTypes; -import com.sap.sailing.datamining.shared.QueryDefinition; +import com.sap.sailing.datamining.shared.QueryDefinitionDeprecated; import com.sap.sailing.gwt.ui.datamining.client.DataMiningService; import com.sap.sailing.server.RacingEventService; import com.sap.sse.datamining.DataMiningServer; @@ -38,14 +38,13 @@ public class DataMiningServiceImpl extends RemoteServiceServlet implements DataM private ServiceTracker createAndOpenDataMiningServerTracker( BundleContext context) { - ServiceTracker result = - new ServiceTracker( - context, com.sap.sse.datamining.DataMiningServer.class.getName(), null); + ServiceTracker result = new ServiceTracker( + context, DataMiningServer.class.getName(), null); result.open(); return result; } - private com.sap.sse.datamining.DataMiningServer getDataMiningServer() { + private DataMiningServer getDataMiningServer() { return dataMiningServerTracker.getService(); } @@ -70,9 +69,9 @@ public class DataMiningServiceImpl extends RemoteServiceServlet implements DataM private Class getBaseClassFor(DataTypes dataType) { switch (dataType) { case GPSFix: - return GPSFixWithContext.class; + return HasGPSFixContext.class; case TrackedLegOfCompetitor: - return TrackedLegOfCompetitorWithContext.class; + return HasTrackedLegOfCompetitorContext.class; } throw new IllegalArgumentException("No base class for data type " + dataType); } @@ -86,8 +85,9 @@ public class DataMiningServiceImpl extends RemoteServiceServlet implements DataM } @Override - public QueryResult runQuery(QueryDefinition queryDefinition) throws Exception { - Query query = DataMiningFactory.createQuery(queryDefinition, getRacingEventService()); + public QueryResult runQuery(QueryDefinitionDeprecated queryDefinition) throws Exception { + @SuppressWarnings("unchecked") // TODO Fix after the data mining has been cleaned + Query query = (Query) DataMiningFactory.createQuery(queryDefinition, getRacingEventService(), getDataMiningServer().getFunctionProvider()); return query.run(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/PlaceHolderDeviceIdentifierStringSerializationHandler.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/PlaceHolderDeviceIdentifierStringSerializationHandler.java new file mode 100644 index 00000000000..dd95376bcfe --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/PlaceHolderDeviceIdentifierStringSerializationHandler.java @@ -0,0 +1,8 @@ +package com.sap.sailing.gwt.ui.server; + +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifierStringSerializationHandler; +import com.sap.sailing.domain.racelog.tracking.impl.PlaceHolderDeviceIdentifierSerializationHandler; + +public class PlaceHolderDeviceIdentifierStringSerializationHandler extends PlaceHolderDeviceIdentifierSerializationHandler implements + DeviceIdentifierStringSerializationHandler { +} 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 404ffa9cce5..e6b36aea07d 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 @@ -50,6 +50,8 @@ import javax.servlet.ServletContext; import org.apache.http.client.ClientProtocolException; import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; import com.sap.sailing.domain.base.BoatClass; @@ -60,12 +62,18 @@ import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.LeaderboardGroupBase; +import com.sap.sailing.domain.base.LeaderboardSearchResult; +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.Nationality; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnInSeries; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.Waypoint; @@ -80,9 +88,17 @@ import com.sap.sailing.domain.base.configuration.impl.GateStartConfigurationImpl import com.sap.sailing.domain.base.configuration.impl.RRS26ConfigurationImpl; import com.sap.sailing.domain.base.configuration.impl.RacingProcedureConfigurationImpl; import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl; +import com.sap.sailing.domain.base.impl.BoatImpl; +import com.sap.sailing.domain.base.impl.CourseDataImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.DynamicBoat; +import com.sap.sailing.domain.base.impl.DynamicPerson; +import com.sap.sailing.domain.base.impl.DynamicTeam; import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.PersonImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.base.impl.TeamImpl; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.CountryCode; import com.sap.sailing.domain.common.DataImportProgress; @@ -115,6 +131,7 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.TimeRange; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; @@ -131,6 +148,7 @@ import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO; import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.domain.common.dto.RaceLogTrackingInfoDTO; import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; import com.sap.sailing.domain.common.dto.TrackedRaceDTO; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; @@ -141,14 +159,19 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.PolarSheetGenerationResponseImpl; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.common.impl.TimeRangeImpl; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.common.racelog.FlagPole; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.common.racelog.tracking.NoCorrespondingServiceRegisteredException; +import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; +import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; +import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinder; +import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory; import com.sap.sailing.domain.igtimiadapter.Account; import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; @@ -164,14 +187,33 @@ import com.sap.sailing.domain.persistence.MongoObjectFactory; import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; import com.sap.sailing.domain.racelog.RaceLogFlagEvent; import com.sap.sailing.domain.racelog.RaceStateOfSameDayHelper; +import com.sap.sailing.domain.racelog.Revokable; import com.sap.sailing.domain.racelog.analyzing.impl.AbortingFlagFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.LastPublishedCourseDesignFinder; import com.sap.sailing.domain.racelog.state.ReadonlyRaceState; import com.sap.sailing.domain.racelog.state.impl.ReadonlyRaceStateImpl; import com.sap.sailing.domain.racelog.state.racingprocedure.FlagPoleState; import com.sap.sailing.domain.racelog.state.racingprocedure.gate.ReadonlyGateStartRacingProcedure; import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.ReadonlyRRS26RacingProcedure; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; +import com.sap.sailing.domain.racelog.tracking.DeviceIdentifierStringSerializationHandler; +import com.sap.sailing.domain.racelog.tracking.DeviceMapping; +import com.sap.sailing.domain.racelog.tracking.DeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DefinedMarkFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceCompetitorMappingFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceMarkMappingFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.LastPingMappingEventFinder; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.OpenEndedDeviceMappingCloser; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.RaceLogTrackingStateAnalyzer; +import com.sap.sailing.domain.racelog.tracking.analyzing.impl.RegisteredCompetitorsAnalyzer; +import com.sap.sailing.domain.racelog.tracking.impl.DeviceMappingImpl; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; +import com.sap.sailing.domain.swisstimingadapter.StartList; import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory; import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration; @@ -181,6 +223,9 @@ import com.sap.sailing.domain.swisstimingadapter.persistence.SwissTimingAdapterP import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayRace; import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayService; import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayServiceFactory; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; +import com.sap.sailing.domain.trackimport.GPSFixImporter; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.GPSFix; @@ -198,6 +243,8 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl; +import com.sap.sailing.domain.tracking.impl.GPSFixImpl; import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tractracadapter.RaceRecord; @@ -216,10 +263,15 @@ import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; +import com.sap.sailing.gwt.ui.shared.DeviceIdentifierDTO; +import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.GateDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupBaseDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; import com.sap.sailing.gwt.ui.shared.LegInfoDTO; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; @@ -242,6 +294,7 @@ import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO; import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO.ScoreCorrectionEntryDTO; +import com.sap.sailing.gwt.ui.shared.RemoteSailingServerReferenceDTO; import com.sap.sailing.gwt.ui.shared.ReplicaDTO; import com.sap.sailing.gwt.ui.shared.ReplicationMasterDTO; import com.sap.sailing.gwt.ui.shared.ReplicationStateDTO; @@ -257,6 +310,7 @@ import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO; import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO; import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO; +import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; import com.sap.sailing.gwt.ui.shared.VenueDTO; import com.sap.sailing.gwt.ui.shared.WaypointDTO; import com.sap.sailing.gwt.ui.shared.WindDTO; @@ -266,7 +320,6 @@ import com.sap.sailing.manage2sail.EventResultDescriptor; import com.sap.sailing.manage2sail.Manage2SailEventResultsParserImpl; import com.sap.sailing.manage2sail.RaceResultDescriptor; import com.sap.sailing.manage2sail.RegattaResultDescriptor; -import com.sap.sailing.manage2sail.TestManage2SailEventResultsParser; import com.sap.sailing.polars.PolarDataService; import com.sap.sailing.resultimport.ResultUrlProvider; import com.sap.sailing.resultimport.ResultUrlRegistry; @@ -276,6 +329,7 @@ import com.sap.sailing.server.masterdata.MasterDataImporter; import com.sap.sailing.server.operationaltransformation.AddColumnToLeaderboard; import com.sap.sailing.server.operationaltransformation.AddColumnToSeries; import com.sap.sailing.server.operationaltransformation.AddCourseArea; +import com.sap.sailing.server.operationaltransformation.AddRemoteSailingServerReference; import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; import com.sap.sailing.server.operationaltransformation.AllowCompetitorResetToDefaults; import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn; @@ -295,6 +349,7 @@ import com.sap.sailing.server.operationaltransformation.RemoveLeaderboard; import com.sap.sailing.server.operationaltransformation.RemoveLeaderboardColumn; import com.sap.sailing.server.operationaltransformation.RemoveLeaderboardGroup; import com.sap.sailing.server.operationaltransformation.RemoveRegatta; +import com.sap.sailing.server.operationaltransformation.RemoveRemoteSailingServerReference; import com.sap.sailing.server.operationaltransformation.RemoveSeries; import com.sap.sailing.server.operationaltransformation.RenameEvent; import com.sap.sailing.server.operationaltransformation.RenameLeaderboard; @@ -324,6 +379,10 @@ import com.sap.sailing.server.replication.ReplicationMasterDescriptor; import com.sap.sailing.server.replication.ReplicationService; import com.sap.sailing.server.replication.impl.ReplicaDescriptor; import com.sap.sailing.util.BuildVersion; +import com.sap.sailing.xrr.resultimport.schema.RegattaResults; +import com.sap.sse.common.Util; +import com.sap.sse.common.search.KeywordQuery; +import com.sap.sse.common.search.Result; /** * The server side implementation of the RPC service. @@ -349,6 +408,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final ServiceTracker igtimiAdapterTracker; + private final ServiceTracker raceLogTrackingAdapterTracker; + + private final ServiceTracker + deviceIdentifierStringSerializationHandlerTracker; + private final com.sap.sailing.domain.tractracadapter.persistence.MongoObjectFactory tractracMongoObjectFactory; private final DomainObjectFactory domainObjectFactory; @@ -376,7 +440,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S * have been obtained by {@link IncrementalLeaderboardDTO#strip(LeaderboardDTO)}. The cache size is limited to * {@link #LEADERBOARD_DIFFERENCE_CACHE_SIZE}. */ - private final LinkedHashMap, IncrementalLeaderboardDTO> leaderboardDifferenceCacheByIdPair; + private final LinkedHashMap, IncrementalLeaderboardDTO> leaderboardDifferenceCacheByIdPair; private final SwissTimingReplayService swissTimingReplayService; @@ -387,6 +451,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S replicationServiceTracker = createAndOpenReplicationServiceTracker(context); swissTimingAdapterTracker = createAndOpenSwissTimingAdapterTracker(context); tractracAdapterTracker = createAndOpenTracTracAdapterTracker(context); + raceLogTrackingAdapterTracker = createAndOpenRaceLogTrackingAdapterTracker(context); + deviceIdentifierStringSerializationHandlerTracker = createAndOpenDeviceIdentifierStringSerializationHandlerTracker(context); igtimiAdapterTracker = createAndOpenIgtimiTracker(context); baseDomainFactory = getService().getBaseDomainFactory(); mongoObjectFactory = getService().getMongoObjectFactory(); @@ -401,10 +467,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S tractracMongoObjectFactory = com.sap.sailing.domain.tractracadapter.persistence.MongoObjectFactory.INSTANCE; swissTimingFactory = SwissTimingFactory.INSTANCE; countryCodeFactory = com.sap.sailing.domain.common.CountryCodeFactory.INSTANCE; - leaderboardDifferenceCacheByIdPair = new LinkedHashMap, IncrementalLeaderboardDTO>(LEADERBOARD_DIFFERENCE_CACHE_SIZE, 0.75f, /* accessOrder */ true) { + leaderboardDifferenceCacheByIdPair = new LinkedHashMap, IncrementalLeaderboardDTO>(LEADERBOARD_DIFFERENCE_CACHE_SIZE, 0.75f, /* accessOrder */ true) { private static final long serialVersionUID = 3775119859130148488L; @Override - protected boolean removeEldestEntry(Entry, IncrementalLeaderboardDTO> eldest) { + protected boolean removeEldestEntry(Entry, IncrementalLeaderboardDTO> eldest) { return this.size() > LEADERBOARD_DIFFERENCE_CACHE_SIZE; } }; @@ -467,6 +533,15 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return result; } + protected ServiceTracker createAndOpenRaceLogTrackingAdapterTracker( + BundleContext context) { + ServiceTracker result = + new ServiceTracker( + context, RaceLogTrackingAdapterFactory.class.getName(), null); + result.open(); + return result; + } + private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); } @@ -525,12 +600,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private ScoreCorrectionProviderDTO convertScoreCorrectionProviderDTO(ScoreCorrectionProvider scoreCorrectionProvider) throws Exception { - Map>> hasResultsForBoatClassFromDateByEventName = new HashMap>>(); - for (Map.Entry>> e : scoreCorrectionProvider + Map>> hasResultsForBoatClassFromDateByEventName = new HashMap>>(); + for (Map.Entry>> e : scoreCorrectionProvider .getHasResultsForBoatClassFromDateByEventName().entrySet()) { - Set> set = new HashSet>(); - for (Pair p : e.getValue()) { - set.add(new Pair(p.getA(), p.getB().asDate())); + Set> set = new HashSet>(); + for (com.sap.sse.common.Util.Pair p : e.getValue()) { + set.add(new com.sap.sse.common.Util.Pair(p.getA(), p.getB().asDate())); } hasResultsForBoatClassFromDateByEventName.put(e.getKey(), set); } @@ -555,7 +630,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S *

* * Otherwise, the leaderboard is computed synchronously on the fly. - * * @param previousLeaderboardId * if null or no leaderboard with that {@link LeaderboardDTO#getId() ID} is known, a * {@link FullLeaderboardDTO} will be computed; otherwise, an {@link IncrementalLeaderboardDTO} will be @@ -563,8 +637,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S */ @Override public IncrementalOrFullLeaderboardDTO getLeaderboardByName(final String leaderboardName, final Date date, - final Collection namesOfRaceColumnsForWhichToLoadLegDetails, String previousLeaderboardId) - throws NoWindException, InterruptedException, ExecutionException, IllegalArgumentException { + final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + String previousLeaderboardId) throws NoWindException, InterruptedException, ExecutionException, + IllegalArgumentException { try { long startOfRequestHandling = System.currentTimeMillis(); IncrementalOrFullLeaderboardDTO result = null; @@ -577,7 +652,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S timePoint = new MillisecondsTimePoint(date); } LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, - namesOfRaceColumnsForWhichToLoadLegDetails, getService(), baseDomainFactory); + namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, getService(), baseDomainFactory); LeaderboardDTO previousLeaderboardDTO = null; synchronized (leaderboardByNameResultsCacheById) { leaderboardByNameResultsCacheById.put(leaderboardDTO.getId(), leaderboardDTO); @@ -596,7 +671,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S final IncrementalLeaderboardDTO cachedDiff; if (previousLeaderboardId != null) { synchronized (leaderboardDifferenceCacheByIdPair) { - cachedDiff = leaderboardDifferenceCacheByIdPair.get(new Pair(previousLeaderboardId, leaderboardDTO.getId())); + cachedDiff = leaderboardDifferenceCacheByIdPair.get(new com.sap.sse.common.Util.Pair(previousLeaderboardId, leaderboardDTO.getId())); } if (cachedDiff == null) { leaderboardDifferenceCacheByIdPairMisses++; @@ -613,7 +688,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S if (cachedDiff == null) { IncrementalLeaderboardDTO preResult = new IncrementalLeaderboardDTOCloner().clone(leaderboardDTO).strip(previousLeaderboardDTO); synchronized (leaderboardDifferenceCacheByIdPair) { - leaderboardDifferenceCacheByIdPair.put(new Pair(previousLeaderboardId, leaderboardDTO.getId()), preResult); + leaderboardDifferenceCacheByIdPair.put(new com.sap.sse.common.Util.Pair(previousLeaderboardId, leaderboardDTO.getId()), preResult); } incrementalResult = preResult; } else { @@ -623,9 +698,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S result = incrementalResult; } logger.fine("getLeaderboardByName(" + leaderboardName + ", " + date + ", " - + namesOfRaceColumnsForWhichToLoadLegDetails + ") took " - + (System.currentTimeMillis() - startOfRequestHandling) + "ms; diff cache hits/misses " - + leaderboardDifferenceCacheByIdPairHits+"/"+leaderboardDifferenceCacheByIdPairMisses); + + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + + ") took " + (System.currentTimeMillis() - startOfRequestHandling) + + "ms; diff cache hits/misses " + leaderboardDifferenceCacheByIdPairHits + "/" + + leaderboardDifferenceCacheByIdPairMisses); } return result; } catch (NoWindException e) { @@ -821,11 +897,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S List waypointDTOs = new ArrayList(); for (Waypoint waypoint : lastCourseDesign.getWaypoints()) { ControlPointDTO controlPointDTO = convertToControlPointDTO(waypoint.getControlPoint()); - List marks = new ArrayList(); - for (MarkDTO markDTO : controlPointDTO.getMarks()) { - marks.add(markDTO); - } - WaypointDTO waypointDTO = new WaypointDTO(waypoint.getName(), controlPointDTO, marks, waypoint.getPassingInstructions()); + WaypointDTO waypointDTO = new WaypointDTO(waypoint.getName(), controlPointDTO, waypoint.getPassingInstructions()); waypointDTOs.add(waypointDTO); } result = new RaceCourseDTO(waypointDTOs); @@ -843,7 +915,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S trackedRaceDTO = getBaseDomainFactory().createTrackedRaceDTO(trackedRace); } RaceWithCompetitorsDTO raceDTO = new RaceWithCompetitorsDTO(raceIdentifier, convertToCompetitorDTOs(r.getCompetitors()), - trackedRaceDTO, getService().isRaceBeingTracked(r)); + trackedRaceDTO, getService().isRaceBeingTracked(regatta, r)); if (trackedRace != null) { getBaseDomainFactory().updateRaceDTOWithTrackedRaceData(trackedRace, raceDTO); } @@ -863,8 +935,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Pair> listTracTracRacesInEvent(String eventJsonURL, boolean listHiddenRaces) throws MalformedURLException, IOException, ParseException, org.json.simple.parser.ParseException, URISyntaxException { - com.sap.sailing.domain.common.impl.Util.Pair> raceRecords; + public com.sap.sse.common.Util.Pair> listTracTracRacesInEvent(String eventJsonURL, boolean listHiddenRaces) throws MalformedURLException, IOException, ParseException, org.json.simple.parser.ParseException, URISyntaxException { + com.sap.sse.common.Util.Pair> raceRecords; raceRecords = getTracTracAdapter().getTracTracRaceRecords(new URL(eventJsonURL), /*loadClientParam*/ false); List result = new ArrayList(); for (RaceRecord raceRecord : raceRecords.getB()) { @@ -878,7 +950,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S .getTrackingEndTime().asDate(), raceRecord.getRaceStartTime().asDate(), raceRecord.getBoatClassNames(), raceRecord.getRaceStatus(), raceRecord.getJsonURL().toString())); } - return new Pair>(raceRecords.getA(), result); + return new com.sap.sse.common.Util.Pair>(raceRecords.getA(), result); } @Override @@ -1114,7 +1186,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S result.windTrackInfoByWindSource = windTrackInfoDTOs; List windSourcesToDeliver = new ArrayList(); Util.addAll(trackedRace.getWindSources(), windSourcesToDeliver); - // TODO bug #375: add the combined wind; currently, CombinedWindTrackImpl just takes too long to return results... windSourcesToDeliver.add(new WindSourceImpl(WindSourceType.COMBINED)); for (WindSource windSource : windSourcesToDeliver) { if (windSourceTypeNames == null || windSourceTypeNames.contains(windSource.getType().name())) { @@ -1129,17 +1200,16 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Double minWindConfidence = 2.0; Double maxWindConfidence = -1.0; for (int i = 0; i < numberOfFixes; i++) { - WindWithConfidence> averagedWindWithConfidence = windTrack.getAveragedWindWithConfidence(position, timePoint); - + WindWithConfidence> averagedWindWithConfidence = windTrack.getAveragedWindWithConfidence(position, timePoint); if (averagedWindWithConfidence != null) { WindDTO windDTO = createWindDTOFromAlreadyAveraged(averagedWindWithConfidence.getObject(), timePoint); double confidence = averagedWindWithConfidence.getConfidence(); windDTO.confidence = confidence; windTrackInfoDTO.windFixes.add(windDTO); - if(confidence < minWindConfidence) { + if (confidence < minWindConfidence) { minWindConfidence = confidence; } - if(confidence > maxWindConfidence) { + if (confidence > maxWindConfidence) { maxWindConfidence = confidence; } } @@ -1153,20 +1223,37 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return result; } + /** + * @param onlyUpToNewestEvent + * if true, no wind data will be returned for time points later than + * {@link TrackedRace#getTimePointOfNewestEvent() trackedRace.getTimePointOfNewestEvent()}. This is + * helpful in case the client wants to populate a chart during live mode. If false, the + * "best effort" readings are provided for the time interval requested, no matter if based on any sensor + * evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent() + * trackedRace.getTimePointOfNewestEvent()}. + */ @Override public WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, - int numberOfFixes, Collection windSourceTypeNames) + int numberOfFixes, Collection windSourceTypeNames, boolean onlyUpToNewestEvent) throws NoWindException { assert from != null; TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); - WindInfoForRaceDTO result = getAveragedWindInfo(new MillisecondsTimePoint(from), millisecondsStepWidth, numberOfFixes, - windSourceTypeNames, trackedRace); + windSourceTypeNames, trackedRace, /* onlyUpToNewestEvent */ true); return result; } + /** + * @param onlyUpToNewestEvent + * if true, no wind data will be returned for time points later than + * {@link TrackedRace#getTimePointOfNewestEvent() trackedRace.getTimePointOfNewestEvent()}. This is + * helpful in case the client wants to populate a chart during live mode. If false, the + * "best effort" readings are provided for the time interval requested, no matter if based on any sensor + * evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent() + * trackedRace.getTimePointOfNewestEvent()}. + */ private WindInfoForRaceDTO getAveragedWindInfo(TimePoint from, long millisecondsStepWidth, int numberOfFixes, - Collection windSourceTypeNames, TrackedRace trackedRace) { + Collection windSourceTypeNames, TrackedRace trackedRace, boolean onlyUpToNewestEvent) { WindInfoForRaceDTO result = null; if (trackedRace != null) { TimePoint newestEvent = trackedRace.getTimePointOfNewestEvent(); @@ -1194,19 +1281,27 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S TimePoint timePoint = from; Double minWindConfidence = 2.0; Double maxWindConfidence = -1.0; - for (int i = 0; i < numberOfFixes && newestEvent != null && timePoint.compareTo(newestEvent) < 0; i++) { - WindWithConfidence> averagedWindWithConfidence = windTrack.getAveragedWindWithConfidence(null, timePoint); + for (int i = 0; i < numberOfFixes && (!onlyUpToNewestEvent || + (newestEvent != null && timePoint.before(newestEvent))); i++) { + WindWithConfidence> averagedWindWithConfidence = windTrack.getAveragedWindWithConfidence(null, timePoint); if (averagedWindWithConfidence != null) { + if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { + logger.finest("Found averaged wind: " + averagedWindWithConfidence); + } double confidence = averagedWindWithConfidence.getConfidence(); WindDTO windDTO = createWindDTOFromAlreadyAveraged(averagedWindWithConfidence.getObject(), timePoint); windDTO.confidence = confidence; windTrackInfoDTO.windFixes.add(windDTO); - if(confidence < minWindConfidence) { + if (confidence < minWindConfidence) { minWindConfidence = confidence; } if (confidence > maxWindConfidence) { maxWindConfidence = confidence; } + } else { + if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { + logger.finest("Did NOT find any averaged wind for timepoint " + timePoint + " and tracked race " + trackedRace.getRaceIdentifier().getRaceName()); + } } timePoint = new MillisecondsTimePoint(timePoint.asMillis() + millisecondsStepWidth); } @@ -1218,9 +1313,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return result; } + /** + * @param onlyUpToNewestEvent + * if true, no wind data will be returned for time points later than + * {@link TrackedRace#getTimePointOfNewestEvent() trackedRace.getTimePointOfNewestEvent()}. This is + * helpful in case the client wants to populate a chart during live mode. If false, the + * "best effort" readings are provided for the time interval requested, no matter if based on any sensor + * evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent() + * trackedRace.getTimePointOfNewestEvent()}. + */ @Override public WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, Date to, - long resolutionInMilliseconds, Collection windSourceTypeNames) { + long resolutionInMilliseconds, Collection windSourceTypeNames, boolean onlyUpToNewestEvent) { TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); WindInfoForRaceDTO result = null; if (trackedRace != null) { @@ -1228,7 +1332,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S TimePoint toTimePoint = to == null ? trackedRace.getEndOfRace() : new MillisecondsTimePoint(to); if (fromTimePoint != null && toTimePoint != null) { int numberOfFixes = (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis())/resolutionInMilliseconds); - result = getAveragedWindInfo(fromTimePoint, resolutionInMilliseconds, numberOfFixes, windSourceTypeNames, trackedRace); + result = getAveragedWindInfo(fromTimePoint, resolutionInMilliseconds, numberOfFixes, + windSourceTypeNames, trackedRace, onlyUpToNewestEvent); } } return result; @@ -1324,6 +1429,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S while (fixIter.hasNext()) { GPSFixMoving fix = fixIter.next(); if (fix.getTimePoint().compareTo(toTimePointExcluding) < 0) { + if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { + logger.finest(""+competitor.getName()+": " + fix); + } fixes.add(fix); } else { break; @@ -1338,7 +1446,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Position estimatedPosition = track.getEstimatedPosition(middle, extrapolate); SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(middle); if (estimatedPosition != null && estimatedSpeed != null) { - fixes.add(new GPSFixMovingImpl(estimatedPosition, middle, estimatedSpeed)); + GPSFixMoving estimatedFix = new GPSFixMovingImpl(estimatedPosition, middle, estimatedSpeed); + if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { + logger.finest(""+competitor.getName()+": " + estimatedFix+" (estimated)"); + } + fixes.add(estimatedFix); } } Iterator fixIter = fixes.iterator(); @@ -1358,7 +1470,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S trackedLegOfCompetitor.getLeg()).getLegType(fix.getTimePoint()); Wind wind = trackedRace.getWind(fix.getPosition(),toTimePointExcluding); WindDTO windDTO = wind == null ? null : createWindDTOFromAlreadyAveraged(wind, toTimePointExcluding); - GPSFixDTO fixDTO = createGPSFixDTO(fix, fix.getSpeed(), windDTO, tack, legType, /* extrapolate */ + GPSFixDTO fixDTO = createGPSFixDTO(fix, track.getEstimatedSpeed(fix.getTimePoint()), windDTO, tack, legType, /* extrapolate */ false); fixesForCompetitor.add(fixDTO); if (fixIter.hasNext()) { @@ -1426,18 +1538,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceTimesInfo.endOfRace = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate(); raceTimesInfo.delayToLiveInMs = trackedRace.getDelayToLiveInMillis(); - Iterable>> markPassingsTimes = trackedRace.getMarkPassingsTimes(); + Iterable>> markPassingsTimes = trackedRace.getMarkPassingsTimes(); synchronized (markPassingsTimes) { int numberOfWaypoints = Util.size(markPassingsTimes); int wayPointNumber = 1; - for (Pair> markPassingTimes : markPassingsTimes) { + for (com.sap.sse.common.Util.Pair> markPassingTimes : markPassingsTimes) { MarkPassingTimesDTO markPassingTimesDTO = new MarkPassingTimesDTO(); String name = "M" + (wayPointNumber - 1); if (wayPointNumber == numberOfWaypoints) { name = "F"; } markPassingTimesDTO.setName(name); - Pair timesPair = markPassingTimes.getB(); + com.sap.sse.common.Util.Pair timesPair = markPassingTimes.getB(); TimePoint firstPassingTime = timesPair.getA(); TimePoint lastPassingTime = timesPair.getB(); markPassingTimesDTO.firstPassingDate = firstPassingTime == null ? null : firstPassingTime.asDate(); @@ -1575,26 +1687,28 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public RaceCourseDTO getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date) { List waypointDTOs = new ArrayList(); - RaceCourseDTO result = new RaceCourseDTO(waypointDTOs); - if (date != null) { - TimePoint dateAsTimePoint = new MillisecondsTimePoint(date); - TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); - if (trackedRace != null) { - Course course = trackedRace.getRace().getCourse(); - for (Waypoint waypoint : course.getWaypoints()) { - List markDTOs = new ArrayList(); - for (Mark mark : trackedRace.getMarks()) { - GPSFixTrack track = trackedRace.getOrCreateTrack(mark); - Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false); - markDTOs.add(convertToMarkDTO(mark, positionAtDate)); - } - ControlPointDTO controlPointDTO = convertToControlPointDTO(waypoint.getControlPoint(), trackedRace, dateAsTimePoint); - WaypointDTO waypointDTO = new WaypointDTO(waypoint.getName(), controlPointDTO, markDTOs, waypoint.getPassingInstructions()); - waypointDTOs.add(waypointDTO); + Map controlPointCache = new HashMap<>(); + TimePoint dateAsTimePoint = new MillisecondsTimePoint(date); + TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); + List allMarks = new ArrayList<>(); + if (trackedRace != null) { + for (Mark mark : trackedRace.getMarks()) { + Position pos = trackedRace.getOrCreateTrack(mark).getEstimatedPosition(dateAsTimePoint, false); + allMarks.add(convertToMarkDTO(mark, pos)); + } + Course course = trackedRace.getRace().getCourse(); + for (Waypoint waypoint : course.getWaypoints()) { + ControlPointDTO controlPointDTO = controlPointCache.get(waypoint.getControlPoint().getId()); + if (controlPointDTO == null) { + controlPointDTO = convertToControlPointDTO(waypoint.getControlPoint(), trackedRace, dateAsTimePoint); + controlPointCache.put(waypoint.getControlPoint().getId(), controlPointDTO); } + WaypointDTO waypointDTO = new WaypointDTO(waypoint.getName(), controlPointDTO, + waypoint.getPassingInstructions()); + waypointDTOs.add(waypointDTO); } } - return result; + return new RaceCourseDTO(waypointDTOs, allMarks); } private ControlPointDTO convertToControlPointDTO(ControlPoint controlPoint, TrackedRace trackedRace, TimePoint timePoint) { @@ -1608,7 +1722,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } else { final Position posOfFirst = trackedRace.getOrCreateTrack(controlPoint.getMarks().iterator().next()). getEstimatedPosition(timePoint, /* extrapolate */ false); - result = new MarkDTO(controlPoint.getId().toString(), controlPoint.getName(), posOfFirst.getLatDeg(), posOfFirst.getLngDeg()); + result = new MarkDTO(controlPoint.getId().toString(), controlPoint.getName(), posOfFirst == null ? -1 : posOfFirst.getLatDeg(), posOfFirst == null ? -1 : posOfFirst.getLngDeg()); } return result; } @@ -1625,6 +1739,61 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return result; } + /** + * For each {@link ControlPointDTO} in controlPoints tries to find the best-matching waypoint + * from the {@code old} {@code CourseBase}, and also from the newly created additional waypoints. + * If such a waypoint is found, its control point is added to the control point list for the new course. + * Otherwise, a new control point is created using the default {@link com.sap.sailing.domain.base.DomainFactory} + * instance. + */ + private List> resolveControlPoints( + CourseBase old, List> controlPoints) { + Map newlyCreatedCache = new HashMap(); + Iterable waypoints = old.getWaypoints(); + List> newControlPoints = new ArrayList>(); + for (com.sap.sse.common.Util.Pair controlPointAndPassingInstruction : controlPoints) { + ControlPointDTO controlPointDTO = controlPointAndPassingInstruction.getA(); + ControlPoint match = null; + if (newlyCreatedCache.containsKey(controlPointDTO)) { + match = newlyCreatedCache.get(controlPointDTO); + } else { + for (Waypoint oldWP : waypoints) { + ControlPoint oldControlPoint = oldWP.getControlPoint(); + if (oldControlPoint.getId().toString().equals(controlPointDTO.getIdAsString()) + && markIDsMatch(oldControlPoint.getMarks(), controlPointDTO.getMarks())) { + match = oldControlPoint; + break; + } + } + } + if (match != null) { + newControlPoints.add(new com.sap.sse.common.Util.Pair(match, controlPointAndPassingInstruction.getB())); + } else { + // no match found; create new control point: + ControlPoint newControlPoint; + if (controlPointDTO instanceof GateDTO) { + GateDTO gateDTO = (GateDTO) controlPointDTO; + final Serializable id; + if (gateDTO.getIdAsString() == null) { + id = UUID.randomUUID(); + } else { + id = gateDTO.getIdAsString(); + } + Mark left = baseDomainFactory.getOrCreateMark(gateDTO.getLeft().getIdAsString(), gateDTO.getLeft().getName()); + Mark right = baseDomainFactory.getOrCreateMark(gateDTO.getRight().getIdAsString(), gateDTO.getRight().getName()); + newControlPoint = baseDomainFactory.createControlPointWithTwoMarks(id, left, right, gateDTO.getName()); + newControlPoints.add(new com.sap.sse.common.Util.Pair(newControlPoint, null)); + } else { + newControlPoint = baseDomainFactory.getOrCreateMark(controlPointDTO.getIdAsString(), controlPointDTO.getName()); + PassingInstruction passingInstructions = controlPointAndPassingInstruction.getB(); + newControlPoints.add(new com.sap.sse.common.Util.Pair(newControlPoint, passingInstructions)); + } + newlyCreatedCache.put(controlPointDTO, newControlPoint); + } + } + return newControlPoints; + } + /** * For each {@link ControlPointDTO} in controlPoints tries to find the best-matching waypoint * from the course that belongs to the race identified by raceIdentifier. If such a waypoint is @@ -1634,47 +1803,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S * the course of the race identified by raceIdentifier. */ @Override - public void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List> controlPoints) { + public void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List> controlPoints) { TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); if (trackedRace != null) { Course course = trackedRace.getRace().getCourse(); - Iterable waypoints = course.getWaypoints(); - List> newControlPoints = new ArrayList>(); - int lastMatchPosition = -1; - for (Pair controlPointAndPassingInstruction : controlPoints) { - ControlPointDTO controlPointDTO = controlPointAndPassingInstruction.getA(); - ControlPoint matchFromOldCourse = null; - for (int i=lastMatchPosition+1; matchFromOldCourse == null && i(matchFromOldCourse, null)); - lastMatchPosition = i; - } - } - if (matchFromOldCourse == null) { - // no match found; create new control point: - ControlPoint newControlPoint; - if (controlPointDTO instanceof GateDTO) { - GateDTO gateDTO = (GateDTO) controlPointDTO; - final Serializable id; - if (gateDTO.getIdAsString() == null) { - id = UUID.randomUUID(); - } else { - id = gateDTO.getIdAsString(); - } - Mark left = baseDomainFactory.getOrCreateMark(gateDTO.getLeft().getIdAsString(), gateDTO.getLeft().getName()); - Mark right = baseDomainFactory.getOrCreateMark(gateDTO.getRight().getIdAsString(), gateDTO.getRight().getName()); - newControlPoint = baseDomainFactory.createControlPointWithTwoMarks(id, left, right, gateDTO.getName()); - newControlPoints.add(new Pair(newControlPoint, null)); - } else { - newControlPoint = baseDomainFactory.getOrCreateMark(controlPointDTO.getIdAsString(), controlPointDTO.getName()); - PassingInstruction passingInstructions = controlPointAndPassingInstruction.getB(); - newControlPoints.add(new Pair(newControlPoint, passingInstructions)); - } - } - } + List> newControlPoints = resolveControlPoints(course, controlPoints); try { course.update(newControlPoints, baseDomainFactory); } catch (Exception e) { @@ -1820,8 +1953,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Pair checkLeaderboardName(String leaderboardName) { - Pair result = null; + public com.sap.sse.common.Util.Pair checkLeaderboardName(String leaderboardName) { + com.sap.sse.common.Util.Pair result = null; if(getService().getLeaderboards().containsKey(leaderboardName)) { Leaderboard leaderboard = getService().getLeaderboards().get(leaderboardName); @@ -1833,7 +1966,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } else { type = isRegattaLeaderboard ? LeaderboardType.RegattaLeaderboard : LeaderboardType.FlexibleLeaderboard; } - result = new Pair(leaderboard.getName(), type); + result = new com.sap.sse.common.Util.Pair(leaderboard.getName(), type); } return result; @@ -1977,8 +2110,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceDTO = baseDomainFactory.createRaceDTO(getService(), withGeoLocationData, raceIdentifier, trackedRace); } final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); - leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), - fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO); + RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), + raceColumn.getFactor(), fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO); + + RaceLog raceLog = raceColumn.getRaceLog(fleet); + RaceLogTrackingState raceLogTrackingState = raceLog == null ? RaceLogTrackingState.NOT_A_RACELOG_TRACKED_RACE : + new RaceLogTrackingStateAnalyzer(raceLog).analyze(); + boolean raceLogTrackerExists = raceLog == null ? false : getService().getRaceTrackerById(raceLog.getId()) != null; + boolean competitorRegistrationsExist = raceLog == null ? false : !new RegisteredCompetitorsAnalyzer(raceLog).analyze().isEmpty(); + RaceLogTrackingInfoDTO raceLogTrackingInfo = new RaceLogTrackingInfoDTO(raceLogTrackerExists, + competitorRegistrationsExist, raceLogTrackingState); + raceColumnDTO.setRaceLogTrackingInfo(fleetDTO, raceLogTrackingInfo); } } return leaderboardDTO; @@ -2013,8 +2155,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd) { - for(Pair columnToAdd: columnsToAdd) { + public void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd) { + for(com.sap.sse.common.Util.Pair columnToAdd: columnsToAdd) { getService().apply(new AddColumnToLeaderboard(columnToAdd.getA(), leaderboardName, columnToAdd.getB())); } } @@ -2083,7 +2225,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Triple updateLeaderboardMaxPointsReason(String leaderboardName, String competitorIdAsString, String raceColumnName, + public com.sap.sse.common.Util.Triple updateLeaderboardMaxPointsReason(String leaderboardName, String competitorIdAsString, String raceColumnName, MaxPointsReason maxPointsReason, Date date) throws NoWindException { return getService().apply( new UpdateLeaderboardMaxPointsReason(leaderboardName, raceColumnName, competitorIdAsString, @@ -2091,7 +2233,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Triple updateLeaderboardScoreCorrection(String leaderboardName, + public com.sap.sse.common.Util.Triple updateLeaderboardScoreCorrection(String leaderboardName, String competitorIdAsString, String columnName, Double correctedScore, Date date) throws NoWindException { return getService().apply( new UpdateLeaderboardScoreCorrection(leaderboardName, columnName, competitorIdAsString, correctedScore, @@ -2099,8 +2241,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Void updateLeaderboardScoreCorrectionMetadata(String leaderboardName, Date timePointOfLastCorrectionValidity, String comment) { - return getService().apply( + public void updateLeaderboardScoreCorrectionMetadata(String leaderboardName, Date timePointOfLastCorrectionValidity, String comment) { + getService().apply( new UpdateLeaderboardScoreCorrectionMetadata(leaderboardName, timePointOfLastCorrectionValidity == null ? null : new MillisecondsTimePoint(timePointOfLastCorrectionValidity), comment)); @@ -2173,24 +2315,22 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S // TODO: delete getSwissTimingAdapter().getSwissTimingRaceRecords() method // TODO: delete SwissTimingDomainFactory.getRaceTypeFromRaceID(String raceID) - EventResultDescriptor eventResult = null; - if("test".equalsIgnoreCase(eventJsonURL)) { - eventResult = TestManage2SailEventResultsParser.getTestEventResult(); - } else { - URL url = new URL(eventJsonURL); - URLConnection eventResultConn = url.openConnection(); - Manage2SailEventResultsParserImpl parser = new Manage2SailEventResultsParserImpl(); - eventResult = parser.getEventResult((InputStream) eventResultConn.getContent()); - } + URL url = new URL(eventJsonURL); + URLConnection eventResultConn = url.openConnection(); + Manage2SailEventResultsParserImpl parser = new Manage2SailEventResultsParserImpl(); + EventResultDescriptor eventResult = parser.getEventResult((InputStream) eventResultConn.getContent()); if (eventResult != null) { for (RegattaResultDescriptor regattaResult : eventResult.getRegattaResults()) { for(RaceResultDescriptor race: regattaResult.getRaceResults()) { - SwissTimingRaceRecordDTO swissTimingRaceRecordDTO = new SwissTimingRaceRecordDTO(race.getId(), race.getName(), race.getStartTimeUTC()); - swissTimingRaceRecordDTO.boatClass = regattaResult.getIsafId() != null && !regattaResult.getIsafId().isEmpty() ? regattaResult.getIsafId() : regattaResult.getClassName(); - swissTimingRaceRecordDTO.gender = regattaResult.getCompetitorGenderType().name(); - swissTimingRaceRecordDTO.hasCourse = false; - swissTimingRaceRecordDTO.hasStartlist = false; - swissTimingRaces.add(swissTimingRaceRecordDTO); + // add only the tracked races + if(race.isTracked() != null && race.isTracked() == true) { + SwissTimingRaceRecordDTO swissTimingRaceRecordDTO = new SwissTimingRaceRecordDTO(race.getId(), race.getName(), + regattaResult.getName(), race.getSeriesName(), race.getFleetName(), race.getStatus(), race.getStartTime(), + regattaResult.getXrrEntriesUrl() != null ? regattaResult.getXrrEntriesUrl().toExternalForm() : null); + swissTimingRaceRecordDTO.boatClass = regattaResult.getIsafId() != null && !regattaResult.getIsafId().isEmpty() ? regattaResult.getIsafId() : regattaResult.getClassName(); + swissTimingRaceRecordDTO.gender = regattaResult.getCompetitorGenderType().name(); + swissTimingRaces.add(swissTimingRaceRecordDTO); + } } } result = new SwissTimingEventRecordDTO(eventResult.getId(), eventResult.getName(), eventResult.getTrackingDataHost(), @@ -2211,10 +2351,27 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S boolean trackWind, final boolean correctWindByDeclination) throws Exception { logger.info("tracWithSwissTiming for regatta " + regattaToAddTo + " for race records " + rrs + " with hostname " + hostname + " and port " + port); + Map cachedRegattaEntriesLists = new HashMap(); for (SwissTimingRaceRecordDTO rr : rrs) { BoatClass boatClass = getBaseDomainFactory().getOrCreateBoatClass(rr.boatClass); - final RacesHandle raceHandle = getSwissTimingAdapter().addSwissTimingRace(getService(), regattaToAddTo, rr.raceId, rr.raceName, - boatClass, hostname, port, + String raceDescription = rr.regattaName != null ? rr.regattaName : ""; + raceDescription += rr.seriesName != null ? "/" + rr.seriesName : ""; + raceDescription += raceDescription.length() > 0 ? "/" + rr.raceName : rr.raceName; + // try to find a cached entry list for the regatta + RegattaResults regattaResults = cachedRegattaEntriesLists.get(rr.xrrEntriesUrl); + if(regattaResults == null) { + regattaResults = getSwissTimingAdapter().readRegattaEntryListFromXrrUrl(rr.xrrEntriesUrl); + if(regattaResults != null) { + cachedRegattaEntriesLists.put(rr.xrrEntriesUrl, regattaResults); + } + } + StartList startList = null; + if(regattaResults != null) { + startList = getSwissTimingAdapter().readStartListForRace(rr.raceId, regattaResults); + } + // now read the entry list for the race from the result + final RacesHandle raceHandle = getSwissTimingAdapter().addSwissTimingRace(getService(), regattaToAddTo, rr.raceId, rr.raceName, raceDescription, + boatClass, hostname, port, startList, MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(mongoObjectFactory, domainObjectFactory), RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS); if (trackWind) { new Thread("Wind tracking starter for race " + rr.raceId + "/" + rr.raceName) { @@ -2402,8 +2559,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S public CompetitorRaceDataDTO call() throws NoWindException { Competitor competitor = getCompetitorByIdAsString(trackedRace.getRace().getCompetitors(), competitorDTO.getIdAsString()); - ArrayList> markPassingsData = new ArrayList>(); - ArrayList> raceData = new ArrayList>(); + ArrayList> markPassingsData = new ArrayList>(); + ArrayList> raceData = new ArrayList>(); // Filling the mark passings Set competitorMarkPassings = trackedRace.getMarkPassings(competitor); if (competitorMarkPassings != null) { @@ -2414,7 +2571,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Double competitorMarkPassingsData = getCompetitorRaceDataEntry(detailType, trackedRace, competitor, time, leaderboardGroupName, leaderboardName); if (competitorMarkPassingsData != null) { - markPassingsData.add(new Triple(markPassing + markPassingsData.add(new com.sap.sse.common.Util.Triple(markPassing .getWaypoint().getName(), time.asDate(), competitorMarkPassingsData)); } } @@ -2428,7 +2585,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Double competitorRaceData = getCompetitorRaceDataEntry(detailType, trackedRace, competitor, time, leaderboardGroupName, leaderboardName); if (competitorRaceData != null) { - raceData.add(new Pair(time.asDate(), competitorRaceData)); + raceData.add(new com.sap.sse.common.Util.Pair(time.asDate(), competitorRaceData)); } } } @@ -2455,7 +2612,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return result; } - private Triple, List> getTotalPointsForRaceColumn(LeaderboardDTO leaderboard, + private com.sap.sse.common.Util.Triple, List> getTotalPointsForRaceColumn(LeaderboardDTO leaderboard, RaceColumnDTO raceColumn) throws NoWindException { List values = new ArrayList(); List competitorDTOs = new ArrayList(); @@ -2464,13 +2621,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S values.add(leaderboardEntryDTO != null ? leaderboardEntryDTO.totalPoints : null); competitorDTOs.add(entry.getKey()); } - return new Triple, List>(raceColumn.getName(), competitorDTOs, values); + return new com.sap.sse.common.Util.Triple, List>(raceColumn.getName(), competitorDTOs, values); } @Override - public List, List>> getLeaderboardDataEntriesForAllRaceColumns(String leaderboardName, + public List, List>> getLeaderboardDataEntriesForAllRaceColumns(String leaderboardName, Date date, DetailType detailType) throws Exception { - List, List>> result = new ArrayList,List>>(); + List, List>> result = new ArrayList,List>>(); // Attention: The reason why we read the data from the LeaderboardDTO and not from the leaderboard directly is to ensure // the use of the leaderboard cache. final Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); @@ -2485,7 +2642,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S switch (detailType) { case REGATTA_TOTAL_POINTS: LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, Collections. emptyList(), - getService(), baseDomainFactory); + /* addOverallDetails */ false, getService(), baseDomainFactory); for (RaceColumnDTO raceColumnDTO : leaderboardDTO.getRaceList()) { result.add(getTotalPointsForRaceColumn(leaderboardDTO, raceColumnDTO)); } @@ -2505,7 +2662,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S competitorDTOs.add(baseDomainFactory.convertToCompetitorDTO(competitor)); rank++; } - result.add(new Triple, List>(e.getKey().getName(), competitorDTOs, values)); + result.add(new com.sap.sse.common.Util.Triple, List>(e.getKey().getName(), competitorDTOs, values)); } break; default: @@ -2518,7 +2675,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public List> getLeaderboardsNamesOfMetaleaderboard(String metaLeaderboardName) { + public List> getLeaderboardsNamesOfMetaleaderboard(String metaLeaderboardName) { Leaderboard leaderboard = getService().getLeaderboardByName(metaLeaderboardName); if (leaderboard == null) { throw new IllegalArgumentException("Couldn't find leaderboard named "+metaLeaderboardName); @@ -2526,10 +2683,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S if(!(leaderboard instanceof MetaLeaderboard)) { throw new IllegalArgumentException("The leaderboard "+metaLeaderboardName + " is not a metaleaderboard"); } - List> result = new ArrayList>(); + List> result = new ArrayList>(); MetaLeaderboard metaLeaderboard = (MetaLeaderboard) leaderboard; for(Leaderboard containedLeaderboard: metaLeaderboard.getLeaderboards()) { - result.add(new Pair(containedLeaderboard.getName(), + result.add(new com.sap.sse.common.Util.Pair(containedLeaderboard.getName(), containedLeaderboard.getDisplayName() != null ? containedLeaderboard.getDisplayName() : containedLeaderboard.getName())); } return result; @@ -2713,9 +2870,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } private LeaderboardGroupDTO convertToLeaderboardGroupDTO(LeaderboardGroup leaderboardGroup, boolean withGeoLocationData) { - LeaderboardGroupDTO groupDTO = new LeaderboardGroupDTO(); - groupDTO.setName(leaderboardGroup.getName()); - groupDTO.description = leaderboardGroup.getDescription(); + LeaderboardGroupDTO groupDTO = new LeaderboardGroupDTO(leaderboardGroup.getId(), leaderboardGroup.getName(), leaderboardGroup.getDescription()); groupDTO.displayLeaderboardsInReverseOrder = leaderboardGroup.isDisplayGroupsInReverseOrder(); for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { groupDTO.leaderboards.add(createStrippedLeaderboardDTO(leaderboard, withGeoLocationData)); @@ -2798,24 +2953,117 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public List getEvents() { + public List getEvents() throws MalformedURLException { + String requestBaseURL = getRequestBaseURL().toString(); List result = new ArrayList(); for (Event event : getService().getAllEvents()) { EventDTO eventDTO = convertToEventDTO(event); + eventDTO.setBaseURL(requestBaseURL); result.add(eventDTO); } return result; } @Override - public void updateEvent(UUID eventId, String eventName, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, List regattaNames) { - getService().apply(new UpdateEvent(eventId, eventName, new MillisecondsTimePoint(startDate), new MillisecondsTimePoint(endDate), venue.getName(), isPublic, regattaNames)); + public List getPublicEventsOfAllSailingServers() throws MalformedURLException { + List result = new ArrayList<>(); + for (EventDTO localEvent : getEvents()) { + result.add(localEvent); + } + for (Entry, Exception>> serverRefAndEventsOrException : + getService().getPublicEventsOfAllSailingServers().entrySet()) { + final com.sap.sse.common.Util.Pair, Exception> eventsOrException = serverRefAndEventsOrException.getValue(); + final RemoteSailingServerReference serverRef = serverRefAndEventsOrException.getKey(); + final Iterable remoteEvents = eventsOrException.getA(); + String baseURL = getBaseURL(serverRef.getURL()).toString(); + if (remoteEvents != null) { + for (EventBase remoteEvent : remoteEvents) { + EventBaseDTO remoteEventDTO = convertToEventDTO(remoteEvent); + remoteEventDTO.setBaseURL(baseURL); + result.add(remoteEventDTO); + } + } + } + return result; + } + + /** + * Determines the base URL (protocol, host and port parts) used for the currently executing servlet request. Defaults + * to http://sapsailing.com. + * @throws MalformedURLException + */ + private URL getRequestBaseURL() throws MalformedURLException { + final URL url = new URL(getThreadLocalRequest().getRequestURL().toString()); + final URL baseURL = getBaseURL(url); + return baseURL; + } + + private URL getBaseURL(URL url) throws MalformedURLException { + return new URL(url.getProtocol(), url.getHost(), url.getPort(), /* file */ ""); + } + + private RemoteSailingServerReferenceDTO createRemoteSailingServerReferenceDTO( + final RemoteSailingServerReference serverRef, + final com.sap.sse.common.Util.Pair, Exception> eventsOrException) { + final Iterable events = eventsOrException.getA(); + final Iterable eventDTOs; + final RemoteSailingServerReferenceDTO sailingServerDTO; + if (events == null) { + eventDTOs = null; + final Exception exception = eventsOrException.getB(); + sailingServerDTO = new RemoteSailingServerReferenceDTO(serverRef.getName(), + serverRef.getURL().toExternalForm(), exception==null?null:exception.getMessage()); + } else { + eventDTOs = convertToEventDTOs(events); + sailingServerDTO = new RemoteSailingServerReferenceDTO( + serverRef.getName(), serverRef + .getURL().toExternalForm(), eventDTOs); + } + return sailingServerDTO; + } + + private Iterable convertToEventDTOs(Iterable events) { + List result = new ArrayList<>(); + for (EventBase event : events) { + EventBaseDTO eventDTO = convertToEventDTO(event); + result.add(eventDTO); + } + return result; } @Override - public EventDTO createEvent(String eventName, Date startDate, Date endDate, String venue, boolean isPublic, List courseAreaNames) { + public EventDTO updateEvent(UUID eventId, String eventName, Date startDate, Date endDate, VenueDTO venue, + boolean isPublic, Iterable leaderboardGroupIds, Iterable imageURLStrings, Iterable videoURLStrings) throws MalformedURLException { + TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null; + TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null; + List imageURLs = createURLsFromStrings(imageURLStrings); + List videoURLs = createURLsFromStrings(videoURLStrings); + getService().apply(new UpdateEvent(eventId, eventName, startTimePoint, endTimePoint, venue.getName(), isPublic, leaderboardGroupIds, imageURLs, videoURLs)); + return getEventById(eventId); + } + + /** + * @param urlStrings + * @return + * @throws MalformedURLException + */ + private List createURLsFromStrings(Iterable urlStrings) throws MalformedURLException { + List imageURLs = new ArrayList<>(); + for (String imageURLString : urlStrings) { + imageURLs.add(new URL(imageURLString)); + } + return imageURLs; + } + + @Override + public EventDTO createEvent(String eventName, Date startDate, Date endDate, String venue, boolean isPublic, List courseAreaNames, + Iterable imageURLs, Iterable videoURLs) throws MalformedURLException { UUID eventUuid = UUID.randomUUID(); - getService().apply(new CreateEvent(eventName, new MillisecondsTimePoint(startDate), new MillisecondsTimePoint(endDate), venue, isPublic, eventUuid)); + TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null; + TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null; + getService().apply( + new CreateEvent(eventName, startTimePoint, endTimePoint, venue, isPublic, eventUuid, createURLsFromStrings(imageURLs), + createURLsFromStrings(videoURLs))); for (String courseAreaName : courseAreaNames) { createCourseArea(eventUuid, courseAreaName); } @@ -2867,14 +3115,40 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return result; } - private EventDTO convertToEventDTO(Event event) { - EventDTO eventDTO = new EventDTO(event.getName()); + private EventBaseDTO convertToEventDTO(EventBase event) { + final EventBaseDTO eventDTO; + if (event == null) { + eventDTO = null; + } else { + List lgDTOs = new ArrayList<>(); + if (event.getLeaderboardGroups() != null) { + for (LeaderboardGroupBase lgBase : event.getLeaderboardGroups()) { + lgDTOs.add(convertToLeaderboardGroupBaseDTO(lgBase)); + } + } + eventDTO = new EventBaseDTO(event.getName(), lgDTOs); + copyEventBaseFieldToDTO(event, eventDTO); + } + return eventDTO; + } + + private LeaderboardGroupBaseDTO convertToLeaderboardGroupBaseDTO(LeaderboardGroupBase leaderboardGroupBase) { + return new LeaderboardGroupBaseDTO(leaderboardGroupBase.getId(), leaderboardGroupBase.getName(), + leaderboardGroupBase.getDescription(), leaderboardGroupBase.hasOverallLeaderboard()); + } + + private void copyEventBaseFieldToDTO(EventBase event, EventBaseDTO eventDTO) { eventDTO.venue = new VenueDTO(); eventDTO.venue.setName(event.getVenue() != null ? event.getVenue().getName() : null); eventDTO.startDate = event.getStartDate() != null ? event.getStartDate().asDate() : null; eventDTO.endDate = event.getStartDate() != null ? event.getEndDate().asDate() : null; eventDTO.isPublic = event.isPublic(); - eventDTO.id = event.getId(); + eventDTO.id = (UUID) event.getId(); + } + + private EventDTO convertToEventDTO(Event event) { + EventDTO eventDTO = new EventDTO(event.getName()); + copyEventBaseFieldToDTO(event, eventDTO); eventDTO.regattas = new ArrayList(); for (Regatta regatta: event.getRegattas()) { RegattaDTO regattaDTO = new RegattaDTO(); @@ -2887,6 +3161,15 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S CourseAreaDTO courseAreaDTO = convertToCourseAreaDTO(courseArea); eventDTO.venue.getCourseAreas().add(courseAreaDTO); } + for (LeaderboardGroup lg : event.getLeaderboardGroups()) { + eventDTO.addLeaderboardGroup(convertToLeaderboardGroupDTO(lg, /* withGeoLocationData */false)); + } + for (URL imageURL : event.getImageURLs()) { + eventDTO.addImageURL(imageURL.toString()); + } + for (URL videoURL : event.getVideoURLs()) { + eventDTO.addVideoURL(videoURL.toString()); + } return eventDTO; } @@ -3110,6 +3393,41 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return result; } + @Override + public List getRemoteSailingServerReferences() { + List result = new ArrayList(); + for (Entry, Exception>> remoteSailingServerRefAndItsCachedEvent : + getService().getPublicEventsOfAllSailingServers().entrySet()) { + RemoteSailingServerReferenceDTO dto = createRemoteSailingServerReferenceDTO( + remoteSailingServerRefAndItsCachedEvent.getKey(), + remoteSailingServerRefAndItsCachedEvent.getValue()); + result.add(dto); + } + return result; + } + + @Override + public void removeSailingServers(Set namesOfSailingServersToRemove) throws Exception { + for (String serverName : namesOfSailingServersToRemove) { + getService().apply(new RemoveRemoteSailingServerReference(serverName)); + } + } + + @Override + public RemoteSailingServerReferenceDTO addRemoteSailingServerReference(RemoteSailingServerReferenceDTO sailingServer) throws MalformedURLException { + final String expandedURL; + if (sailingServer.getUrl().contains("//")) { + expandedURL = sailingServer.getUrl(); + } else { + expandedURL = "http://" + sailingServer.getUrl(); + } + URL serverURL = new URL(expandedURL); + RemoteSailingServerReference serverRef = getService().apply(new AddRemoteSailingServerReference(sailingServer.getName(), serverURL)); + com.sap.sse.common.Util.Pair, Exception> eventsOrException = getService().updateRemoteServerEventCacheSynchronously(serverRef); + return createRemoteSailingServerReferenceDTO(serverRef, eventsOrException); + + } + @Override public List getResultImportUrls(String resultProviderName) { List result = new ArrayList(); @@ -3444,7 +3762,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public List getLeaderboardGroupNamesFromRemoteServer(String url) { - Pair hostnameAndPort = parseHostAndPort(url); + com.sap.sse.common.Util.Pair hostnameAndPort = parseHostAndPort(url); String hostname = hostnameAndPort.getA(); int port = hostnameAndPort.getB(); final String path = "/sailingserver/api/v1/leaderboardgroups"; @@ -3494,7 +3812,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } - private Pair parseHostAndPort(String urlAsString) { + private com.sap.sse.common.Util.Pair parseHostAndPort(String urlAsString) { String hostname; Integer port = 80; try { @@ -3520,7 +3838,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } } } - return new Pair(hostname, port); + return new com.sap.sse.common.Util.Pair(hostname, port); } @Override @@ -3537,7 +3855,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S long startTime = System.currentTimeMillis(); getService().createOrUpdateDataImportProgressWithReplication(importOperationId, 0.01, "Setting up connection", 0.5); - Pair hostnameAndPort = parseHostAndPort(urlAsString); + com.sap.sse.common.Util.Pair hostnameAndPort = parseHostAndPort(urlAsString); String hostname = hostnameAndPort.getA(); int port = hostnameAndPort.getB(); String query; @@ -3637,18 +3955,40 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Iterable getCompetitorsOfLeaderboard(String leaderboardName) { - Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); - return convertToCompetitorDTOs(leaderboard.getAllCompetitors()); + public Iterable getCompetitorsOfLeaderboard(String leaderboardName, boolean lookInRaceLogs) { + if (lookInRaceLogs) { + Set result = new HashSet(); + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { + for (Fleet fleet : raceColumn.getFleets()) { + RaceLog raceLog = raceColumn.getRaceLog(fleet); + result.addAll(new RegisteredCompetitorsAnalyzer(raceLog).analyze()); + } + } + return convertToCompetitorDTOs(result); + } else { + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + return convertToCompetitorDTOs(leaderboard.getAllCompetitors()); + } } @Override - public CompetitorDTO updateCompetitor(CompetitorDTO competitor) { + public CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor) { + Nationality nationality = (competitor.getThreeLetterIocCountryCode() == null || competitor.getThreeLetterIocCountryCode().isEmpty()) ? null : + getBaseDomainFactory().getOrCreateNationality(competitor.getThreeLetterIocCountryCode()); + //new competitor + if (competitor.getIdAsString() == null || competitor.getIdAsString().isEmpty()) { + BoatClass boatClass = getBaseDomainFactory().getOrCreateBoatClass(competitor.getBoatClass().getName()); + DynamicPerson sailor = new PersonImpl(competitor.getName(), nationality, null, null); + DynamicTeam team = new TeamImpl(competitor.getName() + " team", Collections.singleton(sailor), null); + DynamicBoat boat = new BoatImpl(competitor.getName() + " boat", boatClass, competitor.getSailID()); + return getBaseDomainFactory().convertToCompetitorDTO(getBaseDomainFactory().getOrCreateCompetitor(UUID.randomUUID(), competitor.getName(), competitor.getColor(), + team, boat)); + } + return getBaseDomainFactory().convertToCompetitorDTO( getService().apply(new UpdateCompetitor(competitor.getIdAsString(), competitor.getName(), - competitor.getColor(), competitor.getSailID(), - (competitor.getThreeLetterIocCountryCode() == null || competitor.getThreeLetterIocCountryCode().isEmpty()) ? null : - getBaseDomainFactory().getOrCreateNationality(competitor.getThreeLetterIocCountryCode())))); + competitor.getColor(), competitor.getSailID(), nationality))); } @Override @@ -3819,12 +4159,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Triple getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName) { - Triple result = getService().getStartTimeAndProcedure(leaderboardName, raceColumnName, fleetName); + public com.sap.sse.common.Util.Triple getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName) { + com.sap.sse.common.Util.Triple result = getService().getStartTimeAndProcedure(leaderboardName, raceColumnName, fleetName); if (result == null || result.getA() == null) { return null; } - return new Triple(result.getA() == null ? null : result.getA().asDate(), result.getB(), result.getC()); + return new com.sap.sse.common.Util.Triple(result.getA() == null ? null : result.getA().asDate(), result.getB(), result.getC()); } @Override @@ -3840,6 +4180,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return igtimiAdapterTracker.getService(); } + protected RaceLogTrackingAdapter getRaceLogTrackingAdapter() { + return raceLogTrackingAdapterTracker.getService().getAdapter(getBaseDomainFactory()); + } + @Override public String getIgtimiAuthorizationUrl() { return getIgtimiConnectionFactory().getAuthorizationUrl(); @@ -3931,4 +4275,424 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } -} \ No newline at end of file + @Override + public void denoteForRaceLogTracking(String leaderboardName, + String raceColumnName, String fleetName) throws Exception { + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + RaceColumn raceColumn = getService().getLeaderboardByName(leaderboardName).getRaceColumnByName(raceColumnName); + Fleet fleet = raceColumn.getFleetByName(fleetName); + + getRaceLogTrackingAdapter().denoteRaceForRaceLogTracking(getService(), leaderboard, raceColumn, fleet, null); + } + + @Override + public void removeDenotationForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName) { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + getRaceLogTrackingAdapter().removeDenotationForRaceLogTracking(getService(), raceLog); + } + + @Override + public void denoteForRaceLogTracking(String leaderboardName) throws Exception { + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + getRaceLogTrackingAdapter().denoteAllRacesForRaceLogTracking(getService(), leaderboard); + } + + /** + * @param triple leaderboard and racecolumn and fleet names + * @return + */ + private RaceLog getRaceLog(com.sap.sse.common.Util.Triple triple) { + return getRaceLog(triple.getA(), triple.getB(), triple.getC()); + } + + private RaceLog getRaceLog(String leaderboardName, String raceColumnName, String fleetName) { + RaceColumn raceColumn = getService().getLeaderboardByName(leaderboardName).getRaceColumnByName(raceColumnName); + Fleet fleet = raceColumn.getFleetByName(fleetName); + return raceColumn.getRaceLog(fleet); + } + + @Override + public Collection getCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName) { + return convertToCompetitorDTOs( + new RegisteredCompetitorsAnalyzer( + getRaceLog(leaderboardName, raceColumnName, fleetName)).analyze()); + } + + private Competitor getCompetitor(CompetitorDTO dto) { + return getService().getCompetitorStore().getExistingCompetitorByIdAsString(dto.getIdAsString()); + } + + @Override + public void setCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName, + Set competitorDTOs) { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + Set competitors = new HashSet(); + for (CompetitorDTO dto : competitorDTOs) { + competitors.add(getCompetitor(dto)); + } + + getRaceLogTrackingAdapter().registerCompetitors(getService(), raceLog, competitors); + } + + private Mark convertToMark(MarkDTO dto, boolean resolve) { + if (resolve) { + Mark existing = baseDomainFactory.getExistingMarkByIdAsString(dto.getIdAsString()); + if (existing != null) { + return existing; + } + } + + Serializable id = UUID.randomUUID(); + + return baseDomainFactory.getOrCreateMark(id, dto.getName(), dto.type, dto.color, dto.shape, dto.pattern); + } + + /** + * Also finds the last position of the marks, if set by pinging them + */ + private Collection convertToMarkDTOs(RaceLog raceLog, Iterable marks) { + List dtos = new ArrayList(); + for (Mark mark : marks) { + DynamicGPSFixTrackImpl track = new DynamicGPSFixTrackImpl(mark, 0); + DeviceMappingEvent lastPingMappingEvent = new LastPingMappingEventFinder(raceLog, mark).analyze(); + if (lastPingMappingEvent != null) { + try { + getService().getGPSFixStore().loadMarkTrack(track, DeviceMappingImpl.convertToDeviceMapping(lastPingMappingEvent)); + } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { + logger.log(Level.WARNING, "Could not convert to Mark DTO: " + mark); + } + } + Position lastPos = track.getLastRawFix() == null ? null : track.getLastRawFix().getPosition(); + dtos.add(convertToMarkDTO(mark, lastPos)); + } + return dtos; + } + + @Override + public void addMarkToRaceLog(String leaderboardName, String raceColumnName, String fleetName, MarkDTO markDTO) { + Mark mark = convertToMark(markDTO, false); + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + RaceLogEvent event = RaceLogEventFactory.INSTANCE.createDefineMarkEvent(MillisecondsTimePoint.now(), + getService().getServerAuthor(), raceLog.getCurrentPassId(), mark); + raceLog.add(event); + } + + @Override + public Collection getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName) { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + Collection marks = new DefinedMarkFinder(raceLog).analyze(); + return convertToMarkDTOs(raceLog, marks); + } + + @Override + public void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName, + List> courseDTO) { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + String name = String.format("Course for %s - %s - %s", leaderboardName, raceColumnName, fleetName); + + CourseBase lastPublishedCourse = new LastPublishedCourseDesignFinder(raceLog).analyze(); + if (lastPublishedCourse == null) { + lastPublishedCourse = new CourseDataImpl(name); + } + + List> waypoints = resolveControlPoints(lastPublishedCourse, courseDTO); + Course course = new CourseImpl(name, lastPublishedCourse.getWaypoints()); + + try { + course.update(waypoints, baseDomainFactory); + } catch (Exception e) { + throw new RuntimeException(e); + } + + RaceLogEvent event = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent(MillisecondsTimePoint.now(), + getService().getServerAuthor(), raceLog.getCurrentPassId(), course); + raceLog.add(event); + } + + private WaypointDTO convertToWaypointDTO(Waypoint waypoint, Map controlPointCache) { + ControlPointDTO cp = controlPointCache.get(waypoint.getControlPoint().getId()); + if (cp == null) { + cp = convertToControlPointDTO(waypoint.getControlPoint()); + controlPointCache.put(waypoint.getControlPoint().getId(), cp); + } + return new WaypointDTO(waypoint.getName(), cp, waypoint.getPassingInstructions()); + } + + private RaceCourseDTO convertToRaceCourseDTO(CourseBase course) { + List waypointDTOs = new ArrayList(); + Map controlPointCache = new HashMap<>(); + RaceCourseDTO result = new RaceCourseDTO(waypointDTOs); + for (Waypoint waypoint : course.getWaypoints()) { + waypointDTOs.add(convertToWaypointDTO(waypoint, controlPointCache)); + } + return result; + } + + @Override + public RaceCourseDTO getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, + String fleetName) { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + + CourseBase lastPublishedCourse = new LastPublishedCourseDesignFinder(raceLog).analyze(); + if (lastPublishedCourse == null) { + lastPublishedCourse = new CourseDataImpl(""); + } + + return convertToRaceCourseDTO(lastPublishedCourse); + } + + private Position convertToPosition(PositionDTO dto) { + return new DegreePosition(dto.latDeg, dto.lngDeg); + } + + @Override + public void pingMarkViaRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, + MarkDTO markDTO, PositionDTO positionDTO) { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + Mark mark = convertToMark(markDTO, true); + TimePoint time = MillisecondsTimePoint.now(); + Position position = convertToPosition(positionDTO); + GPSFix fix = new GPSFixImpl(position, time); + + getRaceLogTrackingAdapter().pingMark(raceLog, mark, fix, getService()); + } + + @Override + public void copyCourseAndCompetitorsToOtherRaceLogs(com.sap.sse.common.Util.Triple fromTriple, + Set> toTriples) { + RaceLog fromRaceLog = getRaceLog(fromTriple); + Set toRaceLogs = new HashSet<>(); + for (com.sap.sse.common.Util.Triple toTriple : toTriples) { + toRaceLogs.add(getRaceLog(toTriple)); + } + + getRaceLogTrackingAdapter().copyCourseAndCompetitors(fromRaceLog, toRaceLogs, baseDomainFactory, getService()); + } + + private TypeBasedServiceFinder getDeviceIdentifierStringSerializerHandlerFinder( + boolean withFallback) { + TypeBasedServiceFinderFactory factory = getService().getTypeBasedServiceFinderFactory(); + TypeBasedServiceFinder finder = factory.createServiceFinder(DeviceIdentifierStringSerializationHandler.class); + if (withFallback) { + finder.setFallbackService(new PlaceHolderDeviceIdentifierStringSerializationHandler()); + } + return finder; + } + + private DeviceIdentifier deserializeDeviceIdentifier(String type, String deviceId) throws NoCorrespondingServiceRegisteredException, + TransformationException { + DeviceIdentifierStringSerializationHandler handler = + getDeviceIdentifierStringSerializerHandlerFinder(false).findService(type); + return handler.deserialize(deviceId, type, null); + } + + private String serializeDeviceIdentifier(DeviceIdentifier deviceId) throws TransformationException { + return getDeviceIdentifierStringSerializerHandlerFinder(true).findService( + deviceId.getIdentifierType()).serialize(deviceId).getB(); + } + + private DeviceMappingDTO convertToDeviceMappingDTO(DeviceMapping mapping) throws TransformationException { + String deviceId = serializeDeviceIdentifier(mapping.getDevice()); + Date from = mapping.getTimeRange().from() == null || mapping.getTimeRange().from().asMillis() == Long.MIN_VALUE ? + null : mapping.getTimeRange().from().asDate(); + Date to = mapping.getTimeRange().to() == null || mapping.getTimeRange().to().asMillis() == Long.MAX_VALUE ? + null : mapping.getTimeRange().to().asDate(); + Serializable item = null; + if (mapping.getMappedTo() instanceof Competitor) { + item = baseDomainFactory.convertToCompetitorDTO((Competitor) mapping.getMappedTo()); + } else if (mapping.getMappedTo() instanceof Mark) { + item = convertToMarkDTO((Mark) mapping.getMappedTo(), null); + } else { + throw new RuntimeException("Can only handle Competitor or Mark as mapped item type"); + } + return new DeviceMappingDTO(new DeviceIdentifierDTO(mapping.getDevice().getIdentifierType(), + deviceId), from, to, item, mapping.getOriginalRaceLogEventIds()); + } + + @Override + public List getDeviceMappingsFromRaceLog(String leaderboardName, String raceColumnName, + String fleetName) throws TransformationException { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + List result = new ArrayList(); + for (List> list : new DeviceCompetitorMappingFinder(raceLog).analyze().values()) { + for (DeviceMapping mapping : list) { + result.add(convertToDeviceMappingDTO(mapping)); + } + } + for (List> list : new DeviceMarkMappingFinder(raceLog).analyze().values()) { + for (DeviceMapping mapping : list) { + result.add(convertToDeviceMappingDTO(mapping)); + } + } + return result; + } + + @Override + public void addDeviceMappingToRaceLog(String leaderboardName, String raceColumnName, String fleetName, + DeviceMappingDTO dto) throws NoCorrespondingServiceRegisteredException, TransformationException { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + DeviceMapping mapping = convertToDeviceMapping(dto); + TimePoint now = MillisecondsTimePoint.now(); + DeviceMappingEvent event = null; + TimePoint from = mapping.getTimeRange().openBeginning() ? null : mapping.getTimeRange().from(); + TimePoint to = mapping.getTimeRange().openEnd() ? null : mapping.getTimeRange().to(); + if (dto.mappedTo instanceof MarkDTO) { + Mark mark = convertToMark(((MarkDTO) dto.mappedTo), true); + event = RaceLogEventFactory.INSTANCE.createDeviceMarkMappingEvent(now, getService().getServerAuthor(), + mapping.getDevice(), mark, raceLog.getCurrentPassId(), from, to); + } else if (dto.mappedTo instanceof CompetitorDTO) { + Competitor competitor = getService().getCompetitorStore().getExistingCompetitorByIdAsString( + ((CompetitorDTO) dto.mappedTo).getIdAsString()); + event = RaceLogEventFactory.INSTANCE.createDeviceCompetitorMappingEvent(now, getService().getServerAuthor(), + mapping.getDevice(), competitor, raceLog.getCurrentPassId(), from, to); + } else { + throw new RuntimeException("Can only map devices to competitors or marks"); + } + raceLog.add(event); + } + + private ServiceTracker + createAndOpenDeviceIdentifierStringSerializationHandlerTracker(BundleContext context) { + ServiceTracker tracker = + new ServiceTracker( + context, DeviceIdentifierStringSerializationHandler.class, null); + + tracker.open(); + return tracker; + } + + @Override + public List getDeserializableDeviceIdentifierTypes() { + List result = new ArrayList(); + for (ServiceReference reference : + deviceIdentifierStringSerializationHandlerTracker.getServiceReferences()) { + result.add((String) reference.getProperty(TypeBasedServiceFinder.TYPE)); + } + return result; + } + + DeviceMapping convertToDeviceMapping(DeviceMappingDTO dto) throws NoCorrespondingServiceRegisteredException, TransformationException { + DeviceIdentifier device = deserializeDeviceIdentifier(dto.deviceIdentifier.deviceType, dto.deviceIdentifier.deviceId); + TimePoint from = dto.from == null ? null : new MillisecondsTimePoint(dto.from); + TimePoint to = dto.to == null ? null : new MillisecondsTimePoint(dto.to); + TimeRange timeRange = new TimeRangeImpl(from, to); + if (dto.mappedTo instanceof MarkDTO) { + Mark mark = convertToMark(((MarkDTO) dto.mappedTo), true); + return new DeviceMappingImpl(mark, device, timeRange,dto.originalRaceLogEventIds); + } else if (dto.mappedTo instanceof CompetitorDTO) { + Competitor competitor = getService().getCompetitorStore().getExistingCompetitorByIdAsString( + ((CompetitorDTO) dto.mappedTo).getIdAsString()); + return new DeviceMappingImpl(competitor, device, timeRange, dto.originalRaceLogEventIds); + } else { + throw new RuntimeException("Can only map devices to competitors or marks"); + } + } + + @Override + public void closeOpenEndedDeviceMapping(String leaderboardName, String raceColumnName, String fleetName, + DeviceMappingDTO mappingDTO, Date closingTimePoint) throws NoCorrespondingServiceRegisteredException, TransformationException { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + DeviceMapping mapping = convertToDeviceMapping(mappingDTO); + List closingEvents = + new OpenEndedDeviceMappingCloser(raceLog, mapping, getService().getServerAuthor(), + new MillisecondsTimePoint(closingTimePoint)).analyze(); + + for (RaceLogEvent event : closingEvents) { + raceLog.add(event); + } + } + + @Override + public void revokeRaceLogEvents(String leaderboardName, String raceColumnName, String fleetName, + List eventIds) throws NotRevokableException { + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); + for (Serializable idToRevoke : eventIds) { + raceLog.lockForRead(); + RaceLogEvent event = raceLog.getEventById(idToRevoke); + raceLog.unlockAfterRead(); + if (event instanceof Revokable) { + raceLog.revokeEvent(getService().getServerAuthor(), (Revokable) event); + } + } + } + + @Override + public void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName) + throws NotDenotedForRaceLogTrackingException, Exception { + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); + Fleet fleet = raceColumn.getFleetByName(fleetName); + + getRaceLogTrackingAdapter().startTracking(getService(), leaderboard, raceColumn, fleet); + } + + @Override + public Collection getGPSFixImporterTypes() { + Set result = new HashSet<>(); + Collection> refs = Collections.emptyList(); + try { + refs = Activator.getDefault(). getServiceReferences(GPSFixImporter.class, null); + } catch (InvalidSyntaxException e) { + //shouldn't happen, as we are passing null for the filter + } + for (ServiceReference ref : refs) { + result.add((String) ref.getProperty(TypeBasedServiceFinder.TYPE)); + } + return result; + } + + @Override + public List getTrackFileImportDeviceIds(List uuids) + throws NoCorrespondingServiceRegisteredException, TransformationException { + List result = new ArrayList<>(); + for (String uuidAsString : uuids) { + UUID uuid = UUID.fromString(uuidAsString); + TrackFileImportDeviceIdentifier device = TrackFileImportDeviceIdentifierImpl.getOrCreate(uuid); + long numFixes = getService().getGPSFixStore().getNumberOfFixes(device); + TimeRange timeRange = getService().getGPSFixStore().getTimeRangeCoveredByFixes(device); + Date from = timeRange == null ? null : timeRange.from().asDate(); + Date to = timeRange == null ? null : timeRange.to().asDate(); + result.add(new TrackFileImportDeviceIdentifierDTO(uuidAsString, device.getFileName(), device.getTrackName(), + numFixes, from, to)); + } + return result; + } + + @Override + public Iterable getSearchServerNames() { + List result = new ArrayList<>(); + for (RemoteSailingServerReference remoteServerRef : getService().getLiveRemoteServerReferences()) { + result.add(remoteServerRef.getName()); + } + return result; + } + + @Override + public Iterable search(String serverNameOrNullForMain, KeywordQuery query) throws MalformedURLException { + final List result = new ArrayList<>(); + if (serverNameOrNullForMain == null) { + Result searchResult = getService().search(query); + for (LeaderboardSearchResult hit : searchResult.getHits()) { + result.add(createLeaderboardSearchResultDTO(hit, getRequestBaseURL())); + } + } else { + RemoteSailingServerReference remoteRef = getService().getRemoteServerReferenceByName(serverNameOrNullForMain); + for (LeaderboardSearchResultBase hit : getService().searchRemotely(serverNameOrNullForMain, query).getHits()) { + result.add(createLeaderboardSearchResultDTO(hit, remoteRef.getURL())); + } + } + return result; + } + + private LeaderboardSearchResultDTO createLeaderboardSearchResultDTO(LeaderboardSearchResultBase leaderboardSearchResult, URL baseURL) { + ArrayList leaderboardGroups = new ArrayList<>(); + for (LeaderboardGroupBase lgb : leaderboardSearchResult.getLeaderboardGroups()) { + LeaderboardGroupBaseDTO leaderboardGroupDTO = convertToLeaderboardGroupBaseDTO(lgb); + leaderboardGroups.add(leaderboardGroupDTO); + } + return new LeaderboardSearchResultDTO(baseURL.toString(), leaderboardSearchResult.getLeaderboard().getName(), + leaderboardSearchResult.getLeaderboard().getDisplayName(), leaderboardSearchResult.getRegattaName(), + leaderboardSearchResult.getBoatClassName(), convertToEventDTO(leaderboardSearchResult.getEvent()), + leaderboardGroups); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java index 4368d397918..2065925c1c8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java @@ -24,7 +24,6 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.NauticalMileDistance; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.gwt.ui.client.SimulatorService; import com.sap.sailing.gwt.ui.shared.BoatClassDTOsAndNotificationMessage; @@ -75,6 +74,7 @@ import com.sap.sailing.simulator.windfield.WindControlParameters; import com.sap.sailing.simulator.windfield.WindFieldGenerator; import com.sap.sailing.simulator.windfield.WindFieldGeneratorFactory; import com.sap.sailing.simulator.windfield.impl.WindFieldGeneratorMeasured; +import com.sap.sse.common.Util; public class SimulatorServiceImpl extends RemoteServiceServlet implements SimulatorService { private static final long serialVersionUID = 4445427185387524086L; @@ -199,7 +199,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula throw new WindPatternNotFoundException("Please select a valid wind pattern."); } - Position[][] grid = bd.extractGrid(params.getxRes(), params.getyRes()); + Position[][] grid = bd.extractGrid(params.getxRes(), params.getyRes(), params.getBorderY(), params.getBorderX()); wf.setPositionGrid(grid); TimePoint startTime = new MillisecondsTimePoint(params.getStartTime().getTime());// new @@ -217,7 +217,8 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula wf.setGridAreaGps(gridAreaGps); } - WindFieldDTO wfDTO = createWindFieldDTO(wf, startTime, endTime, timeStep, params.isShowLines(), params.getSeedLines()); + WindFieldDTO wfDTO = createWindFieldDTO(wf, startTime, endTime, timeStep, params); //params.isShowStreamlets2(), params.isShowLines(), params.getSeedLines()); + LOGGER.info("Exiting getWindField"); return wfDTO; @@ -273,9 +274,11 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula wf.setGridAreaGps(gridAreaGps); } - int[] gridRes = new int[2]; + int[] gridRes = new int[4]; gridRes[0] = params.getxRes(); gridRes[1] = params.getyRes(); + gridRes[2] = params.getBorderY(); + gridRes[3] = params.getBorderX(); wf.setGridResolution(gridRes); wf.generate(startTime, null, timeStep); @@ -299,7 +302,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula WindFieldDTO windFieldDTO = null; if (pattern != null) { - windFieldDTO = this.createWindFieldDTO(wf, startTime, endTime, timeStep, params.isShowLines(), params.getSeedLines()); + windFieldDTO = this.createWindFieldDTO(wf, startTime, endTime, timeStep, params); // params.isShowStreamlets2(), params.isShowLines(), params.getSeedLines()); } return new SimulatorResultsDTO(rcDTO, pathDTOs, windFieldDTO, simulatedPaths.notificationMessage); @@ -333,7 +336,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula public PolarDiagramDTOAndNotificationMessage getPolarDiagram(Double bearingStep, int boatClassIndex) throws ConfigurationException { - Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(boatClassIndex); + Util.Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(boatClassIndex); NavigableMap> navMap = polarDiagramAndNotificationMessage.getA() .polarDiagramPlot(bearingStep); @@ -374,7 +377,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula // this.stepSizeMeters); // int noOfPointsMinus1 = points.size() - 1; - Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(requestData.selection.boatClassIndex); + Util.Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(requestData.selection.boatClassIndex); PolarDiagram polarDiagram = polarDiagramAndNotificationMessage.getA(); String notificationMessage = polarDiagramAndNotificationMessage.getB(); @@ -467,7 +470,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula List points = SimulatorServiceUtils.getIntermediatePoints2(requestData.turnPoints, this.stepSizeMeters); int noOfPointsMinus1 = points.size() - 1; - Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(requestData.selection.boatClassIndex); + Util.Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(requestData.selection.boatClassIndex); PolarDiagram polarDiagram = polarDiagramAndNotificationMessage.getA(); String notificationMessage = polarDiagramAndNotificationMessage.getB(); @@ -528,7 +531,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula @Override public Response1TurnerDTO get1Turner(Request1TurnerDTO requestData) throws ConfigurationException { - Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(requestData.selection.boatClassIndex); + Util.Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(requestData.selection.boatClassIndex); PolarDiagram polarDiagram = polarDiagramAndNotificationMessage.getA(); String notificationMessage = polarDiagramAndNotificationMessage.getB(); @@ -721,40 +724,115 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula return result; } - private WindFieldDTO createWindFieldDTO(WindFieldGenerator wf, TimePoint startTime, TimePoint endTime, TimePoint timeStep, boolean isShowLines, - char seedLines) { + private WindFieldDTO createWindFieldDTO(WindFieldGenerator wf, TimePoint startTime, TimePoint endTime, TimePoint timeStep, WindFieldGenParamsDTO params) { + //boolean isStreamlets, boolean isShowLines, char seedLines) { WindFieldDTO windFieldDTO = new WindFieldDTO(); - List wList = new ArrayList(); Position[][] positionGrid = wf.getPositionGrid(); + + //if (!params.isShowStreamlets2()) { + + List wList = new ArrayList(); if (positionGrid != null && positionGrid.length > 0) { - TimePoint t = startTime; - while (t.compareTo(endTime) <= 0) { - for (int i = 0; i < positionGrid.length; ++i) { - for (int j = 0; j < positionGrid[i].length; ++j) { - Wind localWind = wf.getWind(new TimedPositionWithSpeedImpl(t, positionGrid[i][j], null)); - LOGGER.finer(localWind.toString()); - wList.add(createSimulatorWindDTO(localWind)); - } - } - t = new MillisecondsTimePoint(t.asMillis() + timeStep.asMillis()); - } + TimePoint t = startTime; + while (t.compareTo(endTime) <= 0) { + for (int i = 0; i < positionGrid.length; ++i) { + for (int j = 0; j < positionGrid[i].length; ++j) { + Wind localWind = wf.getWind(new TimedPositionWithSpeedImpl(t, positionGrid[i][j], null)); + LOGGER.finer(localWind.toString()); + wList.add(createSimulatorWindDTO(localWind)); + } + } + t = new MillisecondsTimePoint(t.asMillis() + timeStep.asMillis()); + } } windFieldDTO.setMatrix(wList); - if (isShowLines && seedLines == 'f') { - this.getWindLinesFromStartLine(wf, windFieldDTO, startTime, endTime, timeStep); + if (params.isShowLines() && params.getSeedLines() == 'f') { + this.getWindLinesFromStartLine(wf, windFieldDTO, startTime, endTime, timeStep); } - if (isShowLines && seedLines == 'b') { - this.getWindLinesFromEndLine(wf, windFieldDTO, startTime, endTime, timeStep); + if (params.isShowLines() && params.getSeedLines() == 'b') { + this.getWindLinesFromEndLine(wf, windFieldDTO, startTime, endTime, timeStep); } windFieldDTO.curBearing = wf.getWindParameters().curBearing; windFieldDTO.curSpeed = wf.getWindParameters().curSpeed; + //} else { + if (params.isShowStreamlets2()) { + + // generate JSON-vectorfield + String jsonField = "$wnd.windFieldSim={"; + //this.rcStart = field.rcStart; + jsonField += "rcStart:{lat:" + params.getNorthWest().latDeg + ",lng:" + params.getNorthWest().lngDeg + "},"; + //this.rcEnd = field.rcEnd; + jsonField += "rcEnd:{lat:" + params.getSouthEast().latDeg + ",lng:" + params.getSouthEast().lngDeg + "},"; + // SouthWest + jsonField += "boundsSW:{lat:" + positionGrid[0][0].getLatDeg() + ",lng:" + positionGrid[0][0].getLngDeg() + "},"; + // NorthEast + jsonField += "boundsNE:{lat:" + positionGrid[params.getyRes()+2*params.getBorderY()-1][params.getxRes()+2*params.getBorderX()-1].getLatDeg() + ",lng:" + positionGrid[params.getyRes()+2*params.getBorderY()-1][params.getxRes()+2*params.getBorderX()-1].getLngDeg() + "},"; + //this.resY = field.resY; + jsonField += "resY:" + params.getyRes() + ","; + //this.resX = field.resX; + jsonField += "resX:" + params.getxRes() + ","; + // border + jsonField += "borderY:" + params.getBorderY() + ","; + jsonField += "borderX:" + params.getBorderX() + ","; + //this.xScale = field.xScale; + jsonField += "xScale:1.5,"; + + /*String jsonField2 = "data:["; + DecimalFormat df = new DecimalFormat("#.00"); + double maxWindSpeed = 0; + + //List windMat = wfDTO.getMatrix(); + + if (positionGrid != null && positionGrid.length > 0) { + TimePoint t = startTime; + while (t.compareTo(endTime) <= 0) { + jsonField2 += "["; + for (int i = 0; i < positionGrid.length; ++i) { + jsonField2 += "["; + for (int j = 0; j < positionGrid[i].length; ++j) { + Wind localWind = wf.getWind(new TimedPositionWithSpeedImpl(t, positionGrid[i][j], null)); + if (localWind.getKnots() > maxWindSpeed) { + maxWindSpeed = localWind.getKnots(); + } + double windY = localWind.getKnots() * Math.cos(localWind.getBearing().getRadians()); + double windX = localWind.getKnots() * Math.sin(localWind.getBearing().getRadians()); + jsonField2 += df.format(windX) + "," + df.format(windY); + if (j<(params.getxRes()-1)) { + jsonField2 += ","; + } + } + if (i<(params.getyRes()-1)) { + jsonField2 += "],"; + } else { + jsonField2 += "]"; + } + } + t = new MillisecondsTimePoint(t.asMillis() + timeStep.asMillis()); + if (t.compareTo(endTime) <= 0) { + jsonField2 += "],"; + } else { + jsonField2 += "]"; + } + } + } + jsonField += "maxLength:" + maxWindSpeed + "," + jsonField2; + jsonField += "]"; + + // finalize JSON Field + jsonField += "};";*/ + + //LOGGER.info("JSON Field:\n" + jsonField); + windFieldDTO.windDataJSON = jsonField; + // windFieldDTO.setMatrix(null); + } + return windFieldDTO; } @@ -869,7 +947,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula LOGGER.info("Retrieving simulated paths"); - Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(selection.boatClassIndex); + Util.Pair polarDiagramAndNotificationMessage = this.getPolarDiagram(selection.boatClassIndex); PolarDiagram pd = polarDiagramAndNotificationMessage.getA(); SimulationParameters sp = new SimulationParametersImpl(course, pd, wf, mode, showOmniscient, showOpportunist); @@ -930,7 +1008,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula return new SimulatedPathsEvenTimedResultDTO(pathDTOs, rcDTO, null, polarDiagramAndNotificationMessage.getB()); } - private Pair getPolarDiagram(int boatClassIndex) throws ConfigurationException { + private Util.Pair getPolarDiagram(int boatClassIndex) throws ConfigurationException { ConfigurationManager config = ConfigurationManager.INSTANCE; PolarDiagram polarDiagram = null; @@ -956,7 +1034,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula + exception.getMessage()); } - return new Pair(polarDiagram, notificationMessage); + return new Util.Pair(polarDiagram, notificationMessage); } private PathDTO getPolylinePathDTO(Path gpsPoly, Path gpsTrack) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompetitorRaceDataDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompetitorRaceDataDTO.java index a9a15adb904..3065876e0cf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompetitorRaceDataDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompetitorRaceDataDTO.java @@ -10,8 +10,9 @@ import java.util.List; import com.google.gwt.user.client.rpc.IsSerializable; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.Util.Triple; public class CompetitorRaceDataDTO implements IsSerializable { @@ -21,21 +22,21 @@ public class CompetitorRaceDataDTO implements IsSerializable { /** * A: mark name; B: mark passing time; C: data as defined by {@link #detailType} for {@link #competitor} for time point B */ - private List> markPassingsData; + private List> markPassingsData; /** * A: time for B; B: data as defined by {@link #detailType} for {@link #competitor} for time point A */ - private List> raceData; + private List> raceData; CompetitorRaceDataDTO() {} public CompetitorRaceDataDTO(CompetitorDTO competitor, DetailType detailType, - Collection> markPassingsData, Collection> raceData) { + Collection> markPassingsData, Collection> raceData) { this.competitor = competitor; this.detailType = detailType; - this.markPassingsData = new ArrayList>(); + this.markPassingsData = new ArrayList>(); addAllMarkPassingsData(markPassingsData); - this.raceData = new ArrayList>(); + this.raceData = new ArrayList>(); addAllRaceData(raceData); } @@ -59,15 +60,15 @@ public class CompetitorRaceDataDTO implements IsSerializable { * Clears the old {@link #markPassingsData} and {@link #addAllMarkPassingsData(Collection) adds} the data in markPassingsData afterwards. * @param markPassingsData */ - public void setMarkPassingsData(List> markPassingsData) { - this.markPassingsData = new ArrayList>(); + public void setMarkPassingsData(List> markPassingsData) { + this.markPassingsData = new ArrayList>(); addAllMarkPassingsData(markPassingsData); } /** * @return An unmodifiable list of the {@link #markPassingsData}. */ - public List> getMarkPassingsData() { + public List> getMarkPassingsData() { return Collections.unmodifiableList(markPassingsData); } @@ -77,8 +78,8 @@ public class CompetitorRaceDataDTO implements IsSerializable { * * @param markPassingsDataToAdd */ - public void addAllMarkPassingsData(Collection> markPassingsDataToAdd) { - for (Triple data : markPassingsDataToAdd) { + public void addAllMarkPassingsData(Collection> markPassingsDataToAdd) { + for (Util.Triple data : markPassingsDataToAdd) { addMarkPassingsData(data); } } @@ -89,7 +90,7 @@ public class CompetitorRaceDataDTO implements IsSerializable { * * @param markPassingsDataToAdd */ - public void addMarkPassingsData(Triple markPassingsDataToAdd) { + public void addMarkPassingsData(Util.Triple markPassingsDataToAdd) { int index = Collections.binarySearch(markPassingsData, markPassingsDataToAdd, new MarkPassingsByTime()); //binarySearch returns a value smaller then 0 if the key isn't contained if (index < 0) { @@ -100,7 +101,7 @@ public class CompetitorRaceDataDTO implements IsSerializable { /** * @return An unmodifiable list of the {@link #raceData} */ - public List> getRaceData() { + public List> getRaceData() { return Collections.unmodifiableList(raceData); } @@ -110,8 +111,8 @@ public class CompetitorRaceDataDTO implements IsSerializable { * * @param raceDataToAdd */ - public void addAllRaceData(Collection> raceDataToAdd) { - for (Pair data : raceDataToAdd) { + public void addAllRaceData(Collection> raceDataToAdd) { + for (Util.Pair data : raceDataToAdd) { addRaceData(data); } } @@ -122,7 +123,7 @@ public class CompetitorRaceDataDTO implements IsSerializable { * * @param raceDataToAdd */ - public void addRaceData(Pair raceDataToAdd) { + public void addRaceData(Util.Pair raceDataToAdd) { int index = Collections.binarySearch(raceData, raceDataToAdd, new RaceDataByTime()); //binarySearch returns a value smaller then 0 if the key isn't contained if (index < 0) { @@ -147,21 +148,21 @@ public class CompetitorRaceDataDTO implements IsSerializable { * @param after The date, exclusive, from where the data should be returned * @return An unmodifiable sublist of {@link #raceData} */ - public List> getRaceDataAfterDate(Date after) { - int from = Collections.binarySearch(raceData, new Pair(after, 0.0), new RaceDataByTime()); + public List> getRaceDataAfterDate(Date after) { + int from = Collections.binarySearch(raceData, new Util.Pair(after, 0.0), new RaceDataByTime()); return Collections.unmodifiableList(raceData.subList(from + 1, raceData.size())); } - public class RaceDataByTime implements Comparator> { + public class RaceDataByTime implements Comparator> { @Override - public int compare(Pair d1, Pair d2) { + public int compare(Util.Pair d1, Util.Pair d2) { return d1.getA().compareTo(d2.getA()); } } - public class MarkPassingsByTime implements Comparator> { + public class MarkPassingsByTime implements Comparator> { @Override - public int compare(Triple m1, Triple m2) { + public int compare(Util.Triple m1, Util.Triple m2) { return m1.getB().compareTo(m2.getB()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceIdentifierDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceIdentifierDTO.java new file mode 100644 index 00000000000..12d8789a1b1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceIdentifierDTO.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.ui.shared; + +import java.io.Serializable; + +public class DeviceIdentifierDTO implements Serializable { + private static final long serialVersionUID = 1171014272760543228L; + public String deviceType; + public String deviceId; + + protected DeviceIdentifierDTO() {} + + public DeviceIdentifierDTO(String deviceType, String deviceId) { + this.deviceType = deviceType; + this.deviceId = deviceId; + } + + public String toString() { + return deviceType + " - " + deviceId; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceMappingDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceMappingDTO.java new file mode 100644 index 00000000000..233c0b076de --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceMappingDTO.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.ui.shared; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +public class DeviceMappingDTO implements Serializable { + private static final long serialVersionUID = -3272980620254526040L; + public DeviceIdentifierDTO deviceIdentifier; + public Date from; + public Date to; + public Serializable mappedTo; + public List originalRaceLogEventIds; + + protected DeviceMappingDTO() {} + + public DeviceMappingDTO(DeviceIdentifierDTO deviceId, Date from, Date to, Serializable mappedTo, + List originalRaceLogEventIds) { + this.deviceIdentifier = deviceId; + this.from = from; + this.to = to; + this.mappedTo = mappedTo; + this.originalRaceLogEventIds = originalRaceLogEventIds; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java new file mode 100755 index 00000000000..69701795341 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java @@ -0,0 +1,76 @@ +package com.sap.sailing.gwt.ui.shared; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.common.dto.NamedDTO; + +public class EventBaseDTO extends NamedDTO implements IsSerializable { + private static final long serialVersionUID = 818666323178097939L; + public VenueDTO venue; + public Date startDate; + public Date endDate; + public boolean isPublic; + public UUID id; + + private List leaderboardGroups; + private List imageURLs = new ArrayList<>(); + private List videoURLs = new ArrayList<>(); + + /** + * The base URL for the server instance on which the data for this event can be reached. Could be something like + * http://sapsailing.com for archived events that will forever remain in the archive, or + * http://danishleague2014.sapsailing.com for other events that may not yet be archived or may change + * servers at any time in the future, therefore requiring a dedicated stable URL that the Apache server can + * resolve to the correct host IP and Java server instance. + */ + private String baseURL; + + EventBaseDTO() {} // for serialization only + + public EventBaseDTO(List leaderboardGroups) { + this.leaderboardGroups = leaderboardGroups; + } + + public EventBaseDTO(String name, List leaderboardGroups) { + super(name); + this.leaderboardGroups = leaderboardGroups; + } + + /** + * If not {@link #setBaseURL(String) set}, defaults to http://sapsailing.com. Meant to describe the + * base URL that maps to the server instance at which this event can be found, such as + * http://505worlds2013.sapsailing.com. + */ + public String getBaseURL() { + return baseURL; + } + + public void setBaseURL(String baseURL) { + this.baseURL = baseURL; + } + + public void addImageURL(String imageURL) { + imageURLs.add(imageURL); + } + + public void addVideoURL(String videoURL) { + videoURLs.add(videoURL); + } + + public List getImageURLs() { + return imageURLs; + } + + public List getVideoURLs() { + return videoURLs; + } + + public Iterable getLeaderboardGroups() { + return leaderboardGroups; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventDTO.java index 08127c0ae48..5bbdc1dd04c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventDTO.java @@ -3,35 +3,36 @@ package com.sap.sailing.gwt.ui.shared; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.UUID; -import com.google.gwt.user.client.rpc.IsSerializable; -import com.sap.sailing.domain.common.dto.NamedDTO; - -public class EventDTO extends NamedDTO implements IsSerializable { +public class EventDTO extends EventBaseDTO { private static final long serialVersionUID = -7100030301376959817L; public List regattas; - public VenueDTO venue; - public Date startDate; - public Date endDate; - public boolean isPublic; - public UUID id; - - // maybe temporary: as long it's not clear how a leaderboard group relates to a sailing event - public LeaderboardGroupDTO leaderboardGroup; private Date currentServerTime; + private List leaderboardGroups; // keeps the more specific type accessible in a type-safe way + public EventDTO() { + this(new ArrayList()); + } + + private EventDTO(List leaderboardGroups) { + super(leaderboardGroups); + this.leaderboardGroups = leaderboardGroups; initCurrentServerTime(); } public EventDTO(String name) { - super(name); + this(name, new ArrayList()); + } + + private EventDTO(String name, List leaderboardGroups) { + super(name, leaderboardGroups); + this.leaderboardGroups = leaderboardGroups; initCurrentServerTime(); regattas = new ArrayList(); } - + private void initCurrentServerTime() { currentServerTime = new Date(); } @@ -39,4 +40,13 @@ public class EventDTO extends NamedDTO implements IsSerializable { public Date getCurrentServerTime() { return currentServerTime; } + + public void addLeaderboardGroup(LeaderboardGroupDTO leaderboardGroup) { + leaderboardGroups.add(leaderboardGroup); + } + + public Iterable getLeaderboardGroups() { + return leaderboardGroups; + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupBaseDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupBaseDTO.java new file mode 100755 index 00000000000..44ffc3daded --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupBaseDTO.java @@ -0,0 +1,37 @@ +package com.sap.sailing.gwt.ui.shared; + +import java.util.UUID; + +import com.sap.sailing.domain.common.dto.NamedDTO; + +public class LeaderboardGroupBaseDTO extends NamedDTO { + private static final long serialVersionUID = -9023865026348923430L; + private UUID id; + public String description; + private boolean hasOverallLeaderboard; + + LeaderboardGroupBaseDTO() {} // for deserialization + + public LeaderboardGroupBaseDTO(UUID id, String name) { + super(name); + this.id = id; + } + + public LeaderboardGroupBaseDTO(UUID id, String name, String description, boolean hasOverallLeaderboard) { + this(id, name); + this.description = description; + this.hasOverallLeaderboard = hasOverallLeaderboard; + } + + public UUID getId() { + return id; + } + + public boolean isHasOverallLeaderboard() { + return hasOverallLeaderboard; + } + + public void setHasOverallLeaderboard(boolean hasOverallLeaderboard) { + this.hasOverallLeaderboard = hasOverallLeaderboard; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupDTO.java index b5b463d3cdd..471bb280043 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupDTO.java @@ -4,20 +4,19 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.UUID; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.NamedDTO; import com.sap.sailing.domain.common.dto.PlacemarkDTO; import com.sap.sailing.domain.common.dto.PlacemarkOrderDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sse.gwt.client.player.Timer; -public class LeaderboardGroupDTO extends NamedDTO { +public class LeaderboardGroupDTO extends LeaderboardGroupBaseDTO { private static final long serialVersionUID = -2923229069598593687L; - public String description; public List leaderboards; public boolean displayLeaderboardsInReverseOrder; @@ -30,6 +29,8 @@ public class LeaderboardGroupDTO extends NamedDTO { */ private Date currentServerTime; + LeaderboardGroupDTO() {} + /** * Creates a new LeaderboardGroupDTO with empty but non-null name, description and an empty but non-null list for * the leaderboards. The {@link #currentServerTime} will be set to the creation time on the system that creates this @@ -37,8 +38,16 @@ public class LeaderboardGroupDTO extends NamedDTO { *

* The additional data (start dates and places for the races) will be initialized but empty. */ - public LeaderboardGroupDTO() { - this("", "", new ArrayList()); + public LeaderboardGroupDTO(UUID id) { + this(id, ""); + } + + public LeaderboardGroupDTO(UUID id, String name) { + this(id, name, ""); + } + + public LeaderboardGroupDTO(UUID id, String name, String description) { + this(id, name, description, new ArrayList()); } /** @@ -46,8 +55,8 @@ public class LeaderboardGroupDTO extends NamedDTO { * All parameters can be null but then the attributes will also be null.
* The additional data (start dates and places for the races) will be initialized but empty. */ - private LeaderboardGroupDTO(String name, String description, List leaderboards) { - super(name); + private LeaderboardGroupDTO(UUID id, String name, String description, List leaderboards) { + super(id, name); currentServerTime = new Date(); this.description = description; this.leaderboards = leaderboards; @@ -208,5 +217,5 @@ public class LeaderboardGroupDTO extends NamedDTO { public Date getCurrentServerTime() { return currentServerTime; } - + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardSearchResultDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardSearchResultDTO.java new file mode 100755 index 00000000000..db9b8bf2392 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardSearchResultDTO.java @@ -0,0 +1,94 @@ +package com.sap.sailing.gwt.ui.shared; + +import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; +import com.sap.sailing.domain.base.RemoteSailingServerReference; + +/** + * The DTO version of a {@link LeaderboardSearchResultBase} object for transmission to clients. In addition to the + * fields of a {@link LeaderboardSearchResultBase}, this object also tells the base URL of the server from which this + * result was obtained. This may be a server different from the one to which the query was sent, as such a server may + * maintain a set of {@link RemoteSailingServerReference}s pointing to other servers from where search results can be + * obtained as well. The client needs to use this base URL to navigate to a user interface / page that can display those + * search results. + * + * @author Axel Uhl (D043530) + * + */ +public class LeaderboardSearchResultDTO implements IsSerializable { + private String baseURL; + private String leaderboardName; + private String leaderboardDisplayName; + private String regattaName; + private String boatClassName; + private EventBaseDTO event; + private Iterable leaderboardGroups; + + LeaderboardSearchResultDTO() {} // for GWT RPC serialization only + + public LeaderboardSearchResultDTO(String baseURL, String leaderboardName, String leaderboardDisplayName, + String regattaName, String boatClassName, EventBaseDTO event, + Iterable leaderboardGroups) { + super(); + this.baseURL = baseURL; + this.leaderboardName = leaderboardName; + this.leaderboardDisplayName = leaderboardDisplayName; + this.regattaName = regattaName; + this.boatClassName = boatClassName; + this.event = event; + this.leaderboardGroups = leaderboardGroups; + } + + public String getBaseURL() { + return baseURL; + } + + public String getLeaderboardName() { + return leaderboardName; + } + + public String getLeaderboardDisplayName() { + return leaderboardDisplayName; + } + + public String getRegattaName() { + return regattaName; + } + + public String getBoatClassName() { + return boatClassName; + } + + public EventBaseDTO getEvent() { + return event; + } + + public Iterable getLeaderboardGroups() { + return leaderboardGroups; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Search result from "); + sb.append(getBaseURL()); + sb.append(": Leaderboard "); + sb.append(getLeaderboardName()); + if (getLeaderboardDisplayName() != null) { + sb.append(" (display name: "); + sb.append(getLeaderboardDisplayName()); + sb.append(")"); + } + if (getEvent() != null) { + sb.append(", Event "); + sb.append(getEvent().getName()); + } + sb.append(", "); + sb.append("Boat class: "); + sb.append(getBoatClassName()); + sb.append("; "); + sb.append("Regatta Name: "); + sb.append(getRegattaName()); + return sb.toString(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/MarkDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/MarkDTO.java index 0e2720bc656..02752f0e306 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/MarkDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/MarkDTO.java @@ -28,4 +28,18 @@ public class MarkDTO extends ControlPointDTO { public Iterable getMarks() { return Collections.singleton(this); } + + @Override + public boolean equals(Object o) { + if (o instanceof MarkDTO) { + MarkDTO other = (MarkDTO) o; + return getIdAsString() != null && getIdAsString().equals(other.getIdAsString()); + } + return false; + } + + @Override + public int hashCode() { + return getIdAsString().hashCode(); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseDTO.java index 935d72762ac..9ddb68d97b0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseDTO.java @@ -10,30 +10,48 @@ import com.google.gwt.user.client.rpc.IsSerializable; public class RaceCourseDTO implements IsSerializable { public List waypoints; + + /** + * List of all marks in race, that can include such that are not actually part of + * the race course definition. + */ + private List allMarks; - RaceCourseDTO() {} + RaceCourseDTO() { + } public RaceCourseDTO(List waypoints) { + this(waypoints, null); + } + + public RaceCourseDTO(List waypoints, List allMarks) { this.waypoints = waypoints; + this.allMarks = allMarks; } public List getControlPoints() { List controlPoints = new ArrayList(); - for(WaypointDTO waypoint: waypoints) { + for (WaypointDTO waypoint : waypoints) { controlPoints.add(waypoint.controlPoint); } return controlPoints; } public Collection getMarks() { - Map marks = new HashMap(); - for(WaypointDTO waypoint: waypoints) { - for(MarkDTO mark: waypoint.marks) { - if(!marks.containsKey(mark.getName())) { - marks.put(mark.getName(), mark); + Collection result; + if (allMarks != null) { + result = allMarks; + } else { + Map marks = new HashMap(); + for (WaypointDTO waypoint : waypoints) { + for (MarkDTO mark : waypoint.controlPoint.getMarks()) { + if (!marks.containsKey(mark.getName())) { + marks.put(mark.getName(), mark); + } } } + result = marks.values(); } - return marks.values(); + return result; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RemoteSailingServerReferenceDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RemoteSailingServerReferenceDTO.java new file mode 100644 index 00000000000..2b2f62c3dd1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RemoteSailingServerReferenceDTO.java @@ -0,0 +1,58 @@ +package com.sap.sailing.gwt.ui.shared; + +import java.util.Collections; + +import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.common.dto.NamedDTO; + +public class RemoteSailingServerReferenceDTO extends NamedDTO implements IsSerializable { + private static final long serialVersionUID = -4209262742778693873L; + private String url; + private Iterable events; + + /** + * The error message is usually filled in case {@link #events} is null and gives a hint about the + * server-side error that occurred trying to obtain the event list from the server identified by the reference + * represented by this DTO. + */ + private String lastErrorMessage; + + // for GWT + RemoteSailingServerReferenceDTO() { + } + + public RemoteSailingServerReferenceDTO(String name, String url) { + this(name, url, Collections. emptyList()); + } + + public RemoteSailingServerReferenceDTO(String name, String url, Iterable events) { + this(name, url, events, /* error message */ null); + } + + public RemoteSailingServerReferenceDTO(String name, String url, Iterable events, String lastErrorMessage) { + super(name); + this.url = url; + this.events = events; + this.lastErrorMessage = lastErrorMessage; + } + + public RemoteSailingServerReferenceDTO(String name, String url, String lastErrorMessage) { + this(name, url, /* events */ null, lastErrorMessage); + } + + public String getUrl() { + return url; + } + + public Iterable getEvents() { + return events; + } + + public String getLastErrorMessage() { + return lastErrorMessage; + } + + public void setLastErrorMessage(String lastErrorMessage) { + this.lastErrorMessage = lastErrorMessage; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ScoreCorrectionProviderDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ScoreCorrectionProviderDTO.java index 0ada0e685d0..2c063b38c9f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ScoreCorrectionProviderDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ScoreCorrectionProviderDTO.java @@ -5,20 +5,20 @@ import java.util.Map; import java.util.Set; import com.sap.sailing.domain.common.dto.NamedDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sse.common.Util; public class ScoreCorrectionProviderDTO extends NamedDTO { private static final long serialVersionUID = -636159261445539142L; - private Map>> hasResultsForBoatClassFromDateByEventName; + private Map>> hasResultsForBoatClassFromDateByEventName; public ScoreCorrectionProviderDTO() {} - public ScoreCorrectionProviderDTO(String name, Map>> hasResultsForBoatClassFromDateByEventName2) { + public ScoreCorrectionProviderDTO(String name, Map>> hasResultsForBoatClassFromDateByEventName2) { super(name); this.hasResultsForBoatClassFromDateByEventName = hasResultsForBoatClassFromDateByEventName2; } - public Map>> getHasResultsForBoatClassFromDateByEventName() { + public Map>> getHasResultsForBoatClassFromDateByEventName() { return hasResultsForBoatClassFromDateByEventName; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingRaceRecordDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingRaceRecordDTO.java index aa998e6c928..78d1eeb7e83 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingRaceRecordDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SwissTimingRaceRecordDTO.java @@ -8,24 +8,37 @@ public class SwissTimingRaceRecordDTO implements IsSerializable { public String raceId; public String raceName; public Date raceStartTime; + public String regattaName; + public String seriesName; + public String fleetName; + public String raceStatus; + public String xrrEntriesUrl; public String boatClass; public String gender; - - public boolean hasCourse; - public boolean hasStartlist; public SwissTimingRaceRecordDTO() {} - public SwissTimingRaceRecordDTO(String raceId, String raceName, Date raceStartTime) { + public SwissTimingRaceRecordDTO(String raceId, String raceName, String regattaName, String seriesName, String fleetName, + String raceStatus, Date raceStartTime, String xrrEntriesUrl) { super(); this.raceId = raceId; this.raceName = raceName; + this.regattaName = regattaName; + this.seriesName = seriesName; + this.fleetName = fleetName; + this.raceStatus = raceStatus; this.raceStartTime = raceStartTime; + this.xrrEntriesUrl = xrrEntriesUrl; } - - @Override - public String toString() { - return "Race "+raceId+" ("+raceStartTime+")"; - } + + @Override + public String toString() { + return "SwissTimingRaceRecordDTO [raceId=" + raceId + ", raceName=" + + raceName + ", raceStartTime=" + raceStartTime + + ", regattaName=" + regattaName + ", seriesName=" + seriesName + + ", fleetName=" + fleetName + ", raceStatus=" + raceStatus + + ", boatClass=" + boatClass + ", gender=" + gender + + "]"; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/TrackFileImportDeviceIdentifierDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/TrackFileImportDeviceIdentifierDTO.java new file mode 100644 index 00000000000..288d2a96b2a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/TrackFileImportDeviceIdentifierDTO.java @@ -0,0 +1,28 @@ +package com.sap.sailing.gwt.ui.shared; + +import java.util.Date; + +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; + +public class TrackFileImportDeviceIdentifierDTO extends DeviceIdentifierDTO { + private static final long serialVersionUID = 5409142940941055516L; + public String uuidAsString; + public String fileName; + public String trackName; + public long numFixes; + public Date from; + public Date to; + + protected TrackFileImportDeviceIdentifierDTO() {} + + public TrackFileImportDeviceIdentifierDTO(String uuidAsString, String fileName, String trackName, + long numFixes, Date from, Date to) { + super(TrackFileImportDeviceIdentifier.TYPE, uuidAsString); + this.uuidAsString = uuidAsString; + this.fileName = fileName; + this.trackName = trackName; + this.numFixes = numFixes; + this.from = from; + this.to = to; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WaypointDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WaypointDTO.java index 5a775cee274..29836d30408 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WaypointDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WaypointDTO.java @@ -1,14 +1,14 @@ package com.sap.sailing.gwt.ui.shared; -import java.util.List; - import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.dto.NamedDTO; +/** + * Equality and hashcode based on object identity + * + */ public class WaypointDTO extends NamedDTO { private static final long serialVersionUID = 7439553659782967746L; - - public List marks; public ControlPointDTO controlPoint; @@ -16,11 +16,20 @@ public class WaypointDTO extends NamedDTO { WaypointDTO() {} - public WaypointDTO(String name, ControlPointDTO controlPoint, List marks, PassingInstruction passingInstructions) { + public WaypointDTO(String name, ControlPointDTO controlPoint, PassingInstruction passingInstructions) { super(name); this.setName(name); - this.marks = marks; this.controlPoint = controlPoint; this.passingInstructions = passingInstructions; } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldDTO.java index c43b88b61f8..cfb81a1889e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldDTO.java @@ -9,6 +9,8 @@ public class WindFieldDTO implements IsSerializable { public double curSpeed; public double curBearing; + public String windDataJSON; + private List matrix; private WindLinesDTO windLinesDTO; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldGenParamsDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldGenParamsDTO.java index 3a351d02452..736d87e169a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldGenParamsDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldGenParamsDTO.java @@ -12,6 +12,8 @@ public class WindFieldGenParamsDTO implements IsSerializable { private int xRes; private int yRes; + private int borderY; + private int borderX; private Date startTime; private Date endTime; @@ -35,6 +37,7 @@ public class WindFieldGenParamsDTO implements IsSerializable { private boolean showGrid; private boolean showStreamlets; + private boolean showStreamlets2; private boolean showLines; @@ -79,6 +82,19 @@ public class WindFieldGenParamsDTO implements IsSerializable { this.yRes = yRes; } + public int getBorderY() { + return borderY; + } + + public int getBorderX() { + return borderX; + } + + public void setBorder(int border) { + this.borderY = border; + this.borderX = (int)Math.round(border * ((double)xRes) / ((double)yRes)); + } + public Date getStartTime() { return startTime; } @@ -149,6 +165,14 @@ public class WindFieldGenParamsDTO implements IsSerializable { this.showStreamlets = showStreamlets; } + public boolean isShowStreamlets2() { + return showStreamlets2; + } + + public void setShowStreamlets2(boolean showStreamlets2) { + this.showStreamlets2 = showStreamlets2; + } + public boolean isShowLines() { return showLines; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/GoogleMapStyleHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/GoogleMapStyleHelper.java index 27d129176b0..cb7f56500b7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/GoogleMapStyleHelper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/GoogleMapStyleHelper.java @@ -34,6 +34,18 @@ public class GoogleMapStyleHelper { return result; } + + public static MapTypeStyle createElementStyleOnlyLightness(MapTypeStyleFeatureType featureType, MapTypeStyleElementType elementType, int ligthness) { + MapTypeStyle result = MapTypeStyle.newInstance(); + result.setFeatureType(featureType); + result.setElementType(elementType); + + MapTypeStyler[] typeStylers = new MapTypeStyler[1]; + typeStylers[0] = MapTypeStyler.newLightnessStyler(ligthness); + result.setStylers(typeStylers); + + return result; + } public static MapTypeStyle createElementStyleWithVisibility(MapTypeStyleFeatureType featureType, MapTypeStyleElementType elementType, String visibility) { MapTypeStyle result = MapTypeStyle.newInstance(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java index 38f7845a90b..f7ca991f49e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java @@ -57,9 +57,9 @@ public class MarkVectorGraphics { switch(type) { case BUOY: if(shape != null) { - if("Cylinder".equalsIgnoreCase(shape) && pattern != null && "Checkered".equalsIgnoreCase(pattern)) { + if(Shape.CYLINDER.name().equalsIgnoreCase(shape) && pattern != null && Pattern.CHECKERED.name().equalsIgnoreCase(pattern)) { drawBuoyWithFinishFlag(ctx, isSelected, color); - } else if ("Conical".equalsIgnoreCase(shape)) { + } else if (Shape.CONICAL.name().equalsIgnoreCase(shape)) { drawConicalBuoy(ctx, isSelected, color); } else { drawSimpleBuoy(ctx, isSelected, color); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/Pattern.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/Pattern.java new file mode 100644 index 00000000000..4e662d66e85 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/Pattern.java @@ -0,0 +1,5 @@ +package com.sap.sailing.gwt.ui.shared.racemap; + +public enum Pattern { + CHECKERED +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/Shape.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/Shape.java new file mode 100644 index 00000000000..5da42c05bff --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/Shape.java @@ -0,0 +1,5 @@ +package com.sap.sailing.gwt.ui.shared.racemap; + +public enum Shape { + CYLINDER, CONICAL +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathCanvasOverlay.java index 9811f1806e8..0ec3dfb28a2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathCanvasOverlay.java @@ -13,11 +13,11 @@ import com.google.gwt.maps.client.base.LatLng; import com.google.gwt.maps.client.base.Point; import com.google.gwt.maps.client.geometrylib.SphericalUtils; import com.sap.sailing.domain.common.Mile; -import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; import com.sap.sailing.gwt.ui.shared.WindFieldGenParamsDTO; +import com.sap.sse.common.Named; import com.sap.sse.gwt.client.player.Timer; /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathLegendCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathLegendCanvasOverlay.java index 44fe88a263c..4fe916d694b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathLegendCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathLegendCanvasOverlay.java @@ -140,7 +140,7 @@ public class PathLegendCanvasOverlay extends FullCanvasOverlay { } @Override - protected void setCanvasSettings() { + public void setCanvasSettings() { super.setCanvasSettings(); canvas.getElement().getStyle().setZIndex(100000); // make legend float on-top of all overlays } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathPolyline.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathPolyline.java index 224bb74fdc0..f224ab26776 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathPolyline.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathPolyline.java @@ -9,10 +9,14 @@ import com.google.gwt.maps.client.base.Point; import com.google.gwt.maps.client.events.click.ClickMapEvent; import com.google.gwt.maps.client.events.click.ClickMapHandler; import com.google.gwt.maps.client.mvc.MVCArray; +import com.google.gwt.maps.client.overlays.MapCanvasProjection; import com.google.gwt.maps.client.overlays.Marker; import com.google.gwt.maps.client.overlays.MarkerOptions; +import com.google.gwt.maps.client.overlays.OverlayView; import com.google.gwt.maps.client.overlays.Polyline; import com.google.gwt.maps.client.overlays.PolylineOptions; +import com.google.gwt.maps.client.overlays.overlayhandlers.OverlayViewMethods; +import com.google.gwt.maps.client.overlays.overlayhandlers.OverlayViewOnDrawHandler; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Position; @@ -66,6 +70,8 @@ public class PathPolyline { private List allPoints = null; private SimulatorServiceAsync simulatorService = null; private MapWidget map = null; + protected OverlayView mapView; + protected MapCanvasProjection mapProjection; private ErrorReporter errorReporter = null; private boolean warningAlreadyShown = false; private SimulatorMap simulatorMap = null; @@ -114,6 +120,12 @@ public class PathPolyline { this.allPoints = pathPoints; this.simulatorService = simulatorService; this.map = map; + this.mapView = OverlayView.newInstance(map, new OverlayViewOnDrawHandler() { + @Override + public void onDraw(OverlayViewMethods methods) { + mapProjection = methods.getProjection(); + } + }, null, null); this.selectedBoatClassIndex = selection.boatClassIndex; this.selectedRaceIndex = selection.raceIndex; this.selectedCompetitorIndex = selection.competitorIndex; @@ -234,7 +246,7 @@ public class PathPolyline { simulatorMap.setPolyline(polyline); polyline.setEditable(true); // old call: polyline.setEditingEnabled(PolyEditingOptions.newInstance(turnPoints.length - 1)); - + getTotalTime(); } @@ -1037,7 +1049,7 @@ public class PathPolyline { /** * Converts a LatLng object to a TwoD object. Considering that a LatLng represent a point on a sphere, and that the - * TwoDPoint represent one on a plane, it will use the MapWidget aproximation to a container pixel in order to do + * TwoDPoint represent one on a plane, it will use the MapWidget approximation to a container pixel in order to do * so. * * @param latLng @@ -1045,8 +1057,7 @@ public class PathPolyline { * @returns a TwoDPoint object. */ private TwoDPoint toTwoDPoint(LatLng latLng) { - // TODO MigrationV3: Point point = map.convertLatLngToContainerPixel(latLng); - Point point = Point.newInstance(latLng.getLatitude(), latLng.getLongitude()); + Point point = this.mapProjection.fromLatLngToContainerPixel(latLng); return new TwoDPoint(point.getX(), point.getY()); } @@ -1065,8 +1076,7 @@ public class PathPolyline { * @returns a LatLng object. */ private LatLng toLatLng(TwoDPoint point) { - return LatLng.newInstance((int) point.getX(), (int) point.getY()); - // TODO MigrationV3: return this.map.convertContainerPixelToLatLng(Point.newInstance((int) point.getX(), (int) point.getY())); + return this.mapProjection.fromContainerPixelToLatLng(Point.newInstance((int) point.getX(), (int) point.getY())); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RaceCourseCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RaceCourseCanvasOverlay.java index 07a57abf640..08a3cec821e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RaceCourseCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RaceCourseCanvasOverlay.java @@ -148,8 +148,10 @@ public class RaceCourseCanvasOverlay extends FullCanvasOverlay { if (startPoint != null) { Point point = mapProjection.fromLatLngToDivPixel(startPoint); - drawCircleWithText(point.getX() - getWidgetPosLeft(), point.getY() - getWidgetPosTop(), racecourseBuoySize, - racecourseColor, "Start"); + if (racecourseBuoySize > 0.0) { + drawCircleWithText(point.getX() - getWidgetPosLeft(), point.getY() - getWidgetPosTop(), racecourseBuoySize, + racecourseColor, "Start"); + } if (startMarker != null) { startMarker.setPosition(startPoint); } else { @@ -174,10 +176,11 @@ public class RaceCourseCanvasOverlay extends FullCanvasOverlay { } if (endPoint != null) { - Point point = mapProjection.fromLatLngToDivPixel(endPoint); - drawCircleWithText(point.getX() - getWidgetPosLeft(), point.getY() - getWidgetPosTop(), racecourseBuoySize, - racecourseColor, "End"); - + Point point = mapProjection.fromLatLngToDivPixel(endPoint); + if (racecourseBuoySize > 0.0) { + drawCircleWithText(point.getX() - getWidgetPosLeft(), point.getY() - getWidgetPosTop(), racecourseBuoySize, + racecourseColor, "End"); + } if (endMarker != null) { endMarker.setPosition(endPoint); } else { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RegattaAreaCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RegattaAreaCanvasOverlay.java index dc4b3873ff7..80e2d0afa8d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RegattaAreaCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RegattaAreaCanvasOverlay.java @@ -5,12 +5,6 @@ import com.google.gwt.canvas.dom.client.Context2d.TextAlign; import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.base.LatLng; import com.google.gwt.maps.client.base.Point; -import com.google.gwt.maps.client.events.click.ClickMapEvent; -import com.google.gwt.maps.client.events.click.ClickMapHandler; -import com.google.gwt.maps.client.events.idle.IdleMapEvent; -import com.google.gwt.maps.client.events.idle.IdleMapHandler; -import com.google.gwt.maps.client.overlays.Circle; -import com.google.gwt.maps.client.overlays.CircleOptions; import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay; import com.sap.sailing.simulator.util.SailingSimulatorConstants; @@ -22,7 +16,6 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { private CourseAreaDescriptor currentCourseArea = null; private boolean initial; - private boolean mapPan = false; private double raceBearing = 0.0; private double diffBearing = 0.0; @@ -32,58 +25,7 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { this.simulatorMap = simulatorMap; this.venue = VenueDescriptorFactory.createVenue(event); - this.currentCourseArea = this.venue.getDefaultCourseArea(); - - /*getMap().addZoomChangeHandler(new ZoomChangeMapHandler() { - - @Override - public void onEvent(ZoomChangeMapEvent event) { - // TODO Auto-generated method stub - simulatorMap.windRoseCanvasOverlay.removeFromMap(); - simulatorMap.windNeedleCanvasOverlay.removeFromMap(); - mapPan = true; - } - - }); - - getMap().addDragStartHandler(new DragStartMapHandler() { - - @Override - public void onEvent(DragStartMapEvent event) { - // TODO Auto-generated method stub - simulatorMap.windRoseCanvasOverlay.removeFromMap(); - simulatorMap.windNeedleCanvasOverlay.removeFromMap(); - } - - - }); - - getMap().addDragEndHandler(new DragEndMapHandler() { - - @Override - public void onEvent(DragEndMapEvent event) { - // TODO Auto-generated method stub - simulatorMap.windRoseCanvasOverlay.addToMap(); - simulatorMap.windNeedleCanvasOverlay.addToMap(); - } - - - });*/ - - getMap().addIdleHandler(new IdleMapHandler() { - - @Override - public void onEvent(IdleMapEvent event) { - // TODO Auto-generated method stub - if (mapPan) { - //System.out.println("Map Idle Event Handler."); - simulatorMap.getWindRoseCanvasOverlay().addToMap(); - simulatorMap.getWindNeedleCanvasOverlay().addToMap(); - mapPan = false; - } - } - - }); + this.currentCourseArea = this.venue.getDefaultCourseArea(); } @@ -93,7 +35,7 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { int counter = 0; for (CourseAreaDescriptor courseArea : venue.getCourseAreas()) { - drawCircleFromRadius(counter++, courseArea); + simulatorMap.drawCircleFromRadius(counter++, courseArea); } map.panTo(venue.getCenterPos()); @@ -142,48 +84,6 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { } } - // TODO MigrationV3: This is not the right place to create Circles being not drawn to the canvas -> move to SimulatorMap - public void drawCircleFromRadius(int regIdx, CourseAreaDescriptor courseArea) { - - CircleOptions circleOptions = CircleOptions.newInstance(); - circleOptions.setStrokeColor("white"); - circleOptions.setStrokeWeight(1); - circleOptions.setStrokeOpacity(0.0); - circleOptions.setFillColor("green"); - circleOptions.setFillOpacity(0.0); - circleOptions.setCenter(courseArea.getCenterPos()); - Circle circle = Circle.newInstance(circleOptions); - circle.setRadius(courseArea.getRadiusInMeters()); - - final int regIdxFinal = regIdx; - - circle.addClickHandler(new ClickMapHandler() { - public void onEvent(ClickMapEvent e) { - //System.out.println("Click: "+currentCourseArea.getName()); - CourseAreaDescriptor newRegArea = venue.getCourseAreas().get(regIdxFinal); - - if (newRegArea != currentCourseArea) { - currentCourseArea = newRegArea; - simulatorMap.clearOverlays(); - simulatorMap.getWindRoseCanvasOverlay().removeFromMap(); - simulatorMap.getWindNeedleCanvasOverlay().removeFromMap(); - updateRaceCourse(0, 0); - raceCourseCanvasOverlay.draw(); - } - - map.panTo(currentCourseArea.getCenterPos()); - mapPan = true; - if (map.getZoom() < 14) { - map.setZoom(14); - } - - } - }); - - circle.setMap(getMap()); - - } - protected void updateRaceCourse(int type, double bearing) { if (type == 1) { @@ -209,6 +109,14 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { } } + public CourseAreaDescriptor getCurrentCourseArea() { + return this.currentCourseArea; + } + + public void setCurrentCourseArea(CourseAreaDescriptor courseArea) { + this.currentCourseArea = courseArea; + } + protected void drawCourseArea(String name, Context2d context2d, Point centerPoint, Point borderPoint, String color, String colorText, double pxStroke) { @@ -216,18 +124,23 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { centerPoint.getY() - borderPoint.getY()); double pxRadius = Math.sqrt(diffPoint.getX() * diffPoint.getX() + diffPoint.getY() * diffPoint.getY()); + String bgColor; + + if (simulatorMap.getWindParams().isShowStreamlets2()) { + bgColor = "#505050"; + } else { + bgColor = "#DEDEDE"; + } + context2d.setGlobalAlpha(1.0f); - context2d.setFillStyle("#DEDEDE"); - context2d.setLineWidth(pxStroke); - context2d.setStrokeStyle(color); + context2d.setFillStyle(bgColor); context2d.beginPath(); context2d.arc(centerPoint.getX() - this.getWidgetPosLeft(), centerPoint.getY() - this.getWidgetPosTop(), pxRadius, 0.0, 2 * Math.PI); context2d.closePath(); context2d.fill(); - context2d.stroke(); - context2d.setGlobalAlpha(0.4f); + context2d.setGlobalAlpha(0.5f); context2d.setFillStyle(color); context2d.beginPath(); context2d.arc(centerPoint.getX() - this.getWidgetPosLeft(), centerPoint.getY() - this.getWidgetPosTop(), @@ -235,6 +148,24 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { context2d.closePath(); context2d.fill(); + context2d.setGlobalAlpha(1.0f); + context2d.setLineWidth(pxStroke); + context2d.setStrokeStyle(bgColor); + context2d.beginPath(); + context2d.arc(centerPoint.getX() - this.getWidgetPosLeft(), centerPoint.getY() - this.getWidgetPosTop(), + pxRadius, 0.0, 2 * Math.PI); + context2d.closePath(); + context2d.stroke(); + + context2d.setGlobalAlpha(0.8f); + context2d.setLineWidth(pxStroke); + context2d.setStrokeStyle(color); + context2d.beginPath(); + context2d.arc(centerPoint.getX() - this.getWidgetPosLeft(), centerPoint.getY() - this.getWidgetPosTop(), + pxRadius, 0.0, 2 * Math.PI); + context2d.closePath(); + context2d.stroke(); + if (getMap().getZoom() >= 11) { context2d.setGlobalAlpha(0.8f); context2d.setFillStyle(colorText); @@ -246,9 +177,9 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { context2d.fillText(name, centerPoint.getX() - this.getWidgetPosLeft(), centerPoint.getY() + 0.32 * fontsize - this.getWidgetPosTop()); - context2d.setGlobalAlpha(1.0f); } - + + context2d.setGlobalAlpha(1.0f); } protected LatLng getEdgePoint(LatLng pos, double dist) { @@ -282,4 +213,8 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { this.raceCourseCanvasOverlay = rcCanvas; } + public VenueDescriptor getVenue() { + return this.venue; + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java index 936d60566ee..0e4773bb400 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java @@ -10,11 +10,14 @@ import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; +import com.sap.sailing.gwt.ui.client.JavaScriptInjector; import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.client.SimulatorService; import com.sap.sailing.gwt.ui.client.SimulatorServiceAsync; +import com.sap.sailing.gwt.ui.simulator.streamlets.SimulatorJSBundle; import com.sap.sailing.simulator.util.SailingSimulatorConstants; +import com.sap.sse.gwt.client.EntryPointHelper; public class SimulatorEntryPoint extends AbstractEntryPoint { @@ -23,6 +26,8 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { private final SimulatorServiceAsync simulatorService = GWT.create(SimulatorService.class); private int xRes = 40; private int yRes = 20; + private int border = 0; + private int particles = 0; private boolean autoUpdate = false; private char mode = SailingSimulatorConstants.ModeEvent; // default mode: 'e'vent private char event = SailingSimulatorConstants.EventKielerWoche; // default event: 'k'ieler woche @@ -30,18 +35,28 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { private boolean showArrows = false; // show the wind arrows in wind display and replay modes. private boolean showGrid = true; // show the "heat map" in the wind display and replay modes. private boolean showLines = false; // show the wind lines in the wind display and replay modes. + private boolean showMapControls = true; // show the map controls such as zoom and pan private char seedLines = 'b'; // seed lines at: 'b'ack, 'f'ront private boolean showStreamlets = true; // show the wind streamlets in the wind display and replay modes. - + private boolean showStreamlets2 = false; // show animated wind streamlets in the wind display and replay modes. + private boolean injectWindDataJS = false; + private static Logger logger = Logger.getLogger(SimulatorEntryPoint.class.getName()); @Override protected void doOnModuleLoad() { - super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) simulatorService, RemoteServiceMappingConstants.simulatorServiceRemotePath); + super.doOnModuleLoad(); - checkUrlParameters(); + EntryPointHelper.registerASyncService((ServiceDefTarget) simulatorService, RemoteServiceMappingConstants.simulatorServiceRemotePath); + + checkUrlParameters(); + + if (this.injectWindDataJS) { + SimulatorJSBundle bundle = GWT.create(SimulatorJSBundle.class); + JavaScriptInjector.inject(bundle.windStreamletsDataJS().getText()); + } + createSimulatorPanel(); } @@ -58,12 +73,30 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { } else { yRes = Integer.parseInt(verticalRes); } + String border = Window.Location.getParameter("border"); + if (border == null || border.isEmpty()) { + logger.config("Using default border " + this.border); + } else { + this.border = Integer.parseInt(border); + } + String particlesStr = Window.Location.getParameter("particles"); + if (particlesStr == null || particlesStr.isEmpty()) { + logger.config("Using default particles " + this.particles); + } else { + this.particles = Integer.parseInt(particlesStr); + } String autoUpdateStr = Window.Location.getParameter("autoUpdate"); if (autoUpdateStr == null || autoUpdateStr.isEmpty()) { logger.config("Using default auto update " + autoUpdate); } else { autoUpdate = Boolean.parseBoolean(autoUpdateStr); } + String showMapControlsStr = Window.Location.getParameter("showMapControls"); + if (showMapControlsStr == null || showMapControlsStr.isEmpty()) { + logger.config("Using default showMapControls " + showMapControls); + } else { + showMapControls = Boolean.parseBoolean(showMapControlsStr); + } String modeStr = Window.Location.getParameter("mode"); if (modeStr == null || modeStr.isEmpty()) { logger.config("Using default mode " + mode); @@ -106,6 +139,18 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { } else { showStreamlets = false; } + if (windDisplayStr.contains("z")) { + showStreamlets2 = true; + } else { + showStreamlets2 = false; + } + if (windDisplayStr.contains("y")) { + showStreamlets2 = true; + injectWindDataJS = true; + } + if ((showStreamlets2)&&(border==null)) { + this.border = 10; + } if (windDisplayStr.contains("b")) { seedLines = 'b'; } @@ -134,8 +179,8 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { } private void createSimulatorPanel() { - SimulatorMainPanel simulatorPanel = new SimulatorMainPanel(simulatorService, stringMessages, this, xRes, yRes, - autoUpdate, mode, event, showGrid, showLines, seedLines, showArrows, showStreamlets); + SimulatorMainPanel simulatorPanel = new SimulatorMainPanel(simulatorService, stringMessages, this, xRes, yRes, border, particles, + autoUpdate, mode, event, showGrid, showLines, seedLines, showArrows, showStreamlets, showStreamlets2, injectWindDataJS, showMapControls); DockLayoutPanel p = new DockLayoutPanel(Unit.PX); RootLayoutPanel.get().add(p); 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 38331ff5450..5fe66c5656f 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 @@ -72,6 +72,8 @@ public class SimulatorMainPanel extends SimplePanel { } } + public int particles; + private DockLayoutPanel mainPanel; private FlowPanel leftPanel; private ResizableFlowPanel rightPanel; @@ -122,6 +124,7 @@ public class SimulatorMainPanel extends SimplePanel { private CheckBox isPathPolylineFreeMode; private Chart chart; + private boolean injectWindDataJS = false; private boolean warningAlreadyShown = false; private DialogBox polarDiagramDialogBox; private Button polarDiagramDialogCloseButton; @@ -224,10 +227,12 @@ public class SimulatorMainPanel extends SimplePanel { return NumberFormat.getFormat("0.0").format(value); } - public SimulatorMainPanel(SimulatorServiceAsync svc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, boolean autoUpdate, - char mode, char event, boolean showGrid, boolean showLines, char seedLines, boolean showArrows, boolean showStreamlets) { + public SimulatorMainPanel(SimulatorServiceAsync svc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, int border, int particles, boolean autoUpdate, + char mode, char event, boolean showGrid, boolean showLines, char seedLines, boolean showArrows, boolean showStreamlets, boolean showStreamlets2, boolean injectWindDataJS, boolean showMapControls) { super(); + this.particles = particles; + this.simulatorSvc = svc; this.stringMessages = stringMessages; this.errorReporter = errorReporter; @@ -293,26 +298,26 @@ public class SimulatorMainPanel extends SimplePanel { windParams.setShowLines(showLines); windParams.setSeedLines(seedLines); windParams.setShowStreamlets(showStreamlets); + windParams.setShowStreamlets2(showStreamlets2); this.setDefaultTimeSettings(); - + this.injectWindDataJS = injectWindDataJS; + timer = new Timer(PlayModes.Replay, 1000l); TimeRangeWithZoomProvider timeRangeProvider = new TimeRangeWithZoomModel(); initTimer(); timer.setTime(windParams.getStartTime().getTime()); int secondsTimeStep = (int) windParams.getTimeStep().getTime() / 1000; timer.setPlaySpeedFactor(secondsTimeStep); - timer.setAutoAdvance(true); - timePanel = new TimePanel(timer, timeRangeProvider, stringMessages, false); busyIndicator = new SimpleBusyIndicator(false, 0.8f); - simulatorMap = new SimulatorMap(simulatorSvc, stringMessages, errorReporter, xRes, yRes, timer, timePanel, - windParams, busyIndicator, mode, this); + simulatorMap = new SimulatorMap(simulatorSvc, stringMessages, errorReporter, xRes, yRes, border, timer, timePanel, + windParams, busyIndicator, mode, this, showMapControls); simulatorMap.setSize("100%", "100%"); this.rightPanel.add(this.simulatorMap); - + createOptionsPanelTop(); createOptionsPanel(); @@ -333,6 +338,23 @@ public class SimulatorMainPanel extends SimplePanel { this.polarDiagramDialogBox = this.createPolarDiagramDialogBox(); } + public native void setMapInstance(Object mapInstance) /*-{ + $wnd.swarmMap = mapInstance; + }-*/; + + public native void setCanvasProjectionInstance(Object instance) /*-{ + $wnd.swarmCanvasProjection = instance; + }-*/; + + public native void startStreamlets() /*-{ + if ($wnd.swarmAnimator) { + $wnd.swarmUpdData = true; + $wnd.updateStreamlets($wnd.swarmUpdData); + } else { + $wnd.initStreamlets($wnd.swarmMap); + } + }-*/; + public void setDefaultTimeSettings() { Date defaultNow = new Date(); DateTimeFormat fmt = DateTimeFormat.getFormat("Z"); @@ -464,6 +486,10 @@ public class SimulatorMainPanel extends SimplePanel { return windControlPanel; } + public WindPatternDisplay getWindPatternDisplay() { + return currentWPDisplay; + } + private Panel getSliderPanel(Panel parentPanel, WindPatternSetting s) { String labelName = s.getDisplayName(); @@ -823,36 +849,53 @@ public class SimulatorMainPanel extends SimplePanel { private void update() { - // int selectedBoatClassIndex = boatClassSelector.getSelectedIndex(); - // int selectedRaceIndex = raceSelector.getSelectedIndex(); - // int selectedCompetitorIndex = competitorSelector.getSelectedIndex(); - int selectedLegIndex = legSelector.getSelectedIndex(); + if (this.windParams.isShowStreamlets2()) { - SimulatorUISelectionDTO selection = new SimulatorUISelectionDTO(boatClassSelector.getSelectedIndex(), raceSelector.getSelectedIndex(), - competitorSelector.getSelectedIndex(), legSelector.getSelectedIndex()); + this.setMapInstance(this.simulatorMap.getMap().getJso()); + this.setCanvasProjectionInstance(this.simulatorMap.getRegattaAreaCanvasOverlay().getMapProjection()); - if (windDisplayButton.getValue()) { - showTimePanel(true); - simulatorMap.refreshView(SimulatorMap.ViewName.WINDDISPLAY, currentWPDisplay, selection, true); - } else if (summaryButton.getValue()) { - showTimePanel(false); - simulatorMap.getRegattaAreaCanvasOverlay().draw(); - simulatorMap.refreshView(SimulatorMap.ViewName.SUMMARY, currentWPDisplay, selection, true); - } else if (replayButton.getValue()) { - showTimePanel(true); - simulatorMap.refreshView(SimulatorMap.ViewName.REPLAY, currentWPDisplay, selection, true); - } else { - if (mode == SailingSimulatorConstants.ModeMeasured) { + //this.setDefaultTimeSettings(); + //this.simulatorMap.generateWindField(this.getWindPatternDisplay(), true); - if (selectedLegIndex % 2 != 0) { - errorReporter.reportError("Downwind legs are NOT supported yet. Sorry about that :)"); - } else { - simulatorMap.removePolyline(); - showTimePanel(false); - simulatorMap.refreshView(SimulatorMap.ViewName.SUMMARY, currentWPDisplay, selection, true); - } - } - } + } + + if (this.injectWindDataJS) { + + this.startStreamlets(); + + } else { + + // int selectedBoatClassIndex = boatClassSelector.getSelectedIndex(); + // int selectedRaceIndex = raceSelector.getSelectedIndex(); + // int selectedCompetitorIndex = competitorSelector.getSelectedIndex(); + int selectedLegIndex = legSelector.getSelectedIndex(); + + SimulatorUISelectionDTO selection = new SimulatorUISelectionDTO(boatClassSelector.getSelectedIndex(), raceSelector.getSelectedIndex(), + competitorSelector.getSelectedIndex(), legSelector.getSelectedIndex()); + + if (windDisplayButton.getValue()) { + showTimePanel(true); + simulatorMap.refreshView(SimulatorMap.ViewName.WINDDISPLAY, currentWPDisplay, selection, true); + } else if (summaryButton.getValue()) { + showTimePanel(false); + simulatorMap.getRegattaAreaCanvasOverlay().draw(); + simulatorMap.refreshView(SimulatorMap.ViewName.SUMMARY, currentWPDisplay, selection, true); + } else if (replayButton.getValue()) { + showTimePanel(true); + simulatorMap.refreshView(SimulatorMap.ViewName.REPLAY, currentWPDisplay, selection, true); + } else { + if (mode == SailingSimulatorConstants.ModeMeasured) { + + if (selectedLegIndex % 2 != 0) { + errorReporter.reportError("Downwind legs are NOT supported yet. Sorry about that :)"); + } else { + simulatorMap.removePolyline(); + showTimePanel(false); + simulatorMap.refreshView(SimulatorMap.ViewName.SUMMARY, currentWPDisplay, selection, true); + } + } + } + } } private void initPolarDiagramButton() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java index d42d30a9608..9a06ae1bd47 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.logging.Logger; import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.maps.client.LoadApi; @@ -19,7 +20,17 @@ import com.google.gwt.maps.client.controls.MapTypeStyle; import com.google.gwt.maps.client.controls.PanControlOptions; import com.google.gwt.maps.client.controls.ScaleControlOptions; import com.google.gwt.maps.client.controls.ZoomControlOptions; +import com.google.gwt.maps.client.events.bounds.BoundsChangeMapEvent; +import com.google.gwt.maps.client.events.bounds.BoundsChangeMapHandler; +import com.google.gwt.maps.client.events.click.ClickMapEvent; +import com.google.gwt.maps.client.events.click.ClickMapHandler; +import com.google.gwt.maps.client.events.idle.IdleMapEvent; +import com.google.gwt.maps.client.events.idle.IdleMapHandler; +import com.google.gwt.maps.client.maptypes.MapTypeStyleElementType; import com.google.gwt.maps.client.maptypes.MapTypeStyleFeatureType; +import com.google.gwt.maps.client.maptypes.MapTypeStyler; +import com.google.gwt.maps.client.overlays.Circle; +import com.google.gwt.maps.client.overlays.CircleOptions; import com.google.gwt.maps.client.overlays.Polyline; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; @@ -58,17 +69,19 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ private static SimulatorResources resources = GWT.create(SimulatorResources.class); private MapWidget map; + private boolean mapPan = false; private boolean dataInitialized; private boolean overlaysInitialized; private WindFieldGenParamsDTO windParams; // the canvas overlays - private WindStreamletsCanvasOverlay windStreamletsCanvasOverlay; + private WindLineGuidesCanvasOverlay windLineGuidesCanvasOverlay; private WindFieldCanvasOverlay windFieldCanvasOverlay; private WindGridCanvasOverlay windGridCanvasOverlay; private WindLineCanvasOverlay windLineCanvasOverlay; private RegattaAreaCanvasOverlay regattaAreaCanvasOverlay; + private WindStreamletsCanvasOverlay windStreamletsCanvasOverlay; private ImageCanvasOverlay windRoseCanvasOverlay; private ImageCanvasOverlay windNeedleCanvasOverlay; private List replayPathCanvasOverlays; @@ -87,6 +100,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ private ColorPalette colorPalette; private int xRes; private int yRes; + private int border; private boolean warningAlreadyShown = false; private SimulatorMainPanel parent = null; private PathPolyline pathPolyline = null; @@ -197,15 +211,20 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ legendCanvasOverlay.setCurrent(result.getWindField().curSpeed,result.getWindField().curBearing); if (timePanel != null) { - timePanel.setMinMax(new Date(startTime), new Date(startTime + maxDurationTime), true); + Date endDate = new Date(startTime + maxDurationTime); + timePanel.setMinMax(new Date(startTime), endDate, true); timePanel.resetTimeSlider(); + timePanel.timeChanged(windParams.getStartTime(), null); + if (windParams.isShowStreamlets2()) { + windStreamletsCanvasOverlay.setEndDate(endDate); + } } /** * Now we always get the wind field */ WindFieldDTO windFieldDTO = result.getWindField(); - LOGGER.info("Number of windDTO : " + windFieldDTO.getMatrix().size()); + //LOGGER.info("Number of windDTO : " + windFieldDTO.getMatrix().size()); if (windParams.isShowGrid()) { windGridCanvasOverlay.addToMap(); @@ -217,6 +236,9 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ windFieldCanvasOverlay.addToMap(); } if (windParams.isShowStreamlets()) { + windLineGuidesCanvasOverlay.addToMap(); + } + if (windParams.isShowStreamlets2()) { windStreamletsCanvasOverlay.addToMap(); } @@ -228,6 +250,9 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ timeListeners.add(windFieldCanvasOverlay); } if (windParams.isShowStreamlets()) { + timeListeners.add(windLineGuidesCanvasOverlay); + } + if (windParams.isShowStreamlets2()) { timeListeners.add(windStreamletsCanvasOverlay); } if (windParams.isShowGrid()) { @@ -244,8 +269,8 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windFieldCanvasOverlay != null) { windFieldCanvasOverlay.setVisible(false); } - if (windStreamletsCanvasOverlay != null) { - windStreamletsCanvasOverlay.setVisible(false); + if (windLineGuidesCanvasOverlay != null) { + windLineGuidesCanvasOverlay.setVisible(false); } if (windGridCanvasOverlay != null) { windGridCanvasOverlay.setVisible(false); @@ -253,6 +278,13 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windLineCanvasOverlay != null) { windLineCanvasOverlay.setVisible(false); } + if (windStreamletsCanvasOverlay != null) { + windStreamletsCanvasOverlay.setVisible(false); + } + } else { + if (windStreamletsCanvasOverlay != null) { + windStreamletsCanvasOverlay.setVisible(true); + } } legendCanvasOverlay.addToMap(); @@ -264,41 +296,14 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ } - public SimulatorMap(SimulatorServiceAsync simulatorSvc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, Timer timer, - WindFieldGenParamsDTO windParams, SimpleBusyIndicator busyIndicator, char mode, - SimulatorMainPanel parent) { - this.simulatorService = simulatorSvc; - this.stringMessages = stringMessages; - this.errorReporter = errorReporter; - this.xRes = xRes; - this.yRes = yRes; - this.timer = timer; - this.timePanel = null; - timer.addTimeListener(this); - this.windParams = windParams; - this.busyIndicator = busyIndicator; - this.mode = mode; - this.colorPalette = new ColorPaletteGenerator(); - this.dataInitialized = false; - this.overlaysInitialized = false; - this.windFieldCanvasOverlay = null; - this.windStreamletsCanvasOverlay = null; - this.windGridCanvasOverlay = null; - this.windLineCanvasOverlay = null; - this.replayPathCanvasOverlays = null; - this.raceCourseCanvasOverlay = null; - this.timeListeners = new LinkedList(); - this.initializeData(); - this.parent = parent; - } - - public SimulatorMap(SimulatorServiceAsync simulatorSvc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, Timer timer, - TimePanel timePanel, WindFieldGenParamsDTO windParams, SimpleBusyIndicator busyIndicator, char mode, SimulatorMainPanel parent) { + public SimulatorMap(SimulatorServiceAsync simulatorSvc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, int border, Timer timer, + TimePanel timePanel, WindFieldGenParamsDTO windParams, SimpleBusyIndicator busyIndicator, char mode, SimulatorMainPanel parent, boolean showMapControls) { this.simulatorService = simulatorSvc; this.stringMessages = stringMessages; this.errorReporter = errorReporter; this.xRes = xRes; this.yRes = yRes; + this.border = border; this.timer = timer; this.timePanel = timePanel; timer.addTimeListener(this); @@ -309,13 +314,13 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ this.dataInitialized = false; this.overlaysInitialized = false; this.windFieldCanvasOverlay = null; - this.windStreamletsCanvasOverlay = null; + this.windLineGuidesCanvasOverlay = null; this.windGridCanvasOverlay = null; this.windLineCanvasOverlay = null; this.replayPathCanvasOverlays = null; this.raceCourseCanvasOverlay = null; this.timeListeners = new LinkedList(); - this.initializeData(); + this.initializeData(showMapControls); this.parent = parent; } @@ -338,16 +343,72 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ mapOptions.setRotateControl(true); mapOptions.setStreetViewControl(false); - MapTypeStyle[] mapTypeStyles = new MapTypeStyle[4]; + MapTypeStyle[] mapTypeStyles; - // hide all transit lines including ferry lines - mapTypeStyles[0] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.TRANSIT); - // hide points of interest - mapTypeStyles[1] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.POI); - // simplify road display - mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD); - // set water color - mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), -35, -34); + if (windParams.isShowStreamlets2()) { + + mapTypeStyles = new MapTypeStyle[11]; + + // hide all transit lines including ferry lines + mapTypeStyles[0] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.TRANSIT); + // hide points of interest + mapTypeStyles[1] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.POI); + // simplify road display + mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD); + // set water color + mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), -50, -50); + mapTypeStyles[4] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.LANDSCAPE, new RGBColor(255, 255, 255), -100, -70); + mapTypeStyles[5] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.POI, new RGBColor(255, 255, 255), -100, -70); + mapTypeStyles[6] = GoogleMapStyleHelper.createElementStyleOnlyLightness(MapTypeStyleFeatureType.ROAD, MapTypeStyleElementType.ALL, -40); + + MapTypeStyle mapStyle = MapTypeStyle.newInstance(); + mapStyle.setFeatureType(MapTypeStyleFeatureType.ADMINISTRATIVE); + mapStyle.setElementType(MapTypeStyleElementType.LABELS__TEXT__FILL); + MapTypeStyler[] typeStylers = new MapTypeStyler[1]; + typeStylers[0] = MapTypeStyler.newInvertLightnessStyler(true); + mapStyle.setStylers(typeStylers); + mapTypeStyles[7] = mapStyle; + + mapStyle = MapTypeStyle.newInstance(); + mapStyle.setFeatureType(MapTypeStyleFeatureType.ADMINISTRATIVE); + mapStyle.setElementType(MapTypeStyleElementType.LABELS__TEXT__STROKE); + typeStylers = new MapTypeStyler[1]; + typeStylers[0] = MapTypeStyler.newInvertLightnessStyler(true); + mapStyle.setStylers(typeStylers); + mapTypeStyles[8] = mapStyle; + + mapStyle = MapTypeStyle.newInstance(); + mapStyle.setFeatureType(MapTypeStyleFeatureType.ADMINISTRATIVE); + mapStyle.setElementType(MapTypeStyleElementType.GEOMETRY__FILL); + typeStylers = new MapTypeStyler[3]; + typeStylers[0] = MapTypeStyler.newHueStyler("#ffffff"); + typeStylers[1] = MapTypeStyler.newLightnessStyler(-70); + typeStylers[2] = MapTypeStyler.newSaturationStyler(-100); + mapStyle.setStylers(typeStylers); + mapTypeStyles[9] = mapStyle; + + mapStyle = MapTypeStyle.newInstance(); + mapStyle.setFeatureType(MapTypeStyleFeatureType.ADMINISTRATIVE); + mapStyle.setElementType(MapTypeStyleElementType.GEOMETRY__STROKE); + typeStylers = new MapTypeStyler[1]; + typeStylers[0] = MapTypeStyler.newLightnessStyler(-70); + mapStyle.setStylers(typeStylers); + mapTypeStyles[10] = mapStyle; + + } else { + + mapTypeStyles = new MapTypeStyle[4]; + + // hide all transit lines including ferry lines + mapTypeStyles[0] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.TRANSIT); + // hide points of interest + mapTypeStyles[1] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.POI); + // simplify road display + mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD); + // set water color + mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), -35, -34); + + } mapOptions.setMapTypeStyles(mapTypeStyles); @@ -376,7 +437,72 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ add(map, 0, 0); map.setSize("100%", "100%"); - + + if (windParams.isShowStreamlets2()) { + map.addBoundsChangeHandler(new BoundsChangeMapHandler() { + @Override + public void onEvent(BoundsChangeMapEvent event) { + // improve browser performance by deferred scheduling of redraws + Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { + public void execute() { + if (windStreamletsCanvasOverlay.getSwarm() != null) { + windStreamletsCanvasOverlay.getSwarm().onBoundsChanged(); + } + } + }); + } + }); + } + + /*map.addZoomChangeHandler(new ZoomChangeMapHandler() { + + @Override + public void onEvent(ZoomChangeMapEvent event) { + // TODO Auto-generated method stub + windRoseCanvasOverlay.removeFromMap(); + windNeedleCanvasOverlay.removeFromMap(); + mapPan = true; + } + + }); + + map.addDragStartHandler(new DragStartMapHandler() { + + @Override + public void onEvent(DragStartMapEvent event) { + // TODO Auto-generated method stub + windRoseCanvasOverlay.removeFromMap(); + windNeedleCanvasOverlay.removeFromMap(); + } + + }); + + map.addDragEndHandler(new DragEndMapHandler() { + + @Override + public void onEvent(DragEndMapEvent event) { + // TODO Auto-generated method stub + windRoseCanvasOverlay.addToMap(); + windNeedleCanvasOverlay.addToMap(); + } + + });*/ + + map.addIdleHandler(new IdleMapHandler() { + + @Override + public void onEvent(IdleMapEvent event) { + // TODO Auto-generated method stub + if (mapPan) { + //System.out.println("Map Idle Event Handler."); + getWindRoseCanvasOverlay().addToMap(); + getWindNeedleCanvasOverlay().addToMap(); + mapPan = false; + } + } + + }); + initializeOverlays(); dataInitialized = true; @@ -420,7 +546,10 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ windFieldCanvasOverlay = new WindFieldCanvasOverlay(map, SimulatorMapOverlaysZIndexes.WINDFIELD_ZINDEX, timer, windParams); } if (windParams.isShowStreamlets()) { - windStreamletsCanvasOverlay = new WindStreamletsCanvasOverlay(map, SimulatorMapOverlaysZIndexes.WINDSTREAMLETS_ZINDEX, timer, xRes); + windLineGuidesCanvasOverlay = new WindLineGuidesCanvasOverlay(map, SimulatorMapOverlaysZIndexes.WINDSTREAMLETS_ZINDEX, timer, xRes); + } + if (windParams.isShowStreamlets2()) { + windStreamletsCanvasOverlay = new WindStreamletsCanvasOverlay(this, SimulatorMapOverlaysZIndexes.WINDFIELD_ZINDEX, timer, windParams); } if (windParams.isShowGrid()) { windGridCanvasOverlay = new WindGridCanvasOverlay(map, SimulatorMapOverlaysZIndexes.WINDGRID_ZINDEX, timer, xRes, yRes); @@ -443,8 +572,8 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windFieldCanvasOverlay != null) { windFieldCanvasOverlay.onResize(); } - if (windStreamletsCanvasOverlay != null) { - windStreamletsCanvasOverlay.onResize(); + if (windLineGuidesCanvasOverlay != null) { + windLineGuidesCanvasOverlay.onResize(); } if (windGridCanvasOverlay != null) { windGridCanvasOverlay.onResize(); @@ -461,12 +590,17 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ overlaysInitialized = true; } - private void generateWindField(final WindPatternDisplay windPatternDisplay, final boolean removeOverlays) { + public void generateWindField(final WindPatternDisplay windPatternDisplay, final boolean removeOverlays) { LOGGER.info("In generateWindField"); if (windPatternDisplay == null) { errorReporter.reportError("Please select a valid wind pattern."); return; } + + if ((windStreamletsCanvasOverlay != null)&&(windStreamletsCanvasOverlay.isVisible())) { + windStreamletsCanvasOverlay.setVisible(false); + } + PositionDTO startPointDTO = new PositionDTO(raceCourseCanvasOverlay.getStartPoint().getLatitude(), raceCourseCanvasOverlay.getStartPoint().getLongitude()); PositionDTO endPointDTO = new PositionDTO(raceCourseCanvasOverlay.getEndPoint().getLatitude(), @@ -476,6 +610,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ windParams.setSouthEast(endPointDTO); windParams.setxRes(xRes); windParams.setyRes(yRes); + windParams.setBorder(border); busyIndicator.setBusy(true); timer.setTime(windParams.getStartTime().getTime()); @@ -490,7 +625,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (removeOverlays) { removeOverlays(); } - LOGGER.info("Number of windDTO : " + wl.getMatrix().size()); + //LOGGER.info("Number of windDTO : " + wl.getMatrix().size()); // Window.alert("Number of windDTO : " + wl.getMatrix().size()); if (windParams.isShowGrid()) { windGridCanvasOverlay.addToMap(); @@ -502,6 +637,9 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ windFieldCanvasOverlay.addToMap(); } if (windParams.isShowStreamlets()) { + windLineGuidesCanvasOverlay.addToMap(); + } + if (windParams.isShowStreamlets2()) { windStreamletsCanvasOverlay.addToMap(); } refreshWindFieldOverlay(wl); @@ -510,7 +648,11 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ timeListeners.add(windFieldCanvasOverlay); } if (windParams.isShowStreamlets()) { + timeListeners.add(windLineGuidesCanvasOverlay); + } + if (windParams.isShowStreamlets2()) { timeListeners.add(windStreamletsCanvasOverlay); + windStreamletsCanvasOverlay.setVisible(true); } if (windParams.isShowGrid()) { timeListeners.add(windGridCanvasOverlay); @@ -520,7 +662,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ } timePanel.setMinMax(windParams.getStartTime(), windParams.getEndTime(), true); timePanel.resetTimeSlider(); - //timer.setTime(windParams.getStartTime().getTime()); + timePanel.timeChanged(windParams.getStartTime(), null); busyIndicator.setBusy(false); } @@ -528,11 +670,14 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ } private void refreshWindFieldOverlay(WindFieldDTO windFieldDTO) { + if (this.windStreamletsCanvasOverlay != null) { + this.windStreamletsCanvasOverlay.setWindField(windFieldDTO); + } if (this.windFieldCanvasOverlay != null) { this.windFieldCanvasOverlay.setWindField(windFieldDTO); } - if (this.windStreamletsCanvasOverlay != null) { - this.windStreamletsCanvasOverlay.setWindField(windFieldDTO); + if (this.windLineGuidesCanvasOverlay != null) { + this.windLineGuidesCanvasOverlay.setWindField(windFieldDTO); } if (this.windGridCanvasOverlay != null) { this.windGridCanvasOverlay.setWindField(windFieldDTO); @@ -549,7 +694,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ this.windFieldCanvasOverlay.draw(); } if (this.windParams.isShowStreamlets()) { - this.windStreamletsCanvasOverlay.draw(); + this.windLineGuidesCanvasOverlay.draw(); } if (this.windParams.isShowGrid()) { this.windGridCanvasOverlay.draw(); @@ -567,6 +712,10 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ return; } + if ((windStreamletsCanvasOverlay != null)&&(windStreamletsCanvasOverlay.isVisible())) { + windStreamletsCanvasOverlay.setVisible(false); + } + if (mode != SailingSimulatorConstants.ModeMeasured) { PositionDTO startPointDTO = new PositionDTO(raceCourseCanvasOverlay.getStartPoint().getLatitude(), raceCourseCanvasOverlay.getStartPoint().getLongitude()); @@ -579,6 +728,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ windParams.setxRes(xRes); windParams.setyRes(yRes); + windParams.setBorder(border); busyIndicator.setBusy(true); timer.setTime(windParams.getStartTime().getTime()); @@ -611,6 +761,10 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ windFieldCanvasOverlay.removeFromMap(); num++; } + if (windLineGuidesCanvasOverlay != null) { + windLineGuidesCanvasOverlay.removeFromMap(); + num++; + } if (windStreamletsCanvasOverlay != null) { windStreamletsCanvasOverlay.removeFromMap(); num++; @@ -656,6 +810,9 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windStreamletsCanvasOverlay != null) { windStreamletsCanvasOverlay.setVisible(false); } + if (windLineGuidesCanvasOverlay != null) { + windLineGuidesCanvasOverlay.setVisible(false); + } if (windGridCanvasOverlay != null) { windGridCanvasOverlay.setVisible(false); } @@ -692,7 +849,10 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windFieldCanvasOverlay != null) { windFieldCanvasOverlay.setVisible(true); } - if (windStreamletsCanvasOverlay != null) { + if (windLineGuidesCanvasOverlay != null) { + windLineGuidesCanvasOverlay.setVisible(true); + } + if ((windStreamletsCanvasOverlay != null)&&(!windStreamletsCanvasOverlay.isVisible())) { windStreamletsCanvasOverlay.setVisible(true); } if (windGridCanvasOverlay != null) { @@ -731,9 +891,13 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ windFieldCanvasOverlay.setVisible(true); windFieldCanvasOverlay.draw(); } - if (windStreamletsCanvasOverlay != null) { + if (windLineGuidesCanvasOverlay != null) { + windLineGuidesCanvasOverlay.setVisible(true); + windLineGuidesCanvasOverlay.draw(); + } + if ((windStreamletsCanvasOverlay != null)&&(!windStreamletsCanvasOverlay.isVisible())) { windStreamletsCanvasOverlay.setVisible(true); - windStreamletsCanvasOverlay.draw(); + //windStreamletsCanvasOverlay.draw(); } if (windGridCanvasOverlay != null) { windGridCanvasOverlay.setVisible(true); @@ -773,6 +937,9 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ } switch (name) { case SUMMARY: + if ((windStreamletsCanvasOverlay != null)&&(windStreamletsCanvasOverlay.isVisible())) { + windStreamletsCanvasOverlay.setVisible(false); + } refreshSummaryView(windPatternDisplay, selection, force); break; case REPLAY: @@ -795,7 +962,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ } @Override - public void initializeData() { + public void initializeData(boolean showMapControls) { loadMapsAPIV3(); } @@ -808,7 +975,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ public void timeChanged(Date newTime, Date oldTime) { if (shallStop()) { LOGGER.info("Stopping the timer"); - timer.stop(); + timer.pause(); } } @@ -889,5 +1056,44 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ public RaceCourseCanvasOverlay getRaceCourseCanvasOverlay() { return raceCourseCanvasOverlay; } - + + public void drawCircleFromRadius(int regIdx, CourseAreaDescriptor courseArea) { + + CircleOptions circleOptions = CircleOptions.newInstance(); + circleOptions.setStrokeColor("white"); + circleOptions.setStrokeWeight(1); + circleOptions.setStrokeOpacity(0.0); + circleOptions.setFillColor("green"); + circleOptions.setFillOpacity(0.0); + circleOptions.setCenter(courseArea.getCenterPos()); + Circle circle = Circle.newInstance(circleOptions); + circle.setRadius(courseArea.getRadiusInMeters()); + + final int regIdxFinal = regIdx; + + circle.addClickHandler(new ClickMapHandler() { + public void onEvent(ClickMapEvent e) { + //System.out.println("Click: "+currentCourseArea.getName()); + CourseAreaDescriptor newRegArea = regattaAreaCanvasOverlay.getVenue().getCourseAreas().get(regIdxFinal); + + if (newRegArea != regattaAreaCanvasOverlay.getCurrentCourseArea()) { + regattaAreaCanvasOverlay.setCurrentCourseArea(newRegArea); + clearOverlays(); + getWindRoseCanvasOverlay().removeFromMap(); + getWindNeedleCanvasOverlay().removeFromMap(); + regattaAreaCanvasOverlay.updateRaceCourse(0, 0); + raceCourseCanvasOverlay.draw(); + } + + map.panTo(regattaAreaCanvasOverlay.getCurrentCourseArea().getCenterPos()); + mapPan = true; + if (map.getZoom() < 14) { + map.setZoom(14); + } + + } + }); + + circle.setMap(getMap()); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/VenueDescriptorFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/VenueDescriptorFactory.java index a9a453f9a27..7626d10cd12 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/VenueDescriptorFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/VenueDescriptorFactory.java @@ -38,15 +38,15 @@ public class VenueDescriptorFactory { // Foxtrot cPos = LatLng.newInstance(54.445775, 10.29223889); - courseAreas.add(new CourseAreaDescriptor("Foxtrot", cPos, 0.65, "#B4287C")); + courseAreas.add(new CourseAreaDescriptor("Foxtrot", cPos, 0.65, "#DA58A6")); // India cPos = LatLng.newInstance(54.44803611, 10.20863611); - courseAreas.add(new CourseAreaDescriptor("India", cPos, 0.40, "#774741")); + courseAreas.add(new CourseAreaDescriptor("India", cPos, 0.40, "#B7827B")); // Juliett cPos = LatLng.newInstance(54.46183611, 10.2239); - courseAreas.add(new CourseAreaDescriptor("Juliett", cPos, 0.55, "#818585")); + courseAreas.add(new CourseAreaDescriptor("Juliett", cPos, 0.55, "#979b9b")); // Echo cPos = LatLng.newInstance(54.47640278, 10.20090556); @@ -58,7 +58,7 @@ public class VenueDescriptorFactory { // Charlie cPos = LatLng.newInstance(54.49327222, 10.17525833); - courseAreas.add(new CourseAreaDescriptor("Charlie", cPos, 0.70, "#0A5998")); + courseAreas.add(new CourseAreaDescriptor("Charlie", cPos, 0.70, "#2796f1")); // Delta cPos = LatLng.newInstance(54.49706111, 10.21921944); @@ -66,11 +66,11 @@ public class VenueDescriptorFactory { // Bravo cPos = LatLng.newInstance(54.50911667, 10.13973333); - courseAreas.add(new CourseAreaDescriptor("Bravo", cPos, 0.80, "#CE3032")); + courseAreas.add(new CourseAreaDescriptor("Bravo", cPos, 0.80, "#d34547")); // Alfa cPos = LatLng.newInstance(54.52905, 10.18515278); - courseAreas.add(new CourseAreaDescriptor("Alfa", cPos, 1.00, "#D9699B")); + courseAreas.add(new CourseAreaDescriptor("Alfa", cPos, 1.00, "#d65c93")); venue.setDefaultCourseArea(defaultCourseArea); return venue; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindGridCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindGridCanvasOverlay.java index a5ec6af5430..f87e1e8195c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindGridCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindGridCanvasOverlay.java @@ -19,11 +19,11 @@ import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.base.LatLng; import com.google.gwt.maps.client.base.Point; import com.sap.sailing.domain.common.dto.PositionDTO; -import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; import com.sap.sailing.gwt.ui.shared.WindFieldDTO; import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay; import com.sap.sailing.gwt.ui.simulator.util.WindGridColorPalette; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.player.Timer; /** @@ -47,7 +47,7 @@ public class WindGridCanvasOverlay extends FullCanvasOverlay implements TimeList private int xRes; private int yRes; private SimulatorWindDTO[][] windMatrix; - private Map, GridCell> gridCellMap; + private Map, GridCell> gridCellMap; private WindGridColorPalette colorPalette; private static Logger logger = Logger.getLogger(WindFieldCanvasOverlay.class.getName()); @@ -359,7 +359,7 @@ public class WindGridCanvasOverlay extends FullCanvasOverlay implements TimeList if (numRow >= 4) { final int numCol = windMatrix[0].length; if (numCol >= 2) { - gridCellMap = new HashMap, GridCell>(); + gridCellMap = new HashMap, GridCell>(); for (int i = 1; i < numRow - 1; ++i) { for (int j = 1; j < numCol - 1; ++j) { final PositionDTO bl = getCenter(windMatrix[i - 1][j - 1].position, windMatrix[i - 1][j].position, @@ -371,7 +371,7 @@ public class WindGridCanvasOverlay extends FullCanvasOverlay implements TimeList final PositionDTO tr = getCenter(windMatrix[i][j].position, windMatrix[i][j + 1].position, windMatrix[i + 1][j].position, windMatrix[i + 1][j + 1].position); final GridCell cell = new GridCell(bl, br, tl, tr, windMatrix[i - 1][j].trueWindSpeedInKnots); - final Pair cellPair = new Pair(i, j); + final Util.Pair cellPair = new Util.Pair(i, j); gridCellMap.put(cellPair, cell); // drawGridCell(cell); } @@ -390,7 +390,7 @@ public class WindGridCanvasOverlay extends FullCanvasOverlay implements TimeList if (numCol >= 2) { for (int i = 1; i < numRow - 1; ++i) { for (int j = 1; j < numCol - 1; ++j) { - final Pair cellPair = new Pair(i, j); + final Util.Pair cellPair = new Util.Pair(i, j); final GridCell cell = gridCellMap.get(cellPair); cell.windSpeedInKnots = windMatrix[i - 1][j].trueWindSpeedInKnots; } @@ -403,7 +403,7 @@ public class WindGridCanvasOverlay extends FullCanvasOverlay implements TimeList private void drawGridCell() { if (gridCellMap != null & !gridCellMap.isEmpty()) { - for (final Entry, GridCell> cell : gridCellMap.entrySet()) { + for (final Entry, GridCell> cell : gridCellMap.entrySet()) { drawGridCell(cell.getValue()); } } @@ -460,16 +460,16 @@ public class WindGridCanvasOverlay extends FullCanvasOverlay implements TimeList final PositionDTO[] corners = new PositionDTO[4]; final int numRow = windMatrix.length; final int numCol = windMatrix[0].length; - final Pair cellPair1 = new Pair(1, 1); + final Util.Pair cellPair1 = new Util.Pair(1, 1); corners[0] = gridCellMap.get(cellPair1).bottomLeft; - final Pair cellPair2 = new Pair(1, numCol-2); + final Util.Pair cellPair2 = new Util.Pair(1, numCol-2); corners[1] = gridCellMap.get(cellPair2).bottomRight; - final Pair cellPair3 = new Pair(numRow-2, numCol-2); + final Util.Pair cellPair3 = new Util.Pair(numRow-2, numCol-2); corners[2] = gridCellMap.get(cellPair3).topRight; - final Pair cellPair4 = new Pair(numRow-2, 1); + final Util.Pair cellPair4 = new Util.Pair(numRow-2, 1); corners[3] = gridCellMap.get(cellPair4).topLeft; return corners; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineCanvasOverlay.java index acbd4ca3c36..571d2749922 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineCanvasOverlay.java @@ -229,7 +229,7 @@ public class WindLineCanvasOverlay extends FullCanvasOverlay implements TimeList final double weight = 1.0; LatLng positionLatLng = LatLng.newInstance(p1.latDeg, p1.lngDeg); - Point canvasPositionInPx = mapProjection.fromLatLngToContainerPixel(positionLatLng); + Point canvasPositionInPx = mapProjection.fromLatLngToDivPixel(positionLatLng); final double x1 = canvasPositionInPx.getX() - this.getWidgetPosLeft(); final double y1 = canvasPositionInPx.getY() - this.getWidgetPosTop(); @@ -263,7 +263,7 @@ public class WindLineCanvasOverlay extends FullCanvasOverlay implements TimeList } @Override - protected void setCanvasSettings() { + public void setCanvasSettings() { if (corners != null && corners.length == 4) { final Point corner0 = getPointInDivPixel(corners[0]); final Point corner1 = getPointInDivPixel(corners[1]); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineGuidesCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineGuidesCanvasOverlay.java new file mode 100644 index 00000000000..c1a7a3799d8 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineGuidesCanvasOverlay.java @@ -0,0 +1,273 @@ +package com.sap.sailing.gwt.ui.simulator; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.logging.Logger; + +import com.google.gwt.canvas.dom.client.Context2d; +import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.base.Point; +import com.sap.sailing.domain.common.dto.PositionDTO; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; +import com.sap.sailing.gwt.ui.shared.WindFieldDTO; +import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay; +import com.sap.sailing.gwt.ui.simulator.util.ToolTip; +import com.sap.sse.gwt.client.player.Timer; + +/** + * A google map overlay based on a HTML5 canvas for drawing a wind field. The overlay covers the whole map and displays + * the wind objects inside it. + * + * @author Nidhi Sawhney(D054070) + * + */ +public class WindLineGuidesCanvasOverlay extends FullCanvasOverlay implements TimeListenerWithStoppingCriteria { + + /** The wind field that is to be displayed in the overlay */ + private WindFieldDTO windFieldDTO; + + /** + * Map containing the windfield for easy retrieval with key as time point. + */ + private SortedMap> timePointWindDTOMap; + + /** The points where ToolTip is to be displayed */ + private Map windFieldPoints; + private String arrowColor = "Black"; + private String arrowHeadColor = "Blue"; + + private int xRes; + private Timer timer; + + private static Logger logger = Logger.getLogger(WindLineGuidesCanvasOverlay.class.getName()); + + public WindLineGuidesCanvasOverlay(MapWidget map, int zIndex, final Timer timer, int xRes) { + super(map, zIndex); + + this.timer = timer; + this.xRes = xRes; + + windFieldDTO = null; + windFieldPoints = new HashMap(); + + timePointWindDTOMap = new TreeMap>(); + } + + public WindLineGuidesCanvasOverlay(MapWidget map, int zIndex) { + this(map, zIndex, null, 0); + } + + @Override + public void addToMap() { + super.addToMap(); + + if (timer != null) { + timer.addTimeListener(this); + } + } + + @Override + public void removeFromMap() { + super.removeFromMap(); + + if (timer != null) { + timer.removeTimeListener(this); + } + } + + public void setWindField(final WindFieldDTO windField) { + this.windFieldDTO = windField; + + timePointWindDTOMap.clear(); + if (windField != null) { + for(final SimulatorWindDTO w : windField.getMatrix()) { + if (!timePointWindDTOMap.containsKey(w.timepoint)) { + timePointWindDTOMap.put(w.timepoint, new LinkedList()); + } + timePointWindDTOMap.get(w.timepoint).add(w); + } + } + } + + public void setArrowColor(final String arrowColor, final String arrowHeadColor) { + this.arrowColor = arrowColor; + this.arrowHeadColor = arrowHeadColor; + } + + @Override + protected void draw() { + super.draw(); + if (mapProjection != null && windFieldDTO != null) { + clear(); + drawWindField(); + } + } + + private void clear() { + canvas.getContext2d().clearRect(0.0 /*canvas.getAbsoluteLeft()*/, 0.0/*canvas.getAbsoluteTop()*/, + canvas.getCoordinateSpaceWidth(), canvas.getCoordinateSpaceHeight()); + windFieldPoints.clear(); + } + + protected void drawWindField() { + if (timer != null) { + timeChanged(timer.getTime(), null); + } else { + drawWindField(windFieldDTO.getMatrix()); + } + } + + protected void drawScaledArrow(final SimulatorWindDTO windDTO, final double angle, final int index, final double pxLength, final boolean drawHead) { + + final double aWidth = 1.0; + drawArrow(windDTO, angle, pxLength, aWidth, index, drawHead); + + } + + protected void drawWindField(final List windDTOList) { + final boolean drawHead = false; + clear(); + final Context2d context2d = canvas.getContext2d(); + context2d.setGlobalAlpha(0.4); + + if (windDTOList != null && windDTOList.size() > 0) { + Iterator windDTOIter = windDTOList.iterator(); + final SimulatorWindDTO w0 = windDTOIter.next(); + final SimulatorWindDTO w1 = windDTOIter.next(); + final LatLng pg0 = LatLng.newInstance(w0.position.latDeg, w0.position.lngDeg); + final LatLng pg1 = LatLng.newInstance(w1.position.latDeg, w1.position.lngDeg); + final Point px0 = mapProjection.fromLatLngToDivPixel(pg0); + final Point px1 = mapProjection.fromLatLngToDivPixel(pg1); + final double dx = px0.getX()-px1.getX(); + final double dy = px0.getY()-px1.getY(); + final double pxLength = Math.sqrt( dx*dx + dy*dy ); + logger.fine("pxLength = "+pxLength); + + windDTOIter = windDTOList.iterator(); + int index = 0; + while (windDTOIter.hasNext()) { + SimulatorWindDTO windDTO = windDTOIter.next(); + //System.out.println("wind angle: "+index+", "+windDTO.trueWindBearingDeg); + if (((index % xRes) > 0)&&((index % xRes) < (xRes-1))) { + DegreeBearingImpl dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg); + drawScaledArrow(windDTO, dbi.getRadians(), index, pxLength, drawHead); + } + index++; + } + final String title = "Wind Field at " + windDTOList.size() + " points."; + getCanvas().setTitle(title); + } + } + + protected void drawArrow(final SimulatorWindDTO windDTO, final double angle, final double length, final double weight, final int index, final boolean drawHead) { + final String msg = "Wind @ P" + index + ": time : " + windDTO.timepoint + " speed: " + windDTO.trueWindSpeedInKnots + "knots " + + windDTO.trueWindBearingDeg; + logger.fine(msg); + + final Context2d context2d = canvas.getContext2d(); + context2d.setGlobalAlpha(0.2); + + final PositionDTO position = windDTO.position; + + final LatLng positionLatLng = LatLng.newInstance(position.latDeg, position.lngDeg); + final Point canvasPositionInPx = mapProjection.fromLatLngToDivPixel(positionLatLng); + + final double x = canvasPositionInPx.getX() - getWidgetPosLeft(); + final double y = canvasPositionInPx.getY() - getWidgetPosTop(); + + windFieldPoints.put(new ToolTip(x, y), windDTO); + + final double dx = length * Math.sin(angle); + final double dy = -length * Math.cos(angle); + + final double x1 = x + dx / 2; + final double y1 = y + dy / 2; + + drawLine(x - dx / 2, y - dy / 2, x1, y1, weight, arrowColor); + + final double theta = Math.atan2(-dy, dx); + + final double hLength = Math.max(6.,6.+(10./(60.-10.))*Math.max(length-6.,0)); + logger.finer("headlength: "+hLength+", arrowlength: "+length); + + if (drawHead) { + drawHead(x1, y1, theta, hLength, weight); + } + //String text = "P" + index;// + NumberFormat.getFormat("0.00").format(windDTO.trueWindBearingDeg) + "�"; + //drawPointWithText(x, y, text); + //drawPoint(x, y); + + context2d.setGlobalAlpha(0.4); + + } + + protected void drawHead(final double x, final double y, final double theta, final double headLength, final double weight) { + + double t = theta + (Math.PI / 4); + if (t > Math.PI) { + t -= 2 * Math.PI; + } + double t2 = theta - (Math.PI / 4); + if (t2 <= (-Math.PI)) { + t2 += 2 * Math.PI; + } + + final double x1 = (x - Math.cos(t) * headLength); + final double y1 = (y + Math.sin(t) * headLength); + final double x1o = (x + Math.cos(t) * weight/2); + final double y1o = (y - Math.sin(t) * weight/2); + final double x2 = (x - Math.cos(t2) * headLength); + final double y2 = (y + Math.sin(t2) * headLength); + final double x2o = (x + Math.cos(t2) * weight/2); + final double y2o = (y - Math.sin(t2) * weight/2); + drawLine(x1o, y1o, x1, y1, weight, arrowHeadColor); + drawLine(x2o, y2o, x2, y2, weight, arrowHeadColor); + + } + + @Override + public void timeChanged(final Date newDate, Date oldDate) { + + List windDTOToDraw = new ArrayList(); + + final SortedMap> headMap = (timePointWindDTOMap.headMap(newDate.getTime()+1)); + + if (!headMap.isEmpty()) { + windDTOToDraw = headMap.get(headMap.lastKey()); + } + logger.info("In WindFieldCanvasOverlay.drawWindField drawing " + windDTOToDraw.size() + " points" + " @ " + + newDate); + + drawWindField(windDTOToDraw); + + } + + @Override + public boolean shallStop() { + if (!this.isVisible() || timePointWindDTOMap == null || timer == null || timePointWindDTOMap.isEmpty()) { + return true; + } + if (timePointWindDTOMap.lastKey() < timer.getTime().getTime()) { + return true; + } else { + return false; + } + } + + public void setTimer(final Timer timer) { + this.timer = timer; + } + + public Timer getTimer() { + return timer; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindStreamletsCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindStreamletsCanvasOverlay.java old mode 100644 new mode 100755 index ada85ac31a0..0eb9261d174 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindStreamletsCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindStreamletsCanvasOverlay.java @@ -1,26 +1,20 @@ package com.sap.sailing.gwt.ui.simulator; -import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; import java.util.logging.Logger; -import com.google.gwt.canvas.dom.client.Context2d; -import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.json.client.JSONArray; +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.maps.client.base.LatLng; -import com.google.gwt.maps.client.base.Point; -import com.sap.sailing.domain.common.dto.PositionDTO; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; import com.sap.sailing.gwt.ui.shared.WindFieldDTO; +import com.sap.sailing.gwt.ui.shared.WindFieldGenParamsDTO; import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay; -import com.sap.sailing.gwt.ui.simulator.util.ToolTip; +import com.sap.sailing.gwt.ui.simulator.streamlets.Swarm; import com.sap.sse.gwt.client.player.Timer; /** @@ -31,45 +25,219 @@ import com.sap.sse.gwt.client.player.Timer; * */ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements TimeListenerWithStoppingCriteria { - + /** The wind field that is to be displayed in the overlay */ - private WindFieldDTO windFieldDTO; - - /** - * Map containing the windfield for easy retrieval with key as time point. - */ - private SortedMap> timePointWindDTOMap; + protected WindFieldDTO windFieldDTO; + protected WindFieldGenParamsDTO windParams = null; + protected SimulatorMap simulatorMap; - /** The points where ToolTip is to be displayed */ - private Map windFieldPoints; - private String arrowColor = "Black"; - private String arrowHeadColor = "Blue"; - - private int xRes; + private boolean visible = false; private Timer timer; + private Date endDate; + + private int nParticles; + private Swarm swarm; private static Logger logger = Logger.getLogger(WindStreamletsCanvasOverlay.class.getName()); - public WindStreamletsCanvasOverlay(MapWidget map, int zIndex, final Timer timer, int xRes) { - super(map, zIndex); + public WindStreamletsCanvasOverlay(SimulatorMap simulatorMap, int zIndex, final Timer timer, final WindFieldGenParamsDTO windParams) { + super(simulatorMap.getMap(), zIndex); + this.simulatorMap = simulatorMap; this.timer = timer; - this.xRes = xRes; + this.windParams = windParams; + + this.nParticles = this.simulatorMap.getMainPanel().particles; + windFieldDTO = null; - windFieldPoints = new HashMap(); + + getCanvas().getElement().setId("swarm-display"); - timePointWindDTOMap = new TreeMap>(); } - public WindStreamletsCanvasOverlay(MapWidget map, int zIndex) { - this(map, zIndex, null, 0); + public WindFieldDTO getWindFieldDTO() { + return this.windFieldDTO; + } + + public WindFieldGenParamsDTO getWindParams() { + return this.windParams; + } + + public JavaScriptObject getJSONRandomWindData() { + + JSONObject jsonWindData = new JSONObject(); + + jsonWindData.put("timestamp", new JSONString("11:55 am on March 18, 2014")); + + LatLng boundsSW = LatLng.newInstance(53.854617, 8.159124); + LatLng boundsNE = LatLng.newInstance(55.263922, 11.413824); + + jsonWindData.put("x0", new JSONNumber(boundsSW.getLongitude())); + jsonWindData.put("y0", new JSONNumber(boundsSW.getLatitude())); + jsonWindData.put("x1", new JSONNumber(boundsNE.getLongitude())); + jsonWindData.put("y1", new JSONNumber(boundsNE.getLatitude())); + + int maxSteps = 1; + int gridWidth = 100; + int gridHeight = 100; + jsonWindData.put("gridWidth", new JSONNumber(gridWidth)); + jsonWindData.put("gridHeight", new JSONNumber(gridHeight)); + + JSONArray windField = new JSONArray(); + + for(int stp=0; stp < maxSteps; stp++) { + JSONArray vectorField = new JSONArray(); + for(int idx=0; idx < (gridWidth*gridHeight*2); idx++) { + vectorField.set(idx, new JSONNumber((Math.random()-0.5)*10.0+3.0)); + //windField.set(idx, new JSONNumber(10.0)); + } + windField.set(stp, vectorField); + } + jsonWindData.put("field", windField); + + return jsonWindData.getJavaScriptObject(); + } + + public native boolean isSwarmDataExt() /*-{ + if ($wnd.swarmDataExt) { + return true; + } else { + return false; + } + }-*/; + + public native String getSwarmData() /*-{ + return JSON.stringify($wnd.swarmData); + }-*/; + + public native void setSwarmData(JavaScriptObject swarmData) /*-{ + $wnd.swarmData = swarmData; + }-*/; + + private native void getJSNIWind(WindStreamletsCanvasOverlay wsc) /*-{ + $wnd.getWindfromSimulator = function(idx) { + return wsc.@com.sap.sailing.gwt.ui.simulator.WindStreamletsCanvasOverlay::getWind(I)(idx); + }; + }-*/; + + private native JavaScriptObject getWindInst(double x, double y) /*-{ + return {x:x, y:y}; + }-*/; + + public JavaScriptObject getWind(int idx){ + SimulatorWindDTO wind = this.windFieldDTO.getMatrix().get(idx); + double y = wind.trueWindSpeedInKnots*Math.cos(wind.trueWindBearingDeg*Math.PI/180.0); + double x = wind.trueWindSpeedInKnots*Math.sin(wind.trueWindBearingDeg*Math.PI/180.0); + return getWindInst(x,y); + } + + public native void setWindDataJSON(String jsonField) /*-{ + eval(jsonField); + }-*/; + + public WindStreamletsCanvasOverlay(SimulatorMap simulatorMap, int zIndex) { + this(simulatorMap, zIndex, null, null); + } + + public native void setMapInstance(Object mapInstance) /*-{ + $wnd.swarmMap = mapInstance; + }-*/; + + public native void setCanvasProjectionInstance(Object instance) /*-{ + $wnd.swarmCanvasProjection = instance; + }-*/; + + public void startStreamlets() { + if (swarm == null) { + this.swarm = new Swarm(this,map); + } + this.swarm.start(40, windFieldDTO); + } + + public void setStreamletsStep(int step) { + this.swarm.getField().setStep(step); + } + + public void stopStreamlets() { + if (swarm != null) { + this.swarm.stop(); + } + } + + public void extendWindDataJSON() { + + List data = this.windFieldDTO.getMatrix(); + String jsonData = "data:["; + int p = 0; + int imax = this.windParams.getyRes() + 2*this.windParams.getBorderY(); + int jmax = this.windParams.getxRes() + 2*this.windParams.getBorderX(); + int steps = data.size() / (imax * jmax); + double maxWindSpeed = 0; + for(int s=0; s maxWindSpeed) { + maxWindSpeed = wind.trueWindSpeedInKnots; + } + double y = wind.trueWindSpeedInKnots*Math.cos(wind.trueWindBearingDeg*Math.PI/180.0); + double x = wind.trueWindSpeedInKnots*Math.sin(wind.trueWindBearingDeg*Math.PI/180.0); + jsonData += x + "," + y; + if (j<(jmax-1)) { + jsonData += ","; + } + } + if (i<(imax-1)) { + jsonData += "],"; + } else { + jsonData += "]"; + } + } + if (s<(steps-1)) { + jsonData += "],"; + } else { + jsonData += "]"; + } + } + this.windFieldDTO.windDataJSON += jsonData + "],maxLength:" + maxWindSpeed + "};"; + + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public void setWindField(final WindFieldDTO windFieldDTO) { + this.windFieldDTO = windFieldDTO; } + @Override + public boolean isVisible() { + return this.visible; + } + + @Override + public void setVisible(boolean isVisible) { + if (getCanvas() != null) { + if (isVisible) { + this.startStreamlets(); + if (timer.getTime().compareTo(this.windParams.getStartTime()) != 0) { + this.timeChanged(timer.getTime(), null); + } + this.visible = isVisible; + } else { + this.stopStreamlets(); + this.visible = isVisible; + } + } + } + @Override public void addToMap() { - super.addToMap(); - if (timer != null) { timer.addTimeListener(this); } @@ -77,190 +245,51 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti @Override public void removeFromMap() { - super.removeFromMap(); - if (timer != null) { timer.removeTimeListener(this); } + this.setVisible(false); + } + + @Override + protected void drawCenterChanged() { } - public void setWindField(final WindFieldDTO windField) { - this.windFieldDTO = windField; - - timePointWindDTOMap.clear(); - if (windField != null) { - for(final SimulatorWindDTO w : windField.getMatrix()) { - if (!timePointWindDTOMap.containsKey(w.timepoint)) { - timePointWindDTOMap.put(w.timepoint, new LinkedList()); - } - timePointWindDTOMap.get(w.timepoint).add(w); - } - } - } - - public void setArrowColor(final String arrowColor, final String arrowHeadColor) { - this.arrowColor = arrowColor; - this.arrowHeadColor = arrowHeadColor; - } - @Override protected void draw() { super.draw(); - if (mapProjection != null && windFieldDTO != null) { - clear(); - drawWindField(); + if (mapProjection != null) { + if ((nParticles > 0)&&(swarm == null)) { + this.swarm = new Swarm(this,map); + this.swarm.start(40, null); + } + if (windFieldDTO != null) { + // drawing is done by external JavaScript for Streamlets + } } } - + + public Swarm getSwarm() { + return this.swarm; + } + private void clear() { - canvas.getContext2d().clearRect(0.0 /*canvas.getAbsoluteLeft()*/, 0.0/*canvas.getAbsoluteTop()*/, - canvas.getCoordinateSpaceWidth(), canvas.getCoordinateSpaceHeight()); - windFieldPoints.clear(); - } - - protected void drawWindField() { - if (timer != null) { - timeChanged(timer.getTime(), null); - } else { - drawWindField(windFieldDTO.getMatrix()); - } - } - - protected void drawScaledArrow(final SimulatorWindDTO windDTO, final double angle, final int index, final double pxLength, final boolean drawHead) { - - final double aWidth = 1.0; - drawArrow(windDTO, angle, pxLength, aWidth, index, drawHead); - - } - - protected void drawWindField(final List windDTOList) { - final boolean drawHead = false; - clear(); - final Context2d context2d = canvas.getContext2d(); - context2d.setGlobalAlpha(0.4); - - if (windDTOList != null && windDTOList.size() > 0) { - Iterator windDTOIter = windDTOList.iterator(); - final SimulatorWindDTO w0 = windDTOIter.next(); - final SimulatorWindDTO w1 = windDTOIter.next(); - final LatLng pg0 = LatLng.newInstance(w0.position.latDeg, w0.position.lngDeg); - final LatLng pg1 = LatLng.newInstance(w1.position.latDeg, w1.position.lngDeg); - final Point px0 = mapProjection.fromLatLngToDivPixel(pg0); - final Point px1 = mapProjection.fromLatLngToDivPixel(pg1); - final double dx = px0.getX()-px1.getX(); - final double dy = px0.getY()-px1.getY(); - final double pxLength = Math.sqrt( dx*dx + dy*dy ); - logger.fine("pxLength = "+pxLength); - - windDTOIter = windDTOList.iterator(); - int index = 0; - while (windDTOIter.hasNext()) { - SimulatorWindDTO windDTO = windDTOIter.next(); - //System.out.println("wind angle: "+index+", "+windDTO.trueWindBearingDeg); - if (((index % xRes) > 0)&&((index % xRes) < (xRes-1))) { - DegreeBearingImpl dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg); - drawScaledArrow(windDTO, dbi.getRadians(), index, pxLength, drawHead); - } - index++; - } - final String title = "Wind Field at " + windDTOList.size() + " points."; - getCanvas().setTitle(title); - } - } - - protected void drawArrow(final SimulatorWindDTO windDTO, final double angle, final double length, final double weight, final int index, final boolean drawHead) { - final String msg = "Wind @ P" + index + ": time : " + windDTO.timepoint + " speed: " + windDTO.trueWindSpeedInKnots + "knots " - + windDTO.trueWindBearingDeg; - logger.fine(msg); - - final Context2d context2d = canvas.getContext2d(); - context2d.setGlobalAlpha(0.2); - - final PositionDTO position = windDTO.position; - - final LatLng positionLatLng = LatLng.newInstance(position.latDeg, position.lngDeg); - final Point canvasPositionInPx = mapProjection.fromLatLngToDivPixel(positionLatLng); - - final double x = canvasPositionInPx.getX() - getWidgetPosLeft(); - final double y = canvasPositionInPx.getY() - getWidgetPosTop(); - - windFieldPoints.put(new ToolTip(x, y), windDTO); - - final double dx = length * Math.sin(angle); - final double dy = -length * Math.cos(angle); - - final double x1 = x + dx / 2; - final double y1 = y + dy / 2; - - drawLine(x - dx / 2, y - dy / 2, x1, y1, weight, arrowColor); - - final double theta = Math.atan2(-dy, dx); - - final double hLength = Math.max(6.,6.+(10./(60.-10.))*Math.max(length-6.,0)); - logger.finer("headlength: "+hLength+", arrowlength: "+length); - - if (drawHead) { - drawHead(x1, y1, theta, hLength, weight); - } - //String text = "P" + index;// + NumberFormat.getFormat("0.00").format(windDTO.trueWindBearingDeg) + "�"; - //drawPointWithText(x, y, text); - //drawPoint(x, y); - - context2d.setGlobalAlpha(0.4); - - } - - protected void drawHead(final double x, final double y, final double theta, final double headLength, final double weight) { - - double t = theta + (Math.PI / 4); - if (t > Math.PI) { - t -= 2 * Math.PI; - } - double t2 = theta - (Math.PI / 4); - if (t2 <= (-Math.PI)) { - t2 += 2 * Math.PI; - } - - final double x1 = (x - Math.cos(t) * headLength); - final double y1 = (y + Math.sin(t) * headLength); - final double x1o = (x + Math.cos(t) * weight/2); - final double y1o = (y - Math.sin(t) * weight/2); - final double x2 = (x - Math.cos(t2) * headLength); - final double y2 = (y + Math.sin(t2) * headLength); - final double x2o = (x + Math.cos(t2) * weight/2); - final double y2o = (y - Math.sin(t2) * weight/2); - drawLine(x1o, y1o, x1, y1, weight, arrowHeadColor); - drawLine(x2o, y2o, x2, y2, weight, arrowHeadColor); - + this.stopStreamlets(); } @Override - public void timeChanged(final Date newTime, Date oldTime) { - - List windDTOToDraw = new ArrayList(); - - final SortedMap> headMap = (timePointWindDTOMap.headMap(newTime.getTime()+1)); - - if (!headMap.isEmpty()) { - windDTOToDraw = headMap.get(headMap.lastKey()); - } - logger.info("In WindFieldCanvasOverlay.drawWindField drawing " + windDTOToDraw.size() + " points" + " @ " - + newTime); - - drawWindField(windDTOToDraw); - + public void timeChanged(final Date newDate, Date oldDate) { + int step = (int)((newDate.getTime() - this.windParams.getStartTime().getTime()) / this.windParams.getTimeStep().getTime()); + this.setStreamletsStep(step); } @Override public boolean shallStop() { - if (!this.isVisible() || timePointWindDTOMap == null || timer == null || timePointWindDTOMap.isEmpty()) { - return true; - } - if (timePointWindDTOMap.lastKey() < timer.getTime().getTime()) { - return true; - } else { - return false; - } + if (timer.getTime().getTime() >= this.endDate.getTime()) { + return true; + } else { + return false; + } } public void setTimer(final Timer timer) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/FullCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/FullCanvasOverlay.java index b65992ce7df..e1ff06a09ef 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/FullCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/FullCanvasOverlay.java @@ -50,7 +50,7 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi @Override public void onEvent(CenterChangeMapEvent event) { // TODO Auto-generated method stub - draw(); + drawCenterChanged(); }; }); @@ -59,7 +59,7 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi /** * Set the canvas to be the size of the map and set it to the top left corner of the map */ - protected void setCanvasSettings() { + public void setCanvasSettings() { int canvasWidth = getMap().getDiv().getClientWidth(); int canvasHeight = getMap().getDiv().getClientHeight(); @@ -86,6 +86,10 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi }); } + protected void drawCenterChanged() { + draw(); + } + @Override protected void draw() { if (mapProjection != null) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Index.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Index.java new file mode 100644 index 00000000000..59b47daac3b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Index.java @@ -0,0 +1,12 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +public class Index { + + public long x; + public long y; + + public Index(long x, long y) { + this.x = x; + this.y = y; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Mercator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Mercator.java new file mode 100644 index 00000000000..4269ec0663b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Mercator.java @@ -0,0 +1,89 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +import com.google.gwt.canvas.dom.client.Context2d; +import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.base.Point; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay; + +public class Mercator { + + private FullCanvasOverlay canvas; + private MapWidget map; + + private double alpha; + private double beta; + private double gamma; + private double delta; + + public Mercator(FullCanvasOverlay canvas, MapWidget map) { + this.canvas = canvas; + this.alpha = 0.0; + this.beta = 0.0; + this.gamma = 0.0; + this.delta = 0.0; + this.map = map; + this.calibrate(); + } + + public void calibrate() { + + Vector pointSW; + Vector pointNE; + + int canvasHeight = canvas.getCanvas().getOffsetHeight(); + + Position mapSW = new DegreePosition(this.map.getBounds().getSouthWest().getLatitude(), this.map.getBounds().getSouthWest().getLongitude()); + Position mapNE = new DegreePosition(this.map.getBounds().getNorthEast().getLatitude(), this.map.getBounds().getNorthEast().getLongitude()); + + pointSW = this.sphere2plane(mapSW); + pointNE = this.sphere2plane(mapNE); + + if (pointNE.x < pointSW.x) { + pointSW.x -= 2*Math.PI; + } + + this.alpha = canvasHeight / (pointNE.y - pointSW.y); + this.beta = pointSW.x; + + this.gamma = - this.alpha; + this.delta = pointNE.y; + } + + public Vector sphere2plane(Position p) { + Vector result = new Vector(); + result.x = p.getLngDeg()*Math.PI/180.0; + double latsin = Math.sin(p.getLatDeg()*Math.PI/180.0); + result.y = 0.5*Math.log((1.0+latsin)/(1.0-latsin)); + return result; + } + + public LatLng plane2sphere(Point px) { + double lng = px.getX()*180.0/Math.PI; + double lat = Math.atan(Math.sinh(px.getY()))*180.0/Math.PI; + return LatLng.newInstance(lat, lng); + } + + public Vector latlng2pixel(Position p) { + Vector proj = this.sphere2plane(p); + proj.x = this.alpha * (proj.x - this.beta); + proj.y = this.gamma * (proj.y - this.delta); + return proj; + } + + public LatLng pixel2latlng(Point px) { + double p = px.getX() / this.alpha + this.beta; + double q = px.getY() / this.gamma + this.delta; + return this.plane2sphere(Point.newInstance(p, q)); + } + + public void clearCanvas() { + double w = this.canvas.getCanvas().getOffsetWidth(); + double h = this.canvas.getCanvas().getOffsetHeight(); + Context2d g = this.canvas.getCanvas().getContext2d(); + g.clearRect(0, 0, w, h); + }; + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Neighbors.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Neighbors.java new file mode 100644 index 00000000000..22bf7c67037 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Neighbors.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +public class Neighbors { + + public int xBot; + public int xTop; + public double xMod; + public int yBot; + public int yTop; + public double yMod; + + public Neighbors(int xTop, int yTop, int xBot, int yBot, double xMod, double yMod) { + this.xTop = xTop; + this.yTop = yTop; + this.xBot = xBot; + this.yBot = yBot; + this.xMod = xMod; + this.yMod = yMod; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Particle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Particle.java new file mode 100644 index 00000000000..59d0c9cb183 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Particle.java @@ -0,0 +1,14 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +import com.sap.sailing.domain.common.Position; + + +public class Particle { + + public Position pos; + public Vector pxOld; + public int alpha; + public int age; + public Vector v; + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/RectField.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/RectField.java new file mode 100644 index 00000000000..8c21e3980b6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/RectField.java @@ -0,0 +1,187 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONParser; +import com.google.gwt.maps.client.base.LatLng; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.DegreePosition; + +public class RectField implements VectorField { + + private Vector[][] field; + + public double x0; + public double x1; + public double y0; + public double y1; + + //public double visX0; + public double visX1; + public double visY0; + public double visY1; + + public Position visSW; + public Position visNE; + + private int w; + private int h; + + private double maxLength; + public double particleFactor; + + public RectField(Vector[][] field, double x0, double y0, double x1, double y1) { + this.x0 = x0; + this.x1 = x1; + this.y0 = y0; + this.y1 = y1; + + this.visSW = new DegreePosition(0.0, 0.0); + this.visNE = new DegreePosition(0.0, 0.0); + + this.field = field; + this.w = field.length; + this.h = field[0].length; + this.maxLength = 0; + this.particleFactor = 4.5; + + double mx = 0; + double my = 0; + for (int i = 0; i < this.w; i++) { + for (int j = 0; j < this.h; j++) { + if (field[i][j].length() > this.maxLength) { + mx = i; + my = j; + } + this.maxLength = Math.max(this.maxLength, field[i][j].length()); + } + } + mx = (mx / this.w) * (x1 - x0) + x0; + my = (my / this.h) * (y1 - y0) + y0; + } + + public static RectField read(String jsonData, boolean correctForSphere) { + JSONObject data = JSONParser.parseLenient(jsonData).isObject(); + int w = (int)data.get("gridWidth").isNumber().doubleValue(); + int h = (int)data.get("gridHeight").isNumber().doubleValue(); + Vector[][] field = new Vector[w][h]; + + int i = 0; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + Vector v = new Vector(); + v.x = data.get("field").isArray().get(i++).isNumber().doubleValue(); + v.y = data.get("field").isArray().get(i++).isNumber().doubleValue(); + if (correctForSphere) { + double uy = y / (h - 1); + double lat = data.get("y0").isNumber().doubleValue() * (1 - uy) + data.get("y1").isNumber().doubleValue() * uy; + double m = Math.PI * lat / 180; + double length = v.length(); + v.x = v.x / Math.cos(m); + v.setLength(length); + } + field[x][y] = v; + } + } + RectField result = new RectField(field, data.get("x0").isNumber().doubleValue(), data.get("y0").isNumber().doubleValue(), data.get("x1").isNumber().doubleValue(), data.get("y1").isNumber().doubleValue()); + return result; + } + + public void setStep(int step) { + } + + public void nextStep() { + } + + public void prevStep() { + } + + public Position getRandomPosition() { + double rndY = Math.random(); + double rndX = Math.random(); + double latDeg = rndY * this.visSW.getLatDeg() + (1 - rndY) * this.visNE.getLatDeg(); + double lngDeg = rndX * this.visSW.getLngDeg() + (1 - rndX) * this.visNE.getLngDeg(); + return new DegreePosition(latDeg, lngDeg); + } + + public boolean inBounds(Position p) { + return p.getLngDeg() >= this.x0 && p.getLngDeg() < this.x1 && p.getLatDeg() >= this.y0 && p.getLatDeg() < this.y1; + } + + public Vector interpolate(Position p) { + int na = (int)Math.floor(p.getLngDeg()); + int nb = (int)Math.floor(p.getLatDeg()); + int ma = (int)Math.ceil(p.getLngDeg()); + int mb = (int)Math.ceil(p.getLatDeg()); + double fa = p.getLngDeg() - na; + double fb = p.getLatDeg() - nb; + + Vector result = new Vector(); + result.x = this.field[na][nb].x * (1 - fa) * (1 - fb) + this.field[ma][nb].x * fa * (1 - fb) + this.field[na][mb].x * (1 - fa) * fb + this.field[ma][mb].x * fa * fb; + result.y = this.field[na][nb].y * (1 - fa) * (1 - fb) + this.field[ma][nb].y * fa * (1 - fb) + this.field[na][mb].y * (1 - fa) * fb + this.field[ma][mb].y * fa * fb; + + return result; + } + + + public Vector getVector(Position p) { + double lngDeg = (this.w - 1 - 1e-6) * (p.getLngDeg() - this.x0) / (this.x1 - this.x0); + double latDeg = (this.h - 1 - 1e-6) * (p.getLatDeg() - this.y0) / (this.y1 - this.y0); + if ((lngDeg < 0)||(lngDeg > (this.w-1))||(latDeg < 0)||(latDeg > (this.h-1))) { + return null; + } else { + Position q = new DegreePosition(latDeg, lngDeg); + return this.interpolate(q); + } + } + + public LatLng getCenter() { + return LatLng.newInstance((y0+y1)/2.0, (x0+x1)/2.0); + } + + public double getMaxLength() { + return maxLength; + } + + public double motionScale(int zoomLevel) { + return 0.9 * Math.pow(1.7, Math.min(1.0, 6.0 - zoomLevel)); + } + + public double particleWeight(Position p, Vector v) { + return 1.0 - v.length() / this.maxLength; + } + + public String[] getColors() { + String[] colors = new String[256]; + double alpha = 1.0; + int greyValue = 255; + for (int i = 0; i < 256; i++) { + colors[i] = "rgba(" + (greyValue) + "," + (greyValue) + "," + (greyValue) + "," + (alpha*i/255.0) + ")"; + //this.colors[i] = 'hsla(' + 360*(0.55+0.9*(0.5-i/255)) + ',' + (100) + '% ,' + (50) + '%,' + (i/255) + ')'; + } + return colors; + } + + public double lineWidth(int alpha) { + return 1.0; + } + + public Position getFieldNE() { + return new DegreePosition(Math.max(this.y0, this.y1), Math.max(this.x0, this.x1)); + } + + public Position getFieldSW() { + return new DegreePosition(Math.min(this.y0, this.y1), Math.min(this.x0, this.x1)); + } + + public void setVisNE(Position visNE) { + this.visNE = visNE; + } + + public void setVisSW(Position visSW) { + this.visSW = visSW; + } + + public double getParticleFactor() { + return this.particleFactor; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorField.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorField.java new file mode 100644 index 00000000000..1c1366ba1b6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorField.java @@ -0,0 +1,295 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +import java.util.List; + +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONParser; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; +import com.sap.sailing.gwt.ui.shared.WindFieldDTO; +import com.sap.sailing.gwt.ui.shared.WindFieldGenParamsDTO; + +public class SimulatorField implements VectorField { + + private boolean swarmDebug = false; + + private Position rcStart; + private Position rcEnd; + + private int resY; + private int resX; + + private int borderY; + private int borderX; + + private double bdXi; + private double bdPhi; + + private Position bdA; + private Position bdB; + private Position bdC; + + private double xScale; + + private double x0; + private double x1; + private double y0; + private double y1; + + public Position visSW; + public Position visNE; + + private double maxLength; + private double particleFactor; + + private double lngScale; + + private Position nvY; + private Position nvX; + private Position gvX; + + private double[][][] data; + private int step; + + public SimulatorField(WindFieldDTO windData, WindFieldGenParamsDTO windParams) { + + this.step = 0; + + String parseString = windData.windDataJSON.substring(18, windData.windDataJSON.length()-1) + "}"; + JSONObject baseData = JSONParser.parseLenient(parseString).isObject(); + + this.rcStart = new DegreePosition(baseData.get("rcStart").isObject().get("lat").isNumber().doubleValue(), baseData.get("rcStart").isObject().get("lng").isNumber().doubleValue()); + this.rcEnd = new DegreePosition(baseData.get("rcEnd").isObject().get("lat").isNumber().doubleValue(), baseData.get("rcEnd").isObject().get("lng").isNumber().doubleValue()); + + this.resY = (int)baseData.get("resY").isNumber().doubleValue(); + this.resX = (int)baseData.get("resX").isNumber().doubleValue(); + + this.borderY = (int)baseData.get("borderY").isNumber().doubleValue(); + this.borderX = (int)baseData.get("borderX").isNumber().doubleValue(); + + this.bdXi = (this.borderY + 0.5) / (this.resY - 1); + this.bdPhi = 1.0 + 2*this.bdXi; + this.bdA = new DegreePosition(this.rcEnd.getLatDeg()+(this.rcEnd.getLatDeg()-this.rcStart.getLatDeg())*this.bdXi, this.rcEnd.getLngDeg()+(this.rcEnd.getLngDeg()-this.rcStart.getLngDeg())*this.bdXi); + this.bdB = new DegreePosition((this.rcStart.getLatDeg()-this.rcEnd.getLatDeg())*this.bdPhi, (this.rcStart.getLngDeg()-this.rcEnd.getLngDeg())*this.bdPhi); + + this.xScale = baseData.get("xScale").isNumber().doubleValue(); + + this.x0 = baseData.get("boundsSW").isObject().get("lng").isNumber().doubleValue(); + this.x1 = baseData.get("boundsNE").isObject().get("lng").isNumber().doubleValue(); + this.y0 = baseData.get("boundsSW").isObject().get("lat").isNumber().doubleValue(); + this.y1 = baseData.get("boundsNE").isObject().get("lat").isNumber().doubleValue(); + + this.visSW = new DegreePosition(0.0, 0.0); + this.visNE = new DegreePosition(0.0, 0.0); + + List gridData = windData.getMatrix(); + + int p = 0; + int imax = windParams.getyRes() + 2*windParams.getBorderY(); + int jmax = windParams.getxRes() + 2*windParams.getBorderX(); + int steps = gridData.size() / (imax * jmax); + + this.data = new double[steps][imax][2*jmax]; + + double maxWindSpeed = 0; + for(int s=0; s maxWindSpeed) { + maxWindSpeed = wind.trueWindSpeedInKnots; + } + + this.data[s][i][2*j+1] = wind.trueWindSpeedInKnots*Math.cos(wind.trueWindBearingDeg*Math.PI/180.0); + this.data[s][i][2*j] = wind.trueWindSpeedInKnots*Math.sin(wind.trueWindBearingDeg*Math.PI/180.0); + + } + } + } + this.maxLength = maxWindSpeed; + + this.particleFactor = 2.0; + + double latAvg = (this.rcEnd.getLatDeg() + this.rcStart.getLatDeg()) / 2.; + this.lngScale = Math.cos(latAvg * Math.PI / 180.0); + + double difLat = this.rcEnd.getLatDeg() - this.rcStart.getLatDeg(); + double difLng = (this.rcEnd.getLngDeg() - this.rcStart.getLngDeg()) * this.lngScale; + double difLen = Math.sqrt(difLat*difLat + difLng*difLng); + this.nvY = new DegreePosition(difLat/difLen/difLen*(this.resY-1), difLng/difLen/difLen*(this.resY-1)); + + double nrmLat = -difLng/difLen; + double nrmLng = difLat/difLen; + this.nvX = new DegreePosition( nrmLat/this.xScale/difLen*(this.resX-1), nrmLng/this.xScale/difLen*(this.resX-1) ); + this.gvX = new DegreePosition( nrmLat*this.xScale*difLen, nrmLng/this.lngScale*this.xScale*difLen ); + this.bdC = new DegreePosition( this.gvX.getLatDeg()*(this.resX+2*this.borderX-1)/(this.resX-1), this.gvX.getLngDeg()*(this.resX+2*this.borderX-1)/(this.resX-1) ); + } + + public static native void console(String msg) /*-{ + console.log(msg); + }-*/; + + public Position getRandomPosition() { + double rndY = Math.random(); + double rndX = Math.random() - 0.5; + + double latDeg = this.bdA.getLatDeg() + rndY * this.bdB.getLatDeg() + rndX * this.bdC.getLatDeg(); + double lngDeg = this.bdA.getLngDeg() + rndY * this.bdB.getLngDeg() + rndX * this.bdC.getLngDeg(); + Position result = new DegreePosition(latDeg, lngDeg); + + if (swarmDebug&&(!this.inBounds(result))) { + console("random-position: out of bounds"); + } + + return result; + } + + + public boolean inBounds(Position p) { + Index idx = this.getIndex(p); + boolean inBool = (idx.x >= 0) && (idx.x < (this.resX+2*this.borderX)) && (idx.y >= 0) && (idx.y < (this.resY+2*this.borderY)); + return inBool; + } + + + public Vector interpolate(Position p) { + + Neighbors idx = this.getNeighbors(p); + + if (swarmDebug&&((idx.xTop >= (this.resX+2*this.borderX))||(idx.yTop >= (this.resY+2*this.borderY)))) { + console("interpolate: out of range: " + idx.xTop + " " + idx.yTop); + } + + //System.out.println("neighbors:"+idx.xBot+","+idx.xTop+","+idx.yBot+","+idx.yTop); + + double avgX = this.data[this.step][idx.yBot][2*idx.xBot] * (1 - idx.yMod) * (1 - idx.xMod) + this.data[this.step][idx.yTop][2*idx.xBot] * idx.yMod * (1 - idx.xMod) + + this.data[this.step][idx.yBot][2*idx.xTop] * (1 - idx.yMod) * idx.xMod + this.data[this.step][idx.yTop][2*idx.xTop] * idx.yMod * idx.xMod; + double avgY = this.data[this.step][idx.yBot][2*idx.xBot+1] * (1 - idx.yMod) * (1 - idx.xMod) + this.data[this.step][idx.yTop][2*idx.xBot+1] * idx.yMod * (1 - idx.xMod) + + this.data[this.step][idx.yBot][2*idx.xTop+1] * (1 - idx.yMod) * idx.xMod + this.data[this.step][idx.yTop][2*idx.xTop+1] * idx.yMod * idx.xMod; + + return new Vector(avgX / this.lngScale, avgY); + } + + public void setStep(int step) { + if (step < 0) { + this.step = 0; + } else if (step >= this.data.length) { + this.step = this.data.length-1; + } else { + this.step = step; + } + } + + public void nextStep() { + if (this.step < (this.data.length-1)) { + this.step++; + } + } + + public void prevStep() { + if (this.step > 0) { + this.step--; + } + } + + public Vector getVector(Position p) { + return this.interpolate(p); + } + + public Index getIndex(Position p) { + + // calculate grid indexes + Position posR = new DegreePosition( p.getLatDeg() - this.rcStart.getLatDeg(), (p.getLngDeg() - this.rcStart.getLngDeg()) * this.lngScale ); + + // closest grid point + long yIdx = Math.round( posR.getLatDeg() * this.nvY.getLatDeg() + posR.getLngDeg() * this.nvY.getLngDeg() ) + this.borderY; + long xIdx = Math.round( posR.getLatDeg() * this.nvX.getLatDeg() + posR.getLngDeg() * this.nvX.getLngDeg() + (this.resX - 1) / 2. ) + this.borderX; + + return new Index( xIdx, yIdx ); + } + + public Neighbors getNeighbors(Position p) { + + // calculate grid indexes + Position posR = new DegreePosition( p.getLatDeg() - this.rcStart.getLatDeg(), (p.getLngDeg() - this.rcStart.getLngDeg()) * this.lngScale ); + + // surrounding grid points + double yFlt = posR.getLatDeg() * this.nvY.getLatDeg() + posR.getLngDeg() * this.nvY.getLngDeg() + this.borderY; + double xFlt = posR.getLatDeg() * this.nvX.getLatDeg() + posR.getLngDeg() * this.nvX.getLngDeg() + (this.resX - 1) / 2. + this.borderX; + double yBot = Math.floor( yFlt ); + double xBot = Math.floor( xFlt ); + double yTop = Math.ceil( yFlt ); + double xTop = Math.ceil( xFlt ); + double yMod = yFlt - yBot; + double xMod = xFlt - xBot; + + if (xBot < 0) { + xBot = 0; + } + + if (yBot < 0) { + yBot = 0; + } + + if (xTop >= (this.resX+2*this.borderX)) { + xTop = this.resX+2*this.borderX-1; + } + + if (yTop >= (this.resY+2*this.borderY)) { + yTop = this.resY+2*this.borderY-1; + } + + //System.out.println("neighbors:"+xFlt+","+xBot+","+xTop+","+yFlt+","+yBot+","+yTop); + + return new Neighbors((int)xTop, (int)yTop, (int)xBot, (int)yBot, xMod, yMod ); + } + + public double getMaxLength() { + return maxLength; + } + + public double motionScale(int zoomLevel) { + return 0.08 * Math.pow(1.6, Math.min(1.0, 6.0 - zoomLevel)); + } + + public double particleWeight(Position p, Vector v) { + return v.length() / this.maxLength + 0.1; + } + + public String[] getColors() { + String[] colors = new String[256]; + double alpha = 0.7; + int greyValue = 255; + for (int i = 0; i < 256; i++) { + colors[i] = "rgba(" + (greyValue) + "," + (greyValue) + "," + (greyValue) + "," + (alpha*i/255.0) + ")"; + //this.colors[i] = 'hsla(' + 360*(0.55+0.9*(0.5-i/255)) + ',' + (100) + '% ,' + (50) + '%,' + (i/255) + ')'; + } + return colors; + } + + public double lineWidth(int alpha) { + return 1.0; + } + + public Position getFieldNE() { + return new DegreePosition(Math.max(this.y0, this.y1), Math.max(this.x0, this.x1)); + } + + public Position getFieldSW() { + return new DegreePosition(Math.min(this.y0, this.y1), Math.min(this.x0, this.x1)); + } + + public void setVisNE(Position visNE) { + this.visNE = visNE; + } + + public void setVisSW(Position visSW) { + this.visSW = visSW; + } + + public double getParticleFactor() { + return this.particleFactor; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorJSBundle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorJSBundle.java new file mode 100644 index 00000000000..ba6a52caa6f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorJSBundle.java @@ -0,0 +1,14 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.TextResource; + +public interface SimulatorJSBundle extends ClientBundle { + + //@Source("wind-streamlets.js") + //TextResource windStreamletsJS(); + + @Source("com/sap/sailing/gwt/ui/simulator/streamlets/wind-data.js") + TextResource windStreamletsDataJS(); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java new file mode 100644 index 00000000000..5bd391bcb49 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java @@ -0,0 +1,292 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +import java.util.Date; + +import com.google.gwt.canvas.client.Canvas; +import com.google.gwt.canvas.dom.client.Context2d; +import com.google.gwt.core.client.GWT; +import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.user.client.Timer; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.gwt.ui.shared.WindFieldDTO; +import com.sap.sailing.gwt.ui.simulator.WindStreamletsCanvasOverlay; +import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay; + +public class Swarm { + + protected FullCanvasOverlay fullcanvas; + protected Canvas canvas; + protected MapWidget map; + + private Timer loopTimer; + private String[] color; + private Mercator projection; + private VectorField field; + private int nParticles; + private Particle[] particles; + private boolean swarmOffScreen = false; + private int swarmPause = 0; + private boolean swarmContinue = true; + private Position boundsNE; + private Position boundsSW; + + public Swarm(FullCanvasOverlay fullcanvas, MapWidget map) { + this.fullcanvas = fullcanvas; + this.canvas = fullcanvas.getCanvas(); + this.map = map; + } + + public void start(int animationDuration, WindFieldDTO windField) { + + projection = new Mercator(fullcanvas, map); + + if (windField == null) { + + SimulatorJSBundle bundle = GWT.create(SimulatorJSBundle.class); + String jsonStr = bundle.windStreamletsDataJS().getText(); + field = RectField.read(jsonStr.substring(19, jsonStr.length()-1), false); + + map.setZoom(5); + map.panTo(((RectField)field).getCenter()); + + projection.calibrate(); + + } else { + + field = new SimulatorField(((WindStreamletsCanvasOverlay)fullcanvas).getWindFieldDTO(), ((WindStreamletsCanvasOverlay)fullcanvas).getWindParams()); + fullcanvas.setCanvasSettings(); + projection.calibrate(); + + } + + this.updateBounds(); + + color = field.getColors(); + + Context2d ctxt = canvas.getContext2d(); + ctxt.setFillStyle("red"); + + /*Vector p = this.projection.latlng2pixel(this.field.getFieldNE()); + ctxt.fillRect(p.x,p.y,5.0,5.0); + + p = this.projection.latlng2pixel(this.field.getFieldSW()); + ctxt.fillRect(p.x,p.y,5.0,5.0);*/ + + particles = this.createParticles(); + + this.swarmContinue = true; + startLoop(animationDuration); + } + + public void stop() { + this.swarmContinue = false; + } + + public Particle createParticle() { + Particle particle = new Particle(); + boolean done = false; + while (!done) { + particle.pos = field.getRandomPosition(); + Vector v = field.getVector(particle.pos); + double weight = field.particleWeight(particle.pos, v); + if (weight >= Math.random()) { + if (v.length() == 0) { + particle.age = 0; + } else { + particle.age = 1 + (int)Math.round(Math.random()*40); + } + particle.pxOld = projection.latlng2pixel(particle.pos); + particle.alpha = 0; + particle.v = v; + done = true; + } + } + return particle; + } + + public Particle[] createParticles() { + + Particle[] newParticles = new Particle[nParticles]; + for(int idx=0; idx 0)||(visibleSW.y < 0)||(visibleNE.x < 0)||(visibleSW.x > 0); + + if (swarmOffScreen) { + + this.boundsNE = fieldNE; + this.boundsSW = fieldSW; + + } else { + + if ((!useBoundsNorth)&&(!useBoundsEast)) { + this.boundsNE = mapNE; + } else if (!useBoundsNorth) { + this.boundsNE = new DegreePosition(mapNE.getLatDeg(), fieldNE.getLngDeg()); + } else if (!useBoundsEast) { + this.boundsNE = new DegreePosition(fieldNE.getLatDeg(), mapNE.getLngDeg()); + } else { + this.boundsNE = fieldNE; + } + + if ((!useBoundsSouth)&&(!useBoundsWest)) { + this.boundsSW = mapSW; + } else if (!useBoundsSouth) { + this.boundsSW = new DegreePosition(mapSW.getLatDeg(), fieldSW.getLngDeg()); + } else if (!useBoundsWest) { + this.boundsSW = new DegreePosition(fieldSW.getLatDeg(), mapSW.getLngDeg()); + } else { + this.boundsSW = fieldSW; + } + + } + + this.field.setVisSW(this.boundsSW); + this.field.setVisNE(this.boundsNE); + + Vector boundsSWpx = this.projection.latlng2pixel(this.boundsSW); + Vector boundsNEpx = this.projection.latlng2pixel(this.boundsNE); + + double boundsWidthpx = Math.abs(boundsNEpx.x - boundsSWpx.x); + double boundsHeightpx = Math.abs(boundsSWpx.y - boundsNEpx.y); + + this.nParticles = (int)Math.round(Math.sqrt(boundsWidthpx * boundsHeightpx) * this.field.getParticleFactor()); + //console("#particles: "+this.nParticles + " at " + (boundsWidthpx) +"x" + (boundsHeightpx) + "px (" + (boundsWidthpx * boundsHeightpx) + " pixels)"); + }; + + public Vector isVisible(Position pos) { + + // test for visibility of swarm + Vector proj = this.projection.latlng2pixel(pos); + + Vector result = new Vector(); + result.x = (proj.x < 0 ? -1 : 0) + (proj.x > canvas.getOffsetWidth() ? 1 : 0); + result.y = (proj.y < 0 ? -1 : 0) + (proj.y > canvas.getOffsetHeight() ? 1 : 0); + + return result; + } + + public void startLoop(final int millis) { + + // Create animation-loop based on timer timeout + loopTimer = new com.google.gwt.user.client.Timer() { + public void run() { + Date time0 = new Date(); + if (swarmPause > 1) { + swarmPause--; + } else if (swarmPause == 1) { + fullcanvas.setCanvasSettings(); + projection.calibrate(); + updateBounds(); + particles = createParticles(); + swarmPause = 0; + } + //console("swarmOffScreen:"+swarmOffScreen); + if ((!swarmOffScreen)&&(swarmPause == 0)) { + execute(); + } + Date time1 = new Date(); + //console("delta:"+(time1.getTime()-time0.getTime())+"/"+millis); + if (swarmContinue) { + loopTimer.schedule((int)Math.max(10, millis - (time1.getTime()-time0.getTime()))); + } else { + projection.clearCanvas(); + } + } + }; + loopTimer.schedule(millis); + } + + public void drawSwarm() { + + Context2d ctxt = canvas.getContext2d(); + + ctxt.setGlobalAlpha(0.08); + ctxt.setGlobalCompositeOperation("destination-out"); + ctxt.setFillStyle("black"); + ctxt.fillRect(0, 0, canvas.getOffsetWidth(), canvas.getOffsetHeight()); + ctxt.setGlobalAlpha(1.0); + ctxt.setGlobalCompositeOperation("source-over"); + ctxt.setFillStyle("white"); + for(int idx=0; idx 0) && (particle.v != null)) { + double latDeg = particle.pos.getLatDeg() + speed*particle.v.y; + double lngDeg = particle.pos.getLngDeg() + speed*particle.v.x; + particle.pos = new DegreePosition(latDeg, lngDeg); + double s = particle.v.length() / field.getMaxLength(); + particle.alpha = (int)Math.min(255, 90 + Math.round(350 * s)); + particle.age--; + if ((particle.age > 0) && (this.field.inBounds(particle.pos))) { + particle.v = field.getVector(particle.pos); + } else { + particle.v = null; + } + } else { + particles[idx] = this.createParticle(); + } + } + + drawSwarm(); + + return true; + } + + public VectorField getField() { + return field; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Vector.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Vector.java new file mode 100644 index 00000000000..6eb1de26712 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Vector.java @@ -0,0 +1,29 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +public class Vector { + + public double x; + public double y; + + public Vector() { + } + + public Vector(double x, double y) { + this.x = x; + this.y = y; + } + + public double length() { + return Math.sqrt(x*x + y*y); + } + + public Vector setLength(double length) { + double current = this.length(); + if (current > 0) { + double scale = length / current; + this.x *= scale; + this.y *= scale; + } + return this; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/VectorField.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/VectorField.java new file mode 100644 index 00000000000..dbd0c28c896 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/VectorField.java @@ -0,0 +1,42 @@ +package com.sap.sailing.gwt.ui.simulator.streamlets; + +import com.sap.sailing.domain.common.Position; + + +public interface VectorField { + + public Position getRandomPosition(); + + public boolean inBounds(Position p); + + public Vector interpolate(Position p); + + public Vector getVector(Position p); + + public double getMaxLength(); + + public double motionScale(int zoomLevel); + + public double particleWeight(Position p, Vector v); + + public String[] getColors(); + + public double lineWidth(int alpha); + + public Position getFieldNE(); + + public Position getFieldSW(); + + public void setVisNE(Position visNE); + + public void setVisSW(Position visSW); + + public double getParticleFactor(); + + public void setStep(int step); + + public void nextStep(); + + public void prevStep(); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/wind-data.js b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/wind-data.js new file mode 100644 index 00000000000..f975506d53c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/wind-data.js @@ -0,0 +1,118748 @@ +var swarmDataExt = { +timestamp: "1:55 pm on March 22, 2014", +x0: -130.103438, +y0: 20.191999, +x1: -60.885558, +y1: 52.807669, +gridWidth: 501.0, +gridHeight: 237.0, +field: [ +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.13,-2.62, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.04,-3.54, +2.3,-3.35, +2.63,-3.12, +3.07,-2.68, +0.78,-0.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.73,-4.75, +1.54,-4.23, +1.98,-3.57, +2.13,-3.48, +2.7,-3.21, +3.22,-3.26, +0.55,-0.46, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.29,-4.5, +1.73,-4.75, +1.73,-4.75, +1.73,-4.75, +1.73,-4.75, +1.97,-4.46, +2.46,-4.24, +3.15,-3.95, +3.3,-3.82, +1.19,-1.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.33,-2.6, +3.71,-3.42, +3.02,-4.04, +2.5,-4.39, +1.92,-5.27, +1.85,-5.09, +1.75,-4.8, +1.73,-4.75, +2.02,-4.62, +2.53,-4.38, +3,-4.26, +3.46,-4.13, +0.92,-1.1, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +7.93,0.47, +6.59,-0.97, +5.38,-2.35, +4.32,-3.41, +3.55,-3.89, +3.14,-4.66, +2.49,-5.44, +2.06,-5.66, +2,-5.5, +1.9,-5.22, +2.21,-4.81, +2.84,-4.92, +3.39,-4.83, +3.96,-4.72, +0.55,-0.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +10.41,3.79, +9.77,3.56, +9.38,1.68, +8.26,0.52, +7.03,-0.7, +5.92,-2.11, +4.96,-3.4, +4.31,-4.18, +3.57,-4.83, +2.75,-5.34, +2.24,-5.59, +2.24,-5.58, +2.23,-5.59, +2.88,-5.28, +3.01,-5.22, +3.61,-5.25, +4.37,-5.21, +1.12,-1.34, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +10.27,5.93, +10.44,6.03, +11.19,4.3, +10.43,3.8, +9.85,3.38, +9.55,1.68, +8.56,0.87, +7.59,-0.5, +6.6,-2.01, +5.52,-3.13, +4.57,-3.93, +3.79,-4.68, +3.01,-5.22, +3.07,-5.31, +3.06,-5.3, +3.07,-5.31, +3.07,-5.31, +3.35,-5.8, +4.12,-5.62, +4.75,-5.66, +0.66,-0.79, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +8.25,4.76, +8.59,4.96, +9.16,5.29, +9.62,5.55, +10.1,5.83, +10.64,4.25, +10.41,3.79, +10.18,3.7, +9.66,2.05, +8.97,1.11, +8.03,-0.52, +7.04,-1.8, +6.37,-2.84, +5.56,-3.89, +4.19,-4.45, +3.49,-5.66, +3.5,-6.06, +3.5,-6.06, +3.49,-6.04, +3.44,-5.96, +3.61,-6.08, +4.7,-5.89, +5.11,-6.08, +1.28,-1.53, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +7,2.55, +7.2,3.89, +7.77,4.48, +8.37,4.83, +8.61,4.97, +9.43,5.44, +9.6,5.54, +9.93,4.91, +10.41,3.79, +10.31,3.75, +9.65,2.81, +9.23,1.54, +8.5,0, +7.72,-1.53, +6.81,-2.61, +5.95,-3.67, +5.11,-4.72, +4.05,-5.69, +3.5,-6.06, +3.5,-6.06, +3.5,-6.06, +3.54,-6.13, +4.03,-6.5, +5.12,-6.11, +5.55,-6.62, +0.75,-0.89, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.05,0.15, +5.7,1.18, +6.18,2.21, +6.73,2.45, +6.93,3.37, +7.2,4.16, +7.77,4.48, +8.44,4.87, +8.8,5.08, +9.48,5.48, +9.64,5.45, +10.39,3.86, +10.41,3.79, +10.17,3.47, +9.6,1.69, +8.92,0.27, +8.05,-1.12, +7.46,-2.22, +6.4,-2.92, +5.52,-4.27, +4.55,-5.31, +4.03,-5.7, +3.74,-5.9, +3.64,-6.27, +3.86,-6.69, +4.88,-6.43, +5.55,-6.62, +5.75,-6.85, +2.19,-2.61, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.88,-2.88, +3.53,-2.01, +4.05,-0.99, +4.84,-0.29, +5.1,0.81, +5.69,1.87, +6.26,2.28, +6.51,3, +6.78,3.92, +7.41,4.28, +7.82,4.51, +8.54,4.93, +9.03,5.22, +9.57,5.53, +10.03,4.63, +10.41,3.79, +10.35,3.74, +9.74,1.97, +9.23,1, +8.84,-0.34, +8.06,-1.78, +6.89,-2.91, +5.84,-3.81, +4.89,-4.98, +4.5,-5.36, +4.75,-5.77, +4.67,-6.43, +4.48,-6.6, +5.57,-6.67, +5.84,-6.96, +6.27,-7.47, +0.91,-1.08, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.22,-6.89, +1.74,-5.91, +2.24,-4.82, +2.96,-3.98, +3.23,-2.6, +3.7,-1.69, +4.17,-0.89, +5,0.11, +5.29,1.11, +5.69,1.98, +6.33,2.53, +6.44,3.67, +6.9,3.98, +7.67,4.43, +8.08,4.67, +8.65,4.99, +9.02,5.21, +9.55,5.51, +10.35,3.94, +10.41,3.79, +9.87,2.73, +9.65,1.44, +8.97,0.02, +8.62,-0.96, +7.58,-2.4, +6.22,-3.26, +5.48,-4.28, +5,-4.95, +5.15,-6.05, +5.14,-6.13, +5.48,-6.53, +5.75,-6.85, +6.03,-7.18, +6.41,-7.57, +1.82,-2.17, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-10.11, +-0,-9.38, +0.14,-8.38, +1.17,-7.54, +1.21,-6.69, +1.97,-5.6, +2.35,-4.64, +3.18,-3.74, +3.38,-2.41, +3.8,-1.46, +4.59,-0.45, +5.01,0.51, +5.4,1.28, +5.8,2.11, +6.23,3.15, +6.61,3.81, +7.2,4.16, +7.74,4.47, +8.56,4.94, +8.59,4.96, +9.09,5.25, +9.67,5.39, +10.2,4.25, +9.75,3.5, +9.68,2.09, +9.67,1.28, +8.83,-0.14, +7.97,-1.36, +6.92,-2.52, +5.81,-3.85, +5.57,-4.45, +6.06,-5.18, +6,-5.89, +5.75,-6.85, +5.98,-6.64, +6.71,-7.02, +7.39,-6.58, +0.84,-0.71, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.26,-12.83, +-1.98,-11.96, +-0.21,-11.36, +-0,-10.89, +-0,-9.87, +-0,-9.03, +0.33,-8.24, +1.24,-7.43, +1.55,-6.51, +2.25,-5.39, +2.74,-4.4, +3.38,-3.36, +3.43,-2.19, +3.93,-1.04, +4.78,-0.03, +5.04,0.79, +5.49,1.79, +5.91,2.59, +6.3,3.64, +6.87,3.97, +7.5,4.33, +8.1,4.68, +8.59,4.96, +8.96,5.18, +9.6,5.54, +9.6,5.54, +10.37,3.9, +9.71,3.11, +9.72,1.91, +9.44,0.49, +8.53,-0.38, +7.61,-1.76, +6.38,-2.98, +6.59,-4.01, +6.35,-4.84, +6.68,-5.61, +6.79,-6.02, +7.46,-6.26, +7.59,-6.37, +7.59,-6.37, +1.07,-0.93, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.6,-14.74, +-2.46,-13.97, +-2.42,-13.72, +-2.31,-13.09, +-2.22,-12.56, +-1.74,-11.83, +-0.51,-11.55, +-0,-10.89, +-0,-9.84, +-0,-9.04, +0.76,-8.07, +1.29,-7.33, +1.96,-6.19, +2.43,-5.19, +2.96,-4.06, +3.36,-3.08, +3.62,-1.84, +4.25,-0.58, +5,0.35, +5.01,1.08, +5.61,1.98, +6,3.28, +6.57,3.79, +7.19,4.15, +7.67,4.74, +8.57,4.98, +8.68,5.01, +9.57,5.53, +9.6,5.54, +9.93,4.84, +10.41,3.79, +10.16,2.97, +9.7,1.55, +9,0.4, +7.88,-1.13, +7.09,-2.44, +7.17,-3.42, +7.18,-4.16, +7.23,-5.23, +7.53,-6.32, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +0.95,-0.8, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.89,-16.37, +-2.77,-15.7, +-2.77,-15.7, +-2.66,-15.1, +-2.6,-14.74, +-2.53,-14.36, +-2.42,-13.73, +-2.33,-13.21, +-2.22,-12.61, +-2.03,-11.88, +-0.45,-11.47, +-0,-10.8, +-0,-9.74, +0.16,-8.83, +0.92,-7.95, +1.4,-7.11, +2.11,-5.9, +2.68,-4.84, +3.22,-3.83, +3.41,-2.7, +3.73,-1.64, +4.46,-0.45, +5.02,0.46, +5.41,1.41, +5.83,2.18, +6.18,3.53, +6.85,3.96, +7.46,4.55, +8.03,4.79, +8.61,4.97, +9.5,5.48, +9.6,5.54, +9.6,5.54, +10.11,4.47, +10.41,3.79, +9.93,2.85, +9.18,1.43, +8.46,0.33, +7.89,-1.05, +7.6,-2.32, +7.74,-3.1, +7.87,-4.23, +8.23,-5.03, +7.99,-5.31, +7.41,-5.38, +7.02,-5.79, +1.04,-0.87, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-18.08, +-2.72,-17.71, +-2.99,-16.97, +-2.94,-16.65, +-2.91,-16.51, +-2.82,-16.01, +-2.77,-15.73, +-2.67,-15.12, +-2.6,-14.74, +-2.53,-14.33, +-2.44,-13.82, +-2.32,-13.14, +-2.24,-12.73, +-1.74,-11.88, +-0.24,-11.36, +-0,-10.52, +-0,-9.61, +0.28,-8.73, +1.34,-7.72, +1.49,-6.94, +2.13,-5.85, +2.86,-4.54, +3.38,-3.54, +3.58,-2.38, +3.89,-1.15, +4.97,-0.23, +5.04,0.85, +5.58,1.91, +6.13,2.77, +6.54,3.78, +7.31,4.22, +7.79,4.53, +8.36,5.25, +8.86,5.97, +9.28,6, +9.6,5.54, +9.64,5.47, +10.19,4.42, +10.33,3.76, +9.38,2.52, +8.87,1.44, +8.18,0.2, +8.11,-0.99, +8.53,-2.15, +8.5,-3.16, +8.92,-4.27, +7.86,-4.43, +7.7,-4.44, +7.2,-4.68, +0.95,-0.57, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-19.05, +-0,-18.66, +-0,-18.16, +-0,-18.13, +-0.82,-18.01, +-3.03,-17.8, +-3.05,-17.28, +-2.96,-16.76, +-2.94,-16.65, +-2.87,-16.3, +-2.77,-15.73, +-2.75,-15.58, +-2.63,-14.89, +-2.52,-14.31, +-2.45,-13.89, +-2.35,-13.35, +-2.24,-12.72, +-1.53,-11.9, +-0.07,-11.31, +-0,-10.72, +-0,-9.67, +0.8,-8.47, +1.35,-7.63, +1.89,-6.69, +2.48,-5.44, +2.94,-4.19, +3.63,-3.45, +3.79,-2.17, +4.38,-0.91, +5.02,0.23, +5.38,1.27, +5.75,2.14, +6.04,3.47, +6.85,3.96, +7,5.25, +7.47,6.27, +8.06,6.77, +8.49,7.12, +9.55,6.06, +10.1,5.83, +10.4,6, +10.24,4.85, +9.86,3.59, +9.3,2.63, +8.82,1.4, +8.93,0.19, +8.92,-1.1, +9.44,-2.23, +9.37,-3.22, +8.54,-3.3, +7.65,-3.53, +6.76,-3.84, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-19.24, +-0,-19.05, +-0,-19.05, +-0,-19.05, +-0,-19.05, +-0,-19, +-0,-18.83, +-0,-18.61, +-2.21,-17.98, +-3.14,-17.8, +-3.11,-17.61, +-3.05,-17.3, +-2.94,-16.68, +-2.88,-16.32, +-2.79,-15.82, +-2.74,-15.56, +-2.6,-14.74, +-2.53,-14.33, +-2.43,-13.78, +-2.35,-13.33, +-2.21,-12.51, +-1.15,-11.84, +-0.03,-11.27, +-0,-10.11, +0.02,-9.25, +1.27,-8.37, +1.32,-7.5, +2.08,-6.31, +2.63,-5.19, +3.2,-4.04, +3.82,-3.09, +4.33,-2.02, +4.99,-0.74, +5.02,0.5, +5.57,1.56, +6,2.67, +6.52,3.77, +7.28,4.38, +7.37,5.58, +7.8,6.54, +8.49,7.12, +9.04,7.11, +10.44,6.03, +10.44,6.03, +10.42,6.01, +9.85,4.84, +9.44,3.5, +9.05,2.7, +8.86,1.52, +9.17,0.39, +9.85,-0.86, +9.57,-1.63, +8.59,-2.05, +7.45,-2.14, +6.43,-2.28, +0.78,-0.28, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-20.02, +-0,-20.02, +-0,-19.97, +-0,-20.02, +-0,-19.75, +-0,-19.61, +-0,-19.44, +-0,-19.33, +-0,-19.12, +-0,-19.05, +-0,-19.05, +-1.2,-18.96, +-3.24,-18.35, +-3.14,-17.83, +-3.13,-17.78, +-3.07,-17.39, +-2.94,-16.68, +-2.91,-16.52, +-2.8,-15.86, +-2.71,-15.36, +-2.61,-14.81, +-2.51,-14.24, +-2.42,-13.73, +-2.29,-12.96, +-2.16,-12.25, +-0.6,-11.84, +-0,-10.84, +-0,-9.89, +0.35,-9.11, +1.31,-8.08, +1.66,-7.08, +2.28,-6.14, +2.9,-5.06, +3.4,-3.71, +4.15,-2.85, +4.74,-1.64, +5.04,-0.21, +5.44,0.94, +5.74,1.9, +6.28,2.97, +6.88,3.97, +7.54,4.77, +7.64,6.12, +8.42,7.07, +8.49,7.12, +9.34,7.41, +10.14,6.49, +10.27,6.27, +9.81,5.66, +9.5,5.13, +9.07,3.82, +9.39,3.14, +9.66,2, +9.85,0.81, +9.57,0.1, +8.33,-0.44, +7.04,-0.32, +5.38,-0.62, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,-20.99, +-0,-20.47, +-0,-20.41, +-0,-20.26, +-0,-20.13, +-0,-20.02, +-0,-20.02, +-0,-20.02, +-0,-20.02, +-0,-20.02, +-0,-20.02, +-0,-20.02, +-0,-20.02, +-0,-19.8, +-0,-19.58, +-0.05,-19.22, +-2.98,-18.8, +-3.3,-18.71, +-3.23,-18.3, +-3.14,-17.83, +-3.06,-17.38, +-2.94,-16.68, +-2.9,-16.44, +-2.77,-15.7, +-2.71,-15.37, +-2.6,-14.74, +-2.48,-14.05, +-2.39,-13.56, +-2.27,-12.9, +-1.81,-12.24, +-0.16,-11.51, +-0,-10.63, +-0,-9.83, +0.98,-8.86, +1.46,-7.78, +1.97,-6.84, +2.5,-5.91, +3.16,-4.65, +3.71,-3.41, +4.4,-2.41, +4.87,-1.29, +5.1,0.12, +5.68,1.35, +6.07,2.64, +6.42,3.71, +7.34,4.23, +7.69,5.62, +8.04,6.75, +8.49,7.12, +9.06,7.6, +9.25,7.76, +9.43,7.59, +9.77,6.67, +9.64,5.65, +9.37,5.29, +9.21,4.25, +9.45,3.5, +9.57,2.57, +9.52,1.68, +7.97,1.66, +6.36,1.47, +4.87,1.14, +0.44,0.2, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.1,-20.74, +2.19,-20.81, +1.15,-20.92, +0.09,-20.99, +-0,-20.91, +-0,-20.91, +-0,-20.9, +-0,-20.7, +-0,-20.56, +-0,-20.61, +-0,-20.24, +-0,-20.22, +-0,-20.17, +-0,-20.02, +-0,-20.02, +-0,-20.02, +-2.66,-19.36, +-3.35,-19.01, +-3.31,-18.76, +-3.23,-18.33, +-3.14,-17.8, +-3.08,-17.45, +-2.94,-16.65, +-2.88,-16.33, +-2.76,-15.67, +-2.67,-15.12, +-2.59,-14.69, +-2.44,-13.81, +-2.35,-13.3, +-2.26,-12.83, +-0.9,-12.03, +-0,-11.32, +-0,-10.58, +0.16,-9.69, +1.5,-8.53, +1.52,-7.65, +2.34,-6.69, +2.84,-5.63, +3.35,-4.37, +4.02,-3.01, +4.63,-1.97, +4.98,-0.72, +5.44,0.67, +5.78,1.85, +6.4,2.97, +6.97,4.03, +7.57,4.94, +7.79,6.53, +8.39,7.04, +8.82,7.4, +9.27,7.78, +9.49,7.97, +9.23,7.75, +9.29,7.68, +9.13,6.76, +9.53,5.65, +9.64,5.26, +10.06,4.33, +9.68,3.54, +7.69,3.31, +5.81,2.93, +3.98,2.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.22,-20.73, +1.28,-20.91, +-0,-20.99, +-0,-20.99, +-0,-20.99, +-0,-20.99, +-0,-20.99, +-0,-20.99, +-0,-20.99, +-0,-20.99, +-0,-20.82, +-0,-20.93, +-0.05,-20.63, +-3.36,-19.83, +-3.47,-19.69, +-3.43,-19.47, +-3.33,-18.89, +-3.24,-18.38, +-3.14,-17.83, +-3.03,-17.2, +-2.94,-16.65, +-2.81,-15.94, +-2.76,-15.63, +-2.61,-14.82, +-2.53,-14.36, +-2.42,-13.71, +-2.32,-13.14, +-1.84,-12.53, +-0,-12, +-0,-11.08, +-0,-10.27, +0.47,-9.32, +1.48,-8.38, +1.8,-7.37, +2.46,-6.18, +3.12,-5.14, +3.55,-3.65, +4.25,-2.71, +4.77,-1.56, +5.03,-0.15, +5.82,1.05, +6.29,2.35, +6.49,3.51, +7.22,4.17, +7.54,5.74, +7.98,6.69, +8.36,7.01, +9.01,7.56, +9.23,7.75, +9.23,7.75, +9.23,7.75, +8.93,7.5, +8.56,7.04, +9.01,6.4, +9.37,5.89, +9.02,5.3, +6.91,5.13, +4.79,4.92, +2.93,4.69, +0.23,0.59, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.54,-20.1, +3.61,-20.48, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.39,-20.71, +1.53,-21.05, +-0,-21.19, +-0,-21.19, +-0,-21.35, +-0,-21.46, +-0,-21.46, +-0,-21.49, +-0,-21.47, +-0,-21.47, +-0,-21.41, +-0.58,-21.05, +-3.64,-20.65, +-3.58,-20.29, +-3.51,-19.89, +-3.44,-19.53, +-3.32,-18.81, +-3.24,-18.4, +-3.14,-17.8, +-3,-17.04, +-2.92,-16.56, +-2.77,-15.72, +-2.68,-15.21, +-2.6,-14.74, +-2.46,-13.97, +-2.39,-13.54, +-2.26,-12.83, +-1.25,-12.25, +-0,-11.73, +-0,-10.75, +0.07,-9.86, +1.24,-8.9, +1.46,-7.98, +2.3,-6.89, +2.61,-5.86, +3.36,-4.89, +3.81,-3.48, +4.53,-2.21, +4.93,-1.02, +5.31,0.26, +5.85,1.38, +6.36,2.7, +6.87,3.8, +7.58,4.55, +7.38,6.19, +7.93,6.66, +8.55,7.18, +8.91,7.64, +8.77,8.22, +8.57,8.42, +8.43,8.51, +7.68,9.22, +5.96,12.33, +6.36,13.67, +6.42,12.23, +6.23,6.77, +3.77,6.63, +1.57,6.43, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.48,-19.72, +3.48,-19.72, +3.49,-19.78, +3.57,-20.27, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.69,-20.94, +3.73,-21.15, +3.76,-21.3, +3.78,-21.46, +3.78,-21.44, +3.78,-21.44, +3.8,-21.54, +3.77,-21.39, +3.77,-21.39, +3.81,-21.63, +1.09,-21.89, +-0,-21.96, +-0,-21.96, +-0,-21.96, +-0,-21.96, +-0,-21.96, +-0,-21.96, +-0,-21.96, +-0,-21.96, +-1.95,-21.74, +-3.76,-21.31, +-3.68,-20.87, +-3.65,-20.68, +-3.09,-20.14, +-2.95,-19.57, +-2.49,-19, +-3.21,-18.19, +-3.11,-17.61, +-2.94,-16.68, +-2.84,-16.08, +-2.77,-15.7, +-2.62,-14.84, +-2.57,-14.57, +-2.43,-13.78, +-2.34,-13.26, +-2.15,-12.7, +-0.46,-12.08, +-0,-11.42, +-0,-10.41, +0.25,-9.63, +1.46,-8.68, +1.63,-7.67, +2.48,-6.45, +3.01,-5.34, +3.7,-4.47, +3.96,-3.11, +4.67,-1.84, +5.01,-0.74, +5.8,0.69, +6.21,1.97, +6.53,2.95, +6.99,4.03, +6.96,5.48, +7.46,6.26, +8.06,7.06, +7.3,8.31, +7.14,8.51, +7.71,9.18, +7.77,11.07, +5.4,13.11, +4.98,13.67, +5.12,14.07, +5.45,14.98, +5.04,8.69, +3.19,8.29, +0.98,8.08, +0,1, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.48,-19.72, +3.48,-19.72, +3.48,-19.72, +3.48,-19.72, +3.48,-19.72, +3.63,-20.57, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.72,-21.11, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +2.56,-21.76, +0.05,-21.96, +-0,-21.96, +-0,-21.96, +-0,-22.16, +-0,-22.66, +-0,-22.65, +-0,-22.74, +-0,-22.74, +-0.19,-22.61, +-3.32,-21.9, +-2.18,-22.19, +-0.46,-21.68, +-0,-20.99, +-0,-20.16, +-0,-19.7, +-0,-18.93, +-1.57,-17.96, +-3.03,-17.17, +-2.92,-16.54, +-2.78,-15.77, +-2.7,-15.29, +-2.6,-14.74, +-2.51,-14.25, +-2.43,-13.78, +-2.28,-12.92, +-1.57,-12.54, +-0,-11.91, +-0,-11.15, +-0,-10, +0.72,-9.25, +1.49,-8.45, +2.11,-7.23, +2.61,-6.3, +3.2,-5.18, +3.77,-3.77, +4.38,-2.47, +4.82,-1.47, +5.08,-0.43, +5.77,0.94, +6.16,2.31, +6.5,3.71, +6.65,4.4, +6.76,5.67, +7.16,6.75, +6.75,8.04, +7.03,8.38, +7.83,12.41, +7.97,13.8, +5.31,14.59, +5.16,14.18, +5.21,14.33, +5.21,14.33, +4.75,12.98, +2.06,9.67, +-0.26,9.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.44,-19.53, +3.48,-19.72, +3.48,-19.72, +3.48,-19.72, +3.48,-19.72, +3.48,-19.72, +3.48,-19.72, +3.48,-19.72, +3.52,-19.96, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.72,-21.08, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.94,-22.35, +4.01,-22.73, +3.99,-22.62, +3.89,-22.07, +3.67,-21.65, +-0,-21.96, +-0,-21.96, +-0,-22.06, +-0,-22.8, +-0,-22.94, +-0,-22.94, +-0,-23.87, +-0,-23.55, +-0,-23.81, +-0,-23.91, +-0,-23.18, +-0,-22.45, +-0,-21.55, +-0,-20.87, +-0,-20.13, +-0,-19.33, +-0,-18.49, +-0.38,-17.53, +-0.42,-16.87, +-1.21,-15.97, +-2.77,-15.7, +-2.64,-14.96, +-2.57,-14.55, +-2.44,-13.83, +-2.35,-13.32, +-2,-12.86, +-0,-12.33, +-0,-11.76, +-0,-10.91, +0.22,-9.81, +1.32,-8.98, +1.47,-7.93, +2.4,-6.75, +2.73,-5.89, +3.29,-4.93, +3.78,-3.31, +4.54,-2.17, +4.89,-1.16, +5.07,0.05, +5.53,1.53, +5.91,2.79, +6.02,3.85, +6.06,5.09, +6.91,6.24, +7.39,8.81, +8.07,11.64, +7.9,13.68, +6.65,13.94, +5.45,14.98, +5.59,15.37, +5.53,15.21, +5.75,15.8, +5.34,14.67, +1.32,10.19, +-0.75,9.86, +-0.25,1.39, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.52,-17.9, +5.16,-18.31, +4.54,-18.45, +3.97,-19.2, +3.56,-19.7, +3.48,-19.72, +3.65,-19.68, +3.7,-19.67, +3.48,-19.72, +3.61,-19.69, +3.69,-19.67, +3.85,-20.53, +3.75,-20.66, +3.78,-20.65, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.65,-20.68, +3.79,-21.47, +3.81,-21.63, +3.81,-21.63, +3.81,-21.63, +3.89,-22.07, +4.22,-23.93, +4.3,-24.37, +4.33,-24.53, +4.35,-24.7, +4.35,-24.7, +4.45,-25.24, +4.06,-24.68, +1.13,-22.89, +-0,-21.96, +-0,-22.55, +-0,-22.94, +-0,-23.71, +-0,-24.58, +-0,-25.91, +0,-25.93, +-0,-25.2, +-0,-24.74, +-0,-23.93, +-0,-22.94, +-0,-22.1, +-0,-21.36, +-0,-20.44, +-0,-19.88, +-0,-19.05, +-0,-18.32, +-0,-17.24, +-0,-16.43, +-0.35,-15.79, +-2.01,-15.13, +-2.6,-14.74, +-2.48,-14.07, +-2.43,-13.78, +-2.31,-13.11, +-0.37,-12.69, +-0,-12.14, +-0,-11.3, +-0,-10.47, +0.77,-9.63, +1.53,-8.7, +1.73,-7.63, +2.43,-6.51, +2.84,-5.45, +3.32,-4.16, +3.92,-3.17, +4.64,-1.86, +4.64,-0.72, +4.98,0.54, +5.33,1.95, +7.01,5.12, +9.72,1.48, +9.94,5.41, +10.04,9.27, +9.32,11.68, +7.55,13.08, +7.18,14.02, +5.64,15.49, +5.78,15.89, +5.95,16.36, +6.18,16.99, +4.94,16.87, +0.83,12.35, +-1.49,10.29, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.18,-16.99, +6.29,-17.29, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.68,-18.34, +6.85,-18.81, +6.59,-18.9, +6.64,-18.88, +6.85,-18.81, +6.71,-18.86, +6.59,-18.9, +6.8,-18.83, +6.81,-19.29, +7.05,-19.77, +7.08,-19.76, +6.93,-19.81, +6.04,-20.06, +5.2,-20.28, +4.18,-20.55, +3.65,-20.68, +3.79,-21.49, +3.81,-21.63, +3.81,-21.63, +4.08,-23.16, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.48,-25.41, +4.52,-25.65, +4.57,-25.93, +4.61,-26.13, +2.93,-25.23, +0.67,-26.38, +0,-26.14, +0,-26.05, +0,-26.6, +0,-26.53, +-0,-26.05, +-0,-25.21, +-0,-24.49, +-0,-23.79, +-0,-22.69, +-0,-21.83, +-0,-20.99, +-0,-20.14, +-0,-19.47, +-0,-18.63, +-0,-17.93, +-0,-16.91, +-0,-16.1, +-0,-15.52, +-0,-14.43, +-0,-13.27, +0,-13.02, +0.25,-12.66, +1.03,-12.28, +1.6,-11.1, +1.56,-10.58, +1.45,-10.12, +1.21,-9.21, +2.13,-8.1, +2.53,-7.02, +2.65,-6.36, +2.82,-5.67, +3.82,-4.02, +4.46,-2.39, +5.73,-0.01, +7.39,2.94, +7.5,4.33, +8.35,4.82, +8.09,6.79, +9.03,-1.4, +11.08,1.43, +10.69,7.35, +9.88,10.35, +7.68,12.09, +7.47,13.73, +6.27,14.9, +5.64,15.5, +5.95,16.36, +6.22,17.08, +5.31,17.22, +1.92,15.87, +-1.78,10.93, +-0.47,1.3, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.09,-17.55, +5.43,-17.22, +6.18,-16.99, +6.18,-16.99, +6.36,-17.47, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.74,-18.52, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.91,-18.97, +7.05,-19.36, +7.16,-19.67, +7.18,-19.73, +7.18,-19.73, +7.18,-19.73, +6.32,-19.98, +4.05,-20.57, +3.73,-21.15, +3.81,-21.63, +4.05,-22.94, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.46,-25.29, +4.52,-25.65, +4.67,-26.49, +4.69,-26.61, +4.73,-26.85, +4.11,-26.66, +1.56,-26.88, +0,-26.78, +0,-27.26, +0,-27.57, +0,-26.88, +-0,-26.05, +-0,-25.2, +-0,-24.35, +-0,-23.08, +-0,-22.21, +-0,-21.41, +-0,-20.72, +-0,-19.9, +-0,-18.93, +-0,-18.36, +-0,-17.41, +-0,-16.55, +-0,-15.82, +-0,-14.41, +-0,-14, +0,-13.16, +0.28,-13, +2.14,-12.14, +2.03,-11.51, +1.87,-10.62, +2.44,-9.57, +3.11,-8.53, +3.18,-7.28, +3.71,-6.08, +3.9,-4.89, +4.7,-3.75, +5.47,-2.47, +5.91,-1.04, +6.81,0.52, +7.06,1.85, +7.24,3.27, +7.5,4.33, +7.8,5.36, +7.28,-2.28, +10.14,-1.98, +11.11,4.19, +9.96,8.78, +8.02,10.82, +7.35,12.72, +7.28,13.99, +6.12,15.02, +6.05,16.62, +6.01,16.52, +5.95,17.55, +1.6,16.14, +-1.86,10.36, +-0.52,1.44, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.94,-16.65, +2.94,-16.65, +2.94,-16.65, +2.94,-16.65, +3.78,-16.85, +6.18,-16.99, +6.18,-16.99, +6.18,-16.99, +6.31,-17.33, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.92, +6.82,-18.74, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.9,-18.95, +7.12,-19.57, +7.18,-19.73, +7.18,-19.73, +7.18,-19.73, +6.52,-19.93, +5.11,-20.9, +3.85,-21.85, +4.29,-24.31, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.45,-25.24, +4.52,-25.65, +4.63,-26.25, +4.69,-26.61, +4.76,-27.02, +4.86,-27.57, +4.19,-27.14, +2.73,-27.06, +1.24,-27.64, +0,-27.99, +0,-27.63, +-0,-26.88, +-0,-25.8, +-0,-25.08, +-0,-23.94, +-0,-22.94, +-0,-21.96, +-0,-20.99, +-0,-20.24, +-0,-19.29, +-0,-18.49, +-0,-18.08, +-0,-17.06, +-0,-16.36, +-0,-15.11, +-0,-14, +-0,-13.75, +0,-13.02, +1.94,-12.85, +2.09,-11.87, +2.02,-11.46, +2.08,-10.36, +3.32,-9.11, +3.01,-8.27, +3.92,-6.78, +3.7,-5.75, +4.23,-4.43, +4.96,-3.38, +6.19,-2.25, +6.89,-0.91, +6.91,1.04, +7.54,2.53, +7.3,3.42, +7.86,4.54, +5.24,-5.04, +8.32,-3.7, +9.93,0.59, +10.58,6.08, +8.76,9.59, +7.9,11.66, +7.62,13.2, +6.53,14.48, +6.34,15.51, +5.96,16.39, +6.61,18.16, +3.48,19.72, +-0.26,12.44, +-0.4,2.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.77,-15.7, +2.77,-15.7, +2.77,-15.7, +2.77,-15.7, +2.88,-16.34, +2.94,-16.65, +2.94,-16.65, +3.47,-16.52, +5.7,-15.99, +6.16,-16.94, +6.18,-16.99, +6.18,-16.99, +6.29,-17.27, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.89,-18.92, +7.08,-19.45, +7.39,-20.3, +7.81,-21.45, +8.4,-23.08, +8.58,-23.56, +8.05,-23.71, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.4,-24.93, +4.52,-25.65, +4.6,-26.06, +4.69,-26.61, +4.8,-27.21, +4.86,-27.57, +4.86,-27.57, +4.86,-27.57, +4.97,-28.2, +2.51,-28.74, +0,-28.23, +0,-27.57, +-0,-26.74, +-0,-25.44, +-0,-24.49, +-0,-23.35, +-0,-22.21, +-0,-21.24, +-0,-20.35, +-0,-19.74, +-0,-19.05, +-0,-18.36, +-0,-17.64, +0,-16.91, +-0,-14.55, +-0,-14, +-0,-14, +0,-13.39, +1.29,-12.91, +2.18,-12.35, +2.09,-11.87, +2.46,-10.77, +3.52,-9.68, +3.27,-8.97, +3.46,-7.76, +3.81,-6.29, +4.18,-5.25, +4.43,-4.06, +5.76,-3.1, +6.6,-1.67, +6.96,-0.15, +7.41,1.5, +7.49,2.73, +7.23,3.94, +4.78,-3.17, +6.43,-4.87, +9.07,-0.34, +9.81,4.19, +9.51,7.59, +8.07,11.07, +7.24,12.54, +5.26,14.46, +5.17,14.2, +5.81,15.95, +6.6,18.13, +3.79,19.01, +-0.4,12.91, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.6,-14.74, +2.6,-14.74, +2.66,-15.09, +2.77,-15.7, +2.77,-15.7, +2.77,-15.7, +2.77,-15.7, +2.79,-15.81, +2.93,-16.61, +3.9,-16.41, +5.68,-15.93, +5.78,-15.89, +6.03,-16.55, +6.18,-16.99, +6.18,-16.99, +6.18,-16.99, +6.49,-17.83, +6.52,-17.9, +6.52,-17.9, +6.53,-17.95, +6.68,-18.34, +6.77,-18.61, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +6.85,-18.81, +7.13,-19.6, +7.51,-20.64, +7.89,-21.68, +8.08,-22.21, +8.18,-22.47, +8.52,-23.41, +8.58,-23.56, +8.58,-23.56, +7.52,-23.85, +5.06,-24.51, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.35,-24.7, +4.33,-24.55, +4.27,-24.2, +4.3,-24.41, +4.42,-25.05, +4.52,-25.65, +4.62,-26.2, +4.78,-27.09, +4.86,-27.57, +4.88,-27.7, +4.94,-27.99, +5.03,-28.52, +5.05,-28.66, +1.89,-28.8, +0,-28.11, +-0,-27.3, +-0,-26.33, +-0,-25.08, +-0,-23.77, +-0,-22.57, +-0,-21.41, +-0,-20.39, +-0,-19.74, +-0,-19.05, +-0,-18.44, +-0,-18.08, +0,-17.24, +-0,-16.18, +-0,-14.1, +-0,-14, +0,-13.86, +1.7,-12.88, +2.26,-12.83, +2.12,-12.01, +2.21,-11.28, +3.68,-10.1, +3.45,-9.47, +3.23,-8.32, +3.99,-6.9, +3.83,-6.22, +4.36,-4.99, +4.87,-3.72, +6.38,-2.46, +6.91,-1.01, +6.96,0.46, +7.61,1.74, +7.27,3.2, +4.11,-3.44, +5.65,-4.71, +8.19,-2.25, +9.84,2.07, +9.44,6.61, +8.35,9.02, +6.84,11.84, +4.93,13.54, +4.95,13.61, +5.86,16.09, +6.52,17.9, +4.56,17.68, +0.54,16.17, +-0.16,0.93, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.43,-13.78, +2.52,-14.31, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.63,-14.9, +2.75,-15.58, +2.77,-15.7, +2.77,-15.7, +2.77,-15.7, +2.87,-15.68, +4.53,-15.57, +5.7,-15.92, +5.78,-15.89, +5.78,-15.89, +5.82,-16, +6.1,-16.76, +6.18,-16.99, +6.18,-16.99, +6.37,-17.49, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.59,-18.11, +6.59,-18.11, +6.54,-17.98, +6.65,-18.27, +6.7,-18.4, +6.68,-18.34, +6.7,-18.4, +7.13,-19.59, +7.84,-21.55, +7.84,-21.55, +8.01,-22.01, +8.18,-22.47, +8.13,-22.34, +8.23,-22.6, +8.48,-23.29, +8.58,-23.56, +8.58,-23.56, +6.99,-23.99, +4.35,-24.7, +4.35,-24.7, +4.28,-24.26, +4.12,-23.39, +4.03,-22.83, +4.03,-22.83, +4.1,-23.23, +4.22,-23.93, +4.4,-24.93, +4.52,-25.65, +4.67,-26.47, +4.82,-27.33, +4.9,-27.81, +4.92,-27.88, +5.07,-28.76, +5.18,-29.36, +5.06,-28.68, +1.89,-28.55, +0,-27.87, +-0,-26.6, +-0,-25.62, +-0,-23.79, +-0,-22.52, +-0,-21.41, +-0,-20.75, +-0,-20.02, +-0,-19.05, +-0,-18.69, +-0,-18.08, +0,-17.35, +-0,-16.08, +-0,-14.69, +-0,-14, +0,-14, +0.28,-13.85, +2.26,-12.83, +2.21,-12.55, +2.09,-11.87, +3.79,-10.41, +3.59,-9.86, +3.31,-9.09, +3.87,-7.49, +3.8,-6.59, +4.33,-5.48, +4.39,-4.3, +5.31,-3.04, +6.58,-1.91, +6.95,-0.52, +6.9,1.06, +7.54,2.53, +4,-2.27, +4.98,-4.63, +8.49,-2.02, +9.4,1.68, +8.8,4.8, +7.9,7.71, +6.18,11.2, +4.74,13.02, +4.41,12.11, +6.19,14.63, +7.29,16.58, +5.16,15.41, +0.79,14.48, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.54,-14.38, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.7,-15.34, +2.77,-15.7, +2.77,-15.7, +2.97,-15.66, +4.42,-15.28, +5.45,-14.98, +5.58,-15.34, +5.78,-15.89, +5.78,-15.89, +5.78,-15.89, +5.93,-16.28, +6.18,-16.99, +6.18,-16.99, +6.21,-17.06, +6.37,-17.51, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +6.52,-17.9, +7.01,-19.27, +7.8,-21.44, +7.84,-21.55, +8.09,-22.24, +8.18,-22.47, +8.18,-22.47, +8.14,-22.35, +8.18,-22.47, +8.18,-22.47, +8.38,-23.02, +8.58,-23.56, +8.58,-23.56, +5.23,-24.19, +4.3,-24.37, +4.11,-23.31, +3.93,-22.27, +3.81,-21.63, +3.79,-21.51, +3.89,-22.06, +4.03,-22.86, +4.16,-23.57, +4.33,-24.55, +4.55,-25.79, +4.67,-26.47, +5.34,-26.93, +4.86,-27.57, +4.96,-28.11, +5.15,-29.21, +5.05,-28.64, +4.98,-28.25, +1.34,-27.32, +0,-26.41, +-0,-25.24, +-0,-23.66, +-0,-22.21, +-0,-21.27, +-0,-20.39, +-0,-19.9, +-0,-19.05, +-0,-18.73, +-0,-18.08, +0,-17.58, +-0,-16.08, +-0,-14.97, +-0,-14.72, +0,-14, +1.04,-13.9, +2.33,-13.24, +2.26,-12.83, +2.21,-12.55, +3.2,-11.43, +3.79,-10.41, +3.46,-9.5, +3.88,-8.63, +4.23,-7.32, +4.14,-6.12, +4.62,-5.07, +4.79,-3.63, +5.93,-2.48, +6.79,-1.61, +6.97,0, +7.2,1.67, +2.94,-3.62, +5.24,-4.29, +7.37,-2.5, +8.47,0.25, +8.98,3.36, +8.19,6.59, +6.81,10.06, +5.04,11.96, +5.07,10.88, +6.6,10.95, +6.74,11.29, +4.23,9.92, +1.04,7.03, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.67,-13.06, +2.49,-13.77, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.57,-14.6, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.63,-14.91, +2.77,-15.7, +3.01,-15.64, +4.89,-15.15, +5.45,-14.98, +5.45,-14.98, +5.46,-15, +5.72,-15.71, +5.78,-15.89, +5.78,-15.89, +5.79,-15.91, +6.12,-16.8, +6.18,-16.99, +6.18,-16.99, +6.18,-16.99, +6.37,-17.51, +6.47,-17.79, +6.5,-17.85, +6.51,-17.87, +6.47,-17.79, +6.5,-17.85, +6.51,-17.87, +6.43,-17.67, +6.38,-17.54, +7.13,-17.92, +8.58,-20.67, +7.84,-21.55, +7.84,-21.55, +7.89,-21.67, +8.09,-22.24, +8.18,-22.47, +7.84,-21.55, +8.05,-22.12, +8.08,-22.21, +8.01,-22.01, +8.14,-22.36, +8.29,-22.78, +8.23,-22.6, +6.61,-22.64, +3.93,-22.32, +3.69,-20.95, +1.78,-19.99, +3.48,-19.72, +3.62,-20.54, +3.77,-21.39, +3.89,-22.04, +3.93,-22.32, +6.25,-23.33, +8.66,-23.8, +8.91,-24.48, +9.16,-25.16, +7.94,-25.88, +5.46,-27.12, +4.67,-26.49, +4.65,-26.35, +4.45,-25.24, +2.71,-24.64, +-0,-23.57, +-0,-21.69, +-0,-20.63, +-0,-20.14, +-0,-19.33, +-0,-19.19, +-0,-19.05, +-0,-19.05, +0,-18.2, +0,-18.08, +-0,-15.94, +-0,-14.97, +-0,-14.97, +0,-14.85, +0.35,-14.1, +2.43,-13.78, +2.3,-13.07, +2.26,-12.83, +3.01,-11.91, +4,-10.98, +3.74,-10.27, +4.27,-9.22, +4.68,-8.1, +4.05,-7.01, +4.71,-5.61, +4.88,-4.78, +5.13,-3.1, +5.79,-1.95, +6.79,-0.58, +6.96,0.46, +3.76,-2.76, +4.64,-3.78, +6.38,-2.81, +8.02,-0.99, +8.74,0.67, +8.38,2.93, +8.03,6.86, +6.01,9.92, +4.91,8.5, +5.11,7.13, +5.42,5.96, +2.97,5.15, +0.98,2.68, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.63,-12.72, +4.58,-13.22, +2.79,-13.71, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.55,-14.45, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +3.01,-14.9, +4.9,-14.99, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.47,-15.03, +5.63,-15.48, +5.78,-15.89, +5.78,-15.89, +5.8,-15.94, +6.02,-16.53, +6.17,-16.96, +6.18,-16.99, +6.19,-17.01, +6.18,-16.99, +6.19,-17.01, +6.19,-17.01, +6.18,-16.99, +6.19,-17.01, +6.19,-17.01, +6.18,-16.99, +7.25,-17.44, +11.41,-19.76, +11.47,-19.86, +11.07,-20.05, +9.66,-20.71, +9.92,-20.59, +9.2,-20.92, +10.11,-20.5, +7.73,-21.25, +9.47,-20.39, +9.99,-20.29, +9.3,-19.98, +9.99,-20.29, +9.49,-21.59, +8.87,-22.36, +8.65,-22.25, +7.3,-21.45, +4.15,-20.4, +2.55,-16.95, +1.34,-14.94, +2.88,-16.34, +3.23,-18.32, +3.36,-19.08, +3.45,-19.58, +5.64,-20, +7.85,-21.58, +8.2,-22.52, +8.47,-23.27, +8.52,-23.4, +6.6,-22.59, +3.89,-22.04, +4.11,-20.93, +3.66,-20.77, +3.78,-22.1, +-1.05,-18.82, +-1.3,-15.07, +-0.42,-16.79, +0.7,-17.09, +1.06,-17.3, +0,-17.74, +0,-17.64, +0.87,-17.97, +1.79,-17.92, +2.66,-17.68, +2.13,-16.34, +-0,-14.97, +0,-14.97, +1.11,-14.87, +2.58,-14.62, +2.46,-13.94, +2.36,-13.37, +2.26,-12.83, +3.11,-12.31, +4.17,-11.46, +3.91,-10.75, +4.54,-10.06, +5.21,-9.02, +4.59,-7.95, +5.04,-6.61, +4.92,-5.33, +5.44,-4.14, +5.44,-2.54, +5.95,-1.51, +6.6,-0.39, +2.87,-2.79, +4.22,-2.99, +5.38,-2.67, +7.07,-2.22, +7.74,-1.19, +7.63,-0.3, +8.32,3.14, +6.33,6.15, +4.47,6.71, +5.55,6.27, +7.17,5.16, +4.67,4.34, +2.06,2.45, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.56,-12.54, +4.79,-13.15, +4.65,-13.2, +3.74,-13.44, +2.58,-13.75, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.46,-13.97, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.75,-14.71, +3.71,-14.47, +5.09,-14.08, +5.14,-14.13, +5.23,-14.36, +5.41,-14.88, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.53,-15.18, +5.73,-15.73, +5.78,-15.89, +5.78,-15.89, +5.83,-16.01, +5.92,-16.26, +5.92,-16.26, +5.99,-16.46, +5.92,-16.26, +5.93,-16.3, +5.93,-16.3, +5.9,-16.2, +7.23,-17.09, +9.17,-18.21, +10.98,-19.02, +11.04,-19.13, +11.29,-19.55, +11.47,-19.86, +11.29,-19.55, +11.16,-19.34, +10.98,-19.02, +11.1,-19.23, +9.09,-18.95, +10.02,-18.4, +10.38,-17.97, +10.62,-18.39, +10.77,-18.66, +11.16,-19.34, +11.59,-20.07, +11.76,-19.32, +9.1,-16.55, +4.27,-12.56, +4.17,-11.46, +4.03,-11.16, +3.5,-12.45, +3.72,-15.47, +3.81,-16.76, +2.92,-16.58, +4.99,-17.39, +6.59,-18.11, +6.89,-18.93, +7.51,-20.64, +7.23,-19.86, +4.21,-18.38, +2.16,-14.67, +1.19,-11.6, +-1.18,-12.06, +-2.66,-11.42, +-4.96,-14.57, +-5.49,-12.98, +-1.41,-12.66, +1.05,-11.96, +2.01,-13.26, +2.55,-14.81, +2.89,-16.41, +2.83,-16.06, +2.83,-16.06, +2.83,-16.06, +2.35,-15.46, +1.09,-14.73, +2.2,-14.64, +2.18,-14.5, +2.56,-14.5, +2.47,-14.02, +2.38,-13.51, +2.26,-12.83, +3.08,-12.61, +4.45,-12.24, +4.25,-11.67, +4.7,-10.44, +5.6,-9.7, +5.29,-9.16, +4.97,-7.73, +5.32,-6.08, +5.83,-4.89, +5.22,-3.01, +5.7,-1.93, +5.95,-0.87, +4.45,-1.89, +3.63,-2.73, +5,-2.05, +5.81,-2.12, +5.92,-2.16, +5.21,-1.6, +5.53,0.56, +4.6,4.09, +4.84,5.53, +6.49,5.47, +6.48,3.41, +6.25,2.32, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.51,-11.28, +5.57,-11.76, +5.39,-11.82, +4.85,-12.07, +4.45,-12.24, +4.5,-12.22, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.53,-12.45, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.23,-13.32, +2.52,-13.77, +2.43,-13.78, +2.43,-13.78, +2.44,-13.83, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +4.18,-14.33, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.19,-14.27, +5.36,-14.72, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.53,-15.2, +5.71,-15.68, +5.78,-15.89, +5.78,-15.89, +5.77,-15.87, +5.78,-15.89, +5.73,-15.75, +5.7,-15.66, +5.68,-15.6, +9.03,-17.85, +10.98,-19.02, +10.98,-19.02, +10.98,-19.02, +10.98,-19.02, +10.98,-19.02, +10.98,-19.02, +10.98,-19.02, +10.74,-18.6, +10.62,-18.39, +10.15,-17.58, +9.35,-17.19, +9.59,-16.6, +9.37,-16.23, +9.46,-16.39, +9.53,-16.5, +11.43,-16.01, +12.1,-15.87, +12.7,-13.55, +5.78,-7.87, +4.13,-8.01, +4.91,-8.16, +5.5,-7.7, +5.21,-7.52, +5.57,-8.93, +5.66,-11.99, +3.79,-10.84, +3.79,-11.18, +5.74,-13.66, +6.95,-14.61, +5.18,-12.02, +3.31,-8.41, +-0.9,-7.77, +-2.33,-9.48, +-5.58,-9.94, +-6.61,-11.45, +-5.41,-10.87, +-5.42,-12.87, +-3.61,-12.61, +-0.87,-11.93, +1.31,-11.82, +2.68,-12.09, +4.04,-11.89, +3.57,-12.48, +4.23,-12.97, +4.72,-13.26, +3.37,-13.36, +3.05,-13.09, +1.85,-11.72, +2.04,-11.59, +2.3,-11.64, +2.77,-12.4, +2.21,-12.55, +2.16,-12.23, +2.12,-12.01, +3.21,-12.07, +4.37,-12.01, +4.23,-11.63, +5.55,-10.66, +5.96,-10.32, +5.53,-9.57, +4.97,-7.92, +5.82,-6.59, +6.17,-4.73, +5.5,-3.58, +5.55,-2.3, +5.9,-1.19, +4.49,-2.65, +4.95,-2.98, +4.75,-1.73, +4.88,-1.78, +4.75,-1.73, +4.49,-1.63, +4.49,0.11, +3.97,2.8, +5.34,3.89, +7.64,4.66, +4.66,1.16, +5.7,1.13, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.29,-11.4, +5.84,-11.6, +5.49,-11.77, +4.64,-12.16, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.48,-12.32, +4.77,-13.1, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.41,-13.26, +3.95,-13.39, +3.05,-13.63, +2.61,-13.74, +2.51,-14.24, +3.06,-14.55, +4.27,-14.31, +4.78,-14.18, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.23,-14.36, +5.34,-14.67, +5.36,-14.74, +5.43,-14.91, +5.45,-14.98, +5.45,-14.98, +5.48,-15.05, +5.49,-15.07, +5.49,-15.09, +5.47,-15.03, +5.48,-15.05, +6.01,-15.17, +9.78,-17.72, +10.5,-18.18, +10.66,-18.46, +10.64,-18.42, +10.71,-18.54, +10.71,-18.56, +10.71,-18.54, +10.57,-18.3, +10.5,-18.18, +10.43,-18.06, +10.07,-17.44, +9.26,-16.03, +8.5,-14.73, +8.21,-14.22, +7.97,-13.8, +7.76,-13.44, +9.4,-11.77, +9.67,-11.08, +9.99,-8.67, +6.61,-4.28, +4.55,-4.18, +4.58,-5.77, +6.02,-5.6, +5.88,-5.69, +6,-5.03, +6.33,-4.57, +6.49,-4.57, +6.01,-4.53, +5.7,-4.22, +5.9,-4.1, +5.76,-2.72, +3.89,-3.52, +1.1,-5.9, +-3.49,-7.13, +-4.81,-6.08, +-5.76,-8.98, +-4.11,-10.05, +-0.11,-10.54, +-3.82,-12.86, +-0.99,-11.48, +0.64,-11.02, +1.6,-10.47, +-0,-10.07, +-0.47,-9.55, +-0.11,-9.24, +3.53,-9.4, +5.36,-9.28, +3.18,-8.14, +1.17,-7.38, +0.64,-7.59, +1.11,-7.46, +2.14,-7.9, +3.56,-9.79, +3.09,-8.61, +3.33,-8.07, +4.05,-8.61, +3.45,-8.99, +4.34,-9.76, +4.79,-9.87, +5.39,-9.34, +5.74,-9.07, +5.24,-8.63, +5.11,-6.85, +5.82,-5.71, +7.02,-3.4, +6.43,-2.71, +5.98,-1.83, +6.46,-0.57, +5.8,-1.19, +5.51,-2.38, +4.9,-1.64, +5.64,-1.6, +4.74,-1.13, +4.36,-1.26, +2.81,0.45, +4,3.58, +4.86,2.75, +9.71,2.94, +5.08,0.71, +4.02,1.46, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.03,-10.44, +6.35,-11, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +5.19,-11.91, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.51,-12.4, +4.76,-13.07, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.55,-13.23, +4.52,-13.23, +4.76,-13.26, +4.93,-13.54, +5.07,-13.93, +5.06,-13.9, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.16,-14.18, +5.17,-14.2, +5.16,-14.18, +5.15,-14.16, +5.13,-14.09, +5.08,-13.95, +5.1,-14.01, +8.01,-16.13, +10.17,-17.62, +10.25,-17.76, +10.15,-17.58, +10.13,-17.55, +10.25,-17.76, +10.22,-17.7, +10.25,-17.76, +10.19,-17.66, +10.07,-17.44, +9.71,-16.81, +8.94,-15.49, +8.05,-13.95, +7,-12.12, +7.1,-11.71, +6.62,-10.87, +6.27,-9.17, +6.67,-7.61, +6.8,-6.19, +6.73,-2.02, +5.81,0.46, +5.56,-1.24, +6.11,-2.41, +6.39,-3.12, +6.23,-3.42, +6.39,-3.87, +6.37,-3.48, +6.04,-2.83, +5.23,-2.7, +5.96,-2.01, +5.44,-1.29, +4.47,-1.7, +2.94,-3.27, +-1.53,-4.79, +-4.88,-6.53, +-8.31,-8.08, +-7.92,-7.64, +-6.18,-6.4, +-5.96,-7.61, +-5.26,-9.71, +-4.34,-10.53, +-1.11,-7.59, +0.62,-5.52, +-2.53,-9.47, +-2.35,-8.72, +-1.89,-9.22, +-0.19,-7.56, +2.32,-6.28, +1.25,-6.96, +-0,-6.03, +-0,-5.78, +0.57,-5.7, +1.11,-6.45, +2.66,-6.85, +2.95,-6.32, +3.99,-6.31, +3.31,-6.68, +4.15,-6.45, +5.37,-5.69, +5.82,-6.59, +5.54,-7, +6.04,-6.39, +6.3,-5.79, +5.72,-5, +7.04,-4.07, +7.92,-2.52, +6.58,-2.39, +6.31,-1.79, +7.05,-0.8, +6.04,-0.54, +5.66,-2.06, +5.45,-1.26, +4.89,-0.46, +3.89,0.02, +3.28,-0.13, +2.88,0.61, +3.13,2.63, +5.08,1.73, +8.26,3.3, +5.91,1.35, +2.92,1.46, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.35,-11, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.4,-11.34, +5.67,-11.68, +4.77,-12.1, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.49,-12.34, +4.69,-12.88, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.81,-13.2, +4.85,-13.33, +4.85,-13.31, +4.82,-13.23, +4.85,-13.31, +4.85,-13.31, +4.8,-13.18, +5.38,-13.05, +5.19,-13.15, +5.03,-13.18, +4.84,-13.29, +4.83,-13.28, +4.83,-13.27, +4.86,-13.35, +4.82,-13.23, +4.75,-13.04, +4.71,-12.95, +6.8,-14.53, +8.58,-15.94, +9.71,-16.81, +9.66,-16.74, +9.65,-16.71, +9.65,-16.71, +9.53,-16.5, +9.59,-16.6, +9.59,-16.6, +10.05,-16.47, +11.69,-15.28, +12.05,-14.36, +11.3,-13.47, +9.89,-11.43, +8.78,-10.09, +6.96,-8.89, +7.93,-6.49, +8.32,-3.74, +7.12,-1.93, +6.14,-1.12, +6.38,0.77, +6.62,1.76, +6.15,1.64, +5.62,0.97, +5.94,0.72, +6.16,-0.97, +5.7,-1.92, +4.35,-2.25, +5.34,-2.43, +5.87,-2.27, +5.12,-2, +4.58,-1.32, +3.49,-0.62, +2.24,-1.27, +-0.8,-2.48, +-3.52,-3.5, +-5.18,-5.57, +-4.72,-10.4, +-6.08,-8.32, +-5.64,-6.89, +-6.06,-8.67, +-3.05,-8.01, +-3.6,-10.39, +-2.63,-8.49, +-4.68,-4.76, +-2.31,-8.43, +-2.49,-7.93, +-1.2,-8.56, +-0.81,-8.04, +-0.84,-7.75, +-1.86,-7.2, +-0,-5.05, +-0,-4.81, +-0,-5.05, +0.63,-5.73, +1.67,-6.63, +2.71,-6.16, +1.77,-6.17, +1.76,-5.99, +3.47,-5.07, +4.28,-4.57, +5.45,-4.58, +5.83,-5.09, +5.86,-4.91, +5.47,-4.59, +5.82,-4.69, +6.38,-3.46, +7.17,-1.59, +6.58,-2.39, +5.99,-1.38, +6.92,-0.87, +6,-0.15, +4.59,-1.27, +4.47,-0.13, +3.95,0.63, +4.47,1.17, +3.1,0.77, +2.56,1.16, +2.1,1.96, +6.08,2.3, +6.59,2.23, +5.79,2.22, +1.11,0.64, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.27,-10.86, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.34,-11.37, +4.69,-12.14, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.5,-12.37, +4.77,-13.11, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +5.35,-12.91, +6.51,-12.37, +6.94,-12.02, +6.88,-11.91, +6.43,-11.87, +6.09,-11.98, +4.64,-12.34, +4.51,-12.4, +4.48,-12.32, +4.41,-12.12, +5.08,-12.65, +7.78,-14.75, +9.22,-15.97, +9.11,-15.78, +9.16,-15.87, +8.95,-15.51, +8.94,-15.49, +8.82,-15.28, +8.66,-15, +8.79,-15.22, +9.61,-14.4, +11.07,-13.2, +11.1,-13.23, +11.2,-11.29, +10.97,-8.86, +9.52,-6.56, +7.69,-4.49, +7.46,-2.94, +8.01,-2.26, +7.91,0, +6.74,1.54, +6.15,2.58, +5.9,2.83, +6.05,2.54, +6.11,2.41, +6.22,2.59, +6.27,2.14, +4.74,1.15, +3.05,0.05, +3.38,-0.57, +4.49,-0.45, +5.59,-0.76, +4.82,-1.46, +4.05,-1.02, +2.14,-1.92, +-2.49,-5.1, +-4.5,-5.5, +-5.85,-2.43, +-5.83,-3.62, +-0.52,-10.33, +-3.71,-11.14, +-5.34,-8.01, +-1.98,-9.63, +-1.74,-10.13, +-1.47,-10.65, +-1.76,-9.16, +-3.64,-6.49, +-2.36,-7.78, +-0.17,-9.18, +-0.4,-8.14, +-0.62,-6.92, +-0.69,-8.47, +-1.51,-7.56, +-0.95,-5.39, +-0.88,-4.98, +-0.24,-5.03, +-0,-6.16, +0.32,-6.83, +1.2,-6.83, +0.41,-6.23, +0.72,-5.8, +2.38,-5.01, +3.55,-3.77, +3.93,-3.87, +5.15,-4.32, +5.8,-5.09, +4.84,-4.51, +6.02,-4.28, +7.44,-3.81, +7.09,-1.72, +6.77,-1.68, +6.05,-1.07, +6.92,-0.87, +6.2,-0.03, +3.95,-0.97, +3.72,0.27, +3.72,1.94, +4.58,2.67, +3.14,1.91, +2.65,1.87, +4.1,2.86, +7.28,2.2, +4.75,-0.35, +5.27,1.96, +0.76,0.64, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.42,-11.21, +5.65,-10.63, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.23,-10.31, +6.46,-10.67, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.38,-11.35, +5.01,-12, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.49,-12.34, +4.7,-12.9, +4.73,-12.99, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +5.89,-12.41, +6.76,-11.78, +6.64,-11.5, +6.6,-11.43, +6.51,-11.28, +6.46,-11.19, +6.33,-10.96, +5.62,-11.1, +4.28,-11.49, +4.11,-11.28, +4.81,-11.79, +8.1,-14.5, +8.67,-15.02, +8.53,-14.77, +8.42,-14.58, +8.18,-14.16, +8.15,-14.12, +7.97,-13.8, +7.62,-13.2, +7.48,-12.96, +8.04,-12.72, +9.62,-11.47, +9.97,-10.94, +10.75,-9.07, +10.81,-7.18, +10.28,-3.91, +8.55,-1.81, +7.5,-1.4, +7.44,-1.16, +7.7,0, +7.56,1.93, +6.31,3.47, +5.72,3.98, +5.77,3.91, +5.88,3.55, +6.78,3.92, +6.54,3.78, +4.93,2.49, +2.83,1.21, +1.48,0.61, +1.95,0.66, +3.68,1.52, +4.84,0.7, +4.85,-0.95, +0.73,-2.67, +4.19,-5.02, +0.52,-8.22, +-1.69,-8.57, +-5.68,-4.84, +-2.52,0.77, +-4.24,-5.37, +-5.98,-7.44, +-2.22,-8.71, +0.47,-9.6, +-0,-11.42, +-0,-10.33, +-1.16,-7.75, +0.04,-7.14, +-0.03,-7.97, +2.81,-8.47, +2.54,-9.01, +0.69,-8.57, +-0.96,-9.41, +-1.22,-6.92, +-0.98,-5.58, +-1.02,-5.77, +-0.13,-5.6, +-0,-7.12, +-0.17,-7.95, +-1.04,-7.64, +-0,-7, +0.17,-6.01, +0.92,-5.23, +1.43,-3.42, +2.66,-4.26, +3.8,-5.06, +5.12,-6.11, +4.74,-4.92, +5.15,-4.32, +7.25,-4.01, +7.29,-1.86, +6.31,-2.64, +5.75,-1.28, +6.55,-1, +6.12,-0.3, +3.74,-0.53, +3.92,0.89, +3.26,2.92, +3.5,4.38, +3.18,4.6, +2.74,3.65, +3.28,4.01, +4.77,-0.77, +4.32,-2.71, +5.11,2.77, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.32,-11.24, +5.91,-10.5, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.15,-10.36, +7.41,-9.49, +6.67,-10.36, +6.51,-11.28, +6.77,-11.12, +6.65,-11.19, +6.89,-11.03, +7.29,-10.75, +7.13,-10.86, +6.64,-11.2, +6.75,-11.13, +6.54,-11.26, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.34,-10.98, +6.16,-10.68, +6.37,-11.04, +6.22,-11.29, +4.9,-12.04, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.53,-12.44, +4.61,-12.67, +4.67,-12.84, +4.65,-12.79, +4.63,-12.72, +4.65,-12.79, +4.65,-12.79, +4.53,-12.45, +4.73,-12.41, +5.99,-11.72, +6.51,-11.28, +6.51,-11.28, +6.45,-11.17, +6.39,-11.06, +6.19,-10.72, +6,-10.39, +5.96,-10.32, +5.78,-10.01, +4.97,-9.97, +4.72,-10.66, +9.81,-13.4, +10.16,-12.86, +9.8,-12.82, +8.8,-12.64, +8.37,-13.02, +7.67,-12.68, +7.14,-12.36, +6.86,-11.88, +6.51,-11.28, +6.31,-10.5, +7.5,-9.41, +8.13,-8.42, +9.28,-7.43, +9.76,-5.47, +9.68,-2.57, +8.61,0.62, +7.3,1.88, +6.43,1.44, +6.72,0.92, +6.8,1.44, +6.5,3.35, +6.45,4.15, +6.03,4.6, +6.01,4.78, +6.35,4.29, +6.81,3.93, +6,3.34, +3.77,1.71, +1.17,0.37, +-0.22,0.75, +0.83,1.96, +3.19,3.02, +4.93,0.87, +4.16,-0.96, +2.76,-5.05, +4.38,-3.58, +-3.02,-1.76, +-3.55,-4.72, +-6.82,-4.87, +-5.03,0.56, +-4.99,-4.71, +-1.62,-6.04, +2.91,-7.21, +3.22,-7.72, +1.5,-10.25, +-1.58,-10.48, +0.54,-8.59, +4.07,-8.18, +2.63,-8.07, +2.5,-9.2, +0.83,-9.71, +0.24,-9.43, +-0.25,-10.25, +-0,-6.51, +-0,-7.86, +-0,-10.01, +-0,-9.53, +-0,-9.5, +-0,-9.43, +-0.75,-8.55, +-0.33,-8.05, +-0,-6.63, +-0,-5.33, +0.35,-5.16, +0.83,-4.83, +2.29,-5.53, +3.13,-5.73, +3.44,-5, +4.92,-4.55, +6,-5.03, +8,-3.97, +6.68,-2.43, +2.24,-1.68, +3.38,-0.96, +3.26,-0.66, +2.35,-0.41, +3.4,0.88, +2.61,3.57, +1.01,5.61, +0.69,5.74, +0.44,5.47, +-1.13,6.13, +-1.28,4.22, +0.8,-3.23, +5.44,2.15, +0.87,0.73, +0,0, +0,0, +-0.94,0.34, +0,0, +-3.58,2.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.92,-10.91, +1.92,-10.91, +3.66,-11.23, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.6,-11.13, +6,-10.45, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.38,-10.36, +6.2,-10.42, +6.08,-10.41, +7.3,-9.57, +7.75,-9.23, +7.05,-9.91, +7.88,-10.34, +8.37,-9.98, +8.33,-10.01, +8.37,-9.98, +8.37,-9.98, +8.35,-9.99, +8.37,-9.98, +8.32,-10.02, +7.9,-10.31, +8.01,-10.26, +7.18,-10.82, +6.71,-11.16, +6.51,-11.28, +6.51,-11.28, +6.32,-10.94, +6.04,-10.46, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.07,-10.63, +4.7,-11.59, +4.4,-12.1, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.69,-12.14, +6.19,-11.44, +6.51,-11.28, +6.45,-11.17, +6.25,-10.82, +6.15,-10.65, +6.03,-10.44, +5.96,-10.32, +5.69,-9.85, +6.13,-9.92, +7.53,-10.67, +9.18,-11.59, +10.16,-12.1, +10.01,-11.93, +9.55,-11.38, +9.26,-11.04, +8.76,-10.44, +8.45,-10.07, +8.01,-9.55, +7.19,-9.36, +6.09,-8.47, +5.59,-7.55, +5.82,-6.43, +6.51,-5.07, +7.99,-4.16, +8.8,-3.26, +9.21,-0.75, +8.31,1.2, +6.65,3.47, +5.9,4.21, +5.54,3.35, +5.56,2.74, +6.52,3.16, +6.6,4.21, +6.5,4.98, +6.67,4.08, +6.8,4.13, +6.6,4.41, +6.06,3.5, +4.06,1.89, +1.32,-0.38, +-0.56,0.02, +-1.35,0.38, +-0.47,2.38, +1.52,3.98, +3.85,2.55, +2.92,-3.54, +3.39,-4.72, +-4.51,-4.39, +-5.4,-4.75, +-6.46,-1.37, +-8.27,-1.03, +-4.94,-2.88, +-2.47,-6.42, +2.76,-6.23, +1.18,-5.69, +1.88,-3.2, +-1.54,-4.32, +0.96,-9.16, +4.32,-7.74, +4.78,-8.38, +4.83,-8.36, +4.8,-8.56, +3.48,-9.57, +2.67,-10.07, +0.46,-8.71, +0.52,-6.95, +0.69,-7.91, +1.05,-12.2, +-0,-10.91, +-0,-10.08, +0.25,-9.89, +0.22,-9.17, +0,-7.97, +0.43,-7.08, +0.39,-6.6, +-0,-6.16, +-0,-5.66, +1.09,-5.35, +2.12,-5.81, +3.55,-5.84, +4.06,-5.82, +5.48,-5.58, +7.67,-3.4, +6.09,-2.63, +1.97,-2.09, +1.36,-0.78, +1.45,-0.39, +3.37,-0.52, +4.75,0.86, +1.92,4.08, +-0.57,5.56, +-1.49,5.59, +-0.19,6.14, +-3.01,6.13, +-2.75,-0.76, +0.49,-1.97, +6.37,-1.27, +6.15,1.72, +-1.4,0.98, +-4.36,1.59, +-4.88,2.62, +0,0, +-6.83,4.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-11.08, +-0,-11.08, +1.58,-10.96, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.95,-10.91, +3.69,-11.23, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +5.3,-10.79, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.43,-10.36, +8.11,-10.01, +7.69,-10.1, +7.27,-9.64, +7.71,-9.27, +7.75,-9.23, +7.93,-9.46, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.27,-10.06, +7.59,-10.53, +6.59,-11.04, +6.05,-10.43, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +5.73,-10.58, +4.51,-11.27, +4.26,-11.71, +4.44,-12.19, +4.45,-12.24, +4.43,-12.18, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +5.25,-11.81, +6.35,-11.29, +6.31,-10.92, +6.24,-10.82, +6.05,-10.49, +6.03,-10.44, +5.92,-10.25, +5.73,-9.93, +5.53,-9.58, +7.84,-10.62, +9.86,-11.75, +9.86,-11.75, +9.62,-11.47, +9.23,-11, +9.24,-10.13, +9.42,-9.71, +9.26,-8.78, +8.34,-8.2, +8,-7.1, +7.63,-6.52, +6.63,-5.34, +6,-4.95, +5.77,-4.12, +5.86,-2.78, +6.24,-1.29, +7.65,-0.5, +8.74,0.67, +8.51,2.07, +6.48,4.12, +5.59,5.08, +4.89,5.32, +4.23,4.07, +3.69,3.86, +4.55,3.62, +5.91,4.15, +5.08,4.43, +5.75,4.32, +6.24,4.2, +4.82,4.53, +3.05,2.91, +1.04,0.4, +-1.68,-1.73, +-2.61,-0.66, +-2.25,0.25, +-1.88,2.35, +0.07,3.98, +1.38,2.13, +4.42,-2.7, +-1.87,-3.81, +-5.71,-3.45, +-4.79,-3.26, +-4.58,-0.75, +-6.04,-1.61, +-1.73,-0.57, +0.53,-6.83, +0.4,-6.53, +0.61,-5.14, +1.33,-1.98, +2.45,-4.35, +2.07,-8.95, +6.09,-6.99, +6.24,-6.99, +4.81,-7.37, +6.48,-5.7, +4.1,-8.32, +0.8,-7.93, +1.09,-7.32, +1.19,-7.74, +1.45,-8.21, +1.85,-9.24, +1.67,-9.45, +1.78,-10.09, +1.67,-9.49, +1.62,-9.17, +0.44,-8.07, +0.15,-7.11, +0.3,-6.73, +0.77,-6.1, +0.92,-6.08, +1.03,-5.81, +2.14,-5.89, +2.94,-6.48, +3.99,-6.9, +4.83,-5.76, +6.11,-3.84, +6.79,-3.19, +1.83,-1.92, +1.68,-0.97, +1.44,-0.52, +3.11,-0.92, +3.03,0, +0.87,2.09, +-1.62,3.48, +-3.64,3.7, +-5.01,2.79, +-6.46,0.33, +0.19,-0.69, +1.93,-3.37, +5.1,-4.32, +5.79,-3.45, +-4.73,-0.55, +-5.37,1.98, +-4.96,4, +-4.39,7.11, +-4.28,6.71, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-11.57, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +1.23,-10.99, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +3.38,-10.64, +3.96,-10.89, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +5.49,-10.71, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +7.15,-10.21, +8.37,-9.98, +8.36,-9.96, +8.08,-9.63, +7.75,-9.23, +7.75,-9.23, +7.87,-9.38, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.02,-9.79, +6.92,-9.83, +6.09,-10.4, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +5.9,-10.5, +4.43,-11.2, +4.14,-11.37, +4.14,-11.37, +4.12,-11.33, +4.24,-11.64, +4.24,-11.64, +4.25,-11.69, +4.3,-11.67, +5.02,-10.98, +5.88,-10.54, +6.05,-10.47, +6.03,-10.44, +6.03,-10.44, +6.02,-10.42, +5.85,-10.13, +5.63,-9.74, +5.54,-9.6, +8.05,-10.59, +9.41,-11.22, +9.31,-11.09, +9.07,-10.81, +9.24,-10.13, +9.61,-8.94, +9.51,-7.98, +8.95,-7.51, +8.36,-6.64, +8.19,-5.22, +7.54,-3.41, +7.22,-2.19, +6.84,-1.14, +5.93,-0.44, +5.91,-0.14, +5.65,0.8, +6.57,1.86, +7.28,2.12, +7.99,3.09, +7.45,3.79, +6.08,5.1, +4.8,5.99, +3.66,5.95, +3.35,3.25, +2.67,2.13, +3.61,3.87, +4.4,3.49, +3.22,3.45, +2.02,4.02, +1.95,2.72, +0.25,1.39, +-0.25,-1.19, +-2.84,-4.39, +-3.47,-3.88, +-5.68,-1.51, +-3.23,-0.22, +-3.15,1.13, +-2.54,3.39, +-1.41,2.25, +-0.06,0.29, +-3.46,-4.85, +-2.51,-3.99, +0.42,-3.58, +-2.87,0.67, +-7.07,-3.4, +-7.14,0.26, +0.77,-4.35, +0.81,-6.17, +0.97,-5.08, +2.18,-1.89, +0.52,-3.52, +2.49,-8.57, +8.26,-3.99, +7.06,-4.69, +6.12,-6.23, +6.23,-4.01, +3.35,-7.02, +1.08,-7.27, +1.49,-7.45, +1.89,-6.71, +2.64,-7.26, +2.11,-6.99, +1.36,-6.71, +1.53,-7.92, +1.55,-8.81, +1.49,-8.45, +1.19,-7.62, +0.84,-7.09, +1.04,-6.91, +1.24,-7.01, +1.26,-7.17, +1.7,-6.39, +2.19,-6.01, +3.26,-5.99, +4.21,-6.09, +4.91,-5.54, +5.02,-4.02, +3.95,-3.02, +1.41,-1.32, +1.75,-1.12, +2.15,-1.24, +2.84,-1.64, +3.5,-1.01, +1.86,1.66, +-2.23,2.23, +-4.67,0.39, +-5.08,-0.83, +-5.64,-4.15, +-2.27,-6.05, +3.28,-5.61, +4.86,-5.79, +3.85,-5.22, +-1.66,-4.16, +-5.81,2.52, +-3.25,6.91, +-0.8,8.87, +-0.7,7.7, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.96,-12.87, +-0.05,-12.83, +-0,-12.15, +-0,-12.05, +-0,-11.83, +-0,-11.24, +-0,-11.08, +-0,-11.08, +-0,-11.08, +0.19,-11.07, +1.71,-10.94, +1.92,-10.89, +1.92,-10.91, +1.92,-10.91, +1.95,-10.91, +3.24,-10.58, +3.8,-10.45, +4.07,-11.17, +4.12,-11.33, +4.12,-11.33, +4.27,-11.27, +5.63,-10.64, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.13,-10.37, +7.22,-10.01, +8.37,-9.98, +8.37,-9.98, +8.22,-9.79, +7.76,-9.25, +7.75,-9.23, +7.91,-9.43, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.36,-9.96, +7.87,-9.38, +7.75,-9.23, +7.41,-9.48, +6.55,-10.09, +6.04,-10.41, +5.9,-10.22, +5.71,-9.89, +5.76,-9.97, +5.89,-10.2, +5.74,-10.32, +4.68,-11.04, +4.12,-11.33, +4.16,-11.29, +4.14,-11.29, +4.12,-11.33, +4.86,-10.97, +5.62,-10.64, +6.02,-10.42, +5.9,-10.23, +5.93,-10.27, +5.84,-10.12, +5.82,-10.08, +5.65,-9.79, +5.54,-9.6, +5.54,-9.6, +5.51,-10.5, +9.23,-11, +9,-10.72, +8.76,-10.44, +9.36,-8.99, +9.66,-8.1, +9.07,-7.61, +8.52,-6.5, +8.46,-5.44, +7.97,-3.98, +7.29,-2.22, +7.08,-0.83, +6.72,1.33, +6.42,2.53, +6.25,3.05, +6,3.29, +5.87,3.26, +5.89,3.75, +6.23,3.14, +6.34,3.48, +6.2,4.35, +4.92,5.33, +3.4,6.08, +1.63,5.13, +0.59,1.93, +1.59,1.63, +0.92,3.58, +0.18,3.46, +-0.89,2.01, +-3.59,0.46, +-4.54,-0.19, +-4.49,-0.99, +-3.86,-3.58, +-2.96,-6.04, +-3.63,-5.27, +-5.36,-2.64, +-4.49,-2.75, +-4.31,-0.1, +-3.79,2.1, +-2.8,3.37, +-1.63,2.24, +-0.54,-2.58, +2.42,-4.89, +0.22,-4.56, +-3.87,-3.32, +-6.34,-1.67, +-5.85,-1.11, +-3.22,-2.25, +3.48,-5.55, +2.31,-5.66, +5.06,-2.72, +4.83,-2.79, +5.98,-6.9, +6.78,-4.83, +5.83,-1.72, +5.58,-6.72, +7.11,-2.75, +2.2,-9.05, +2.38,-7.54, +2.44,-6.69, +2.53,-6.51, +2.82,-6.11, +2.25,-6.17, +2,-6.01, +2.39,-6.58, +2.25,-6.76, +1.6,-7.07, +1.43,-7.21, +1.29,-7.32, +1.33,-7.53, +1.43,-7.21, +2.34,-7.02, +2.44,-6.71, +2.71,-6.43, +3.97,-6.18, +5.02,-5.98, +5.74,-5.49, +6.48,-5.44, +4.94,-3.12, +1.35,-0.97, +2.16,-1.25, +2.83,-2.04, +3.01,-2.29, +3.26,-1.43, +2.13,-0.67, +-1.64,-0.99, +-3.64,-2.6, +-3.33,-3.89, +-1.41,-4.52, +0.67,-5.48, +3,-5.19, +5.04,-6.01, +2.71,-5.36, +-0.53,-5.48, +-5.32,3.42, +-0.7,7.31, +1.24,7.03, +0.82,3.5, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.34,-13.27, +-2.22,-12.83, +-1.13,-12.95, +-0.11,-13.02, +-0,-12.34, +-0,-12.05, +-0,-12.05, +-0,-11.44, +-0,-11.08, +-0,-11.08, +-0,-11.08, +0.58,-11.05, +1.9,-10.77, +1.78,-10.12, +1.88,-10.68, +1.92,-10.91, +1.92,-10.91, +3.32,-10.56, +3.79,-10.41, +3.97,-10.9, +4.12,-11.33, +4.12,-11.33, +4.83,-11.02, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.22,-10.35, +8.2,-9.92, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.15,-9.72, +7.76,-9.25, +8.05,-9.59, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +7.82,-9.33, +7.75,-9.23, +7.75,-9.23, +7.55,-9.22, +6.4,-9.24, +5.55,-9.62, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.55,-9.62, +5.57,-9.64, +5.37,-9.78, +5.49,-9.62, +5.27,-9.87, +5.2,-9.84, +5.43,-9.75, +5.6,-9.71, +5.55,-9.62, +5.58,-9.66, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.35,-11, +7.07,-12.24, +9,-10.72, +8.79,-10.47, +9.06,-9.29, +9.34,-7.84, +8.86,-7.43, +8.7,-5.66, +8.21,-4.74, +7.78,-3.25, +7.15,-1.86, +6.39,-0.06, +5.89,2.17, +6.14,3.77, +6.19,4.57, +6.77,4.74, +7.01,4.05, +6.01,5.04, +5.06,4.57, +4.84,4.41, +4.83,4.25, +4.72,4.45, +3.34,4.8, +1.91,5.17, +0.79,2.54, +-1.69,0.95, +-1.82,0.73, +-2.71,0.64, +-3.87,-0.83, +-4.16,-1.71, +-3.76,-2.73, +-4.21,-3.79, +-3.59,-5.61, +-2.3,-7.39, +-1.04,-7.07, +-2.42,-6.14, +-4.19,-5.54, +-5.19,-5.65, +-5.52,-5.03, +-5.54,-2.02, +-4.23,1.95, +-2.41,2.88, +-1.09,-1.13, +-0.27,-5.16, +-4.65,-2.83, +-1.7,-3.59, +-3.05,-3.83, +-5.92,-1.98, +-4.92,-1.08, +5.61,-3.44, +2.98,-4.9, +5.72,-3.13, +4.94,-2.94, +7.26,-3.15, +5.83,-5, +6.75,-0.2, +3.09,-4.95, +6.33,-5.43, +3.53,-5.81, +3.07,-7.79, +3.57,-6.83, +3.13,-5.43, +3.16,-5.12, +2.43,-5.22, +1.98,-5.43, +2.71,-5.22, +2.48,-6, +2.39,-6.12, +2.14,-6.48, +1.96,-6.41, +2.39,-6.58, +2.56,-7.03, +2.68,-7.38, +3.42,-7.01, +3.76,-6.52, +4.48,-5.17, +5.24,-3.83, +6.25,-3.66, +6.78,-4.4, +5.69,-2.92, +2.02,-1.22, +2.12,-1.32, +3.36,-2.71, +3,-2.52, +2.46,-1.6, +1.85,-1.21, +0.44,-2.88, +-0.95,-4.08, +-0.96,-4.59, +0.23,-4.87, +1.73,-4.95, +4.48,-6.11, +4.23,-5.04, +3.21,-5.25, +1.73,-5.95, +3.52,6.16, +1.76,7.56, +1.97,5.4, +0.95,2.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.79,-13.15, +-2.73,-13.72, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.36,-13.38, +-1.42,-12.95, +-0.11,-13.02, +-0,-13.02, +-0,-12.77, +-0,-12.07, +-0,-12.05, +-0,-11.88, +-0,-11.1, +-0,-11.08, +-0,-11.08, +0.6,-11.05, +1.9,-10.77, +1.74,-9.85, +1.78,-10.1, +1.92,-10.88, +1.92,-10.91, +2.14,-10.87, +3.73,-10.43, +3.87,-10.64, +4.12,-11.33, +4.12,-11.33, +4.58,-11.13, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +7.18,-9.74, +8.28,-9.91, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.36,-9.96, +7.95,-9.47, +8.14,-9.7, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.34,-9.94, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.26,-8.66, +7.12,-8.49, +6.46,-8.97, +5.68,-9.5, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.53,-9.57, +5.43,-9.41, +5.47,-9.48, +5.4,-9.35, +5.47,-9.48, +5.45,-9.45, +5.44,-9.43, +5.53,-9.57, +5.62,-9.55, +6.53,-11.27, +7.21,-12.48, +7.29,-12.06, +8.37,-9.98, +8.95,-9.2, +9.23,-7.75, +8.47,-7.1, +8.43,-5.95, +8.14,-4.48, +7.84,-3.13, +7.24,-1.8, +6.54,-0.2, +5.77,1.47, +5.75,3.34, +6.23,4.71, +7.23,4.57, +7.38,4.46, +7.74,4.47, +6.82,4.74, +5.29,5.77, +4.5,5.36, +3.8,4.52, +3.17,4.6, +1.6,4.26, +0.05,4.32, +-0.15,1.69, +-2.02,-1.09, +-2.88,-2.1, +-3.49,-3.16, +-2.65,-4.17, +-2.16,-5.13, +-2,-6.08, +-2.24,-6.66, +-1.09,-7.68, +-0.19,-7.8, +0.28,-6.85, +-0.29,-6.09, +-2.21,-5.94, +-3.23,-6.16, +-4.44,-6, +-5.26,-5.46, +-6.21,-3.04, +-4.1,1, +-3.91,-1.3, +-2.51,-4.73, +-0.24,-3.86, +1.03,-2.69, +0.29,-3.56, +-5.44,-3.59, +-6.52,-0.06, +-1.67,0.17, +4.91,-4.28, +5.67,-3.56, +5.09,-2.09, +6.04,-0.49, +4.41,-2.2, +7.05,-3.15, +5.6,-4.95, +7.79,-4.61, +3.42,-7.39, +3.31,-7.37, +3.34,-5.78, +3.56,-6.17, +2.77,-4.8, +2.46,-4.26, +2.83,-5.17, +3.61,-4.07, +3.86,-4.21, +4.04,-4.27, +3.78,-5.25, +3.69,-5.78, +3.73,-5.78, +3.65,-6.22, +4.39,-6.03, +3.72,-6.7, +4.73,-5.13, +4.88,-3.42, +5.71,-1.8, +5.71,-1.89, +5.6,-2.71, +5.47,-2.98, +2.16,-1.25, +2.29,-1.6, +2.38,-2, +1.74,-1.37, +2,-1.15, +1.63,-1.05, +1.3,-1.8, +0.73,-3.31, +0.79,-4.84, +2.01,-5.08, +3.11,-5.39, +3.96,-4.53, +4.49,-4.34, +4.48,-4.8, +3.59,-4.78, +7.91,3.23, +2.45,7.31, +1.82,4.71, +1.26,2.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.08,-13.96, +-4.77,-13.22, +-3.06,-13.64, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-1.94,-13.11, +-0,-13.02, +-0,-13.02, +-0,-12.92, +-0,-12.05, +-0,-12.05, +-0,-12.01, +-0,-11.4, +-0,-11.08, +0.17,-11.08, +1.35,-10.98, +1.9,-10.76, +1.73,-9.79, +1.77,-10.05, +1.91,-10.85, +1.92,-10.91, +2.36,-10.81, +3.79,-10.41, +3.81,-10.46, +4.08,-11.21, +4.12,-11.33, +4.56,-11.14, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.23,-10.31, +7.49,-9.49, +8.29,-9.88, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.05,-9.6, +8.19,-9.76, +8.37,-9.98, +8.41,-9.94, +8.59,-9.78, +9.13,-9.24, +9.46,-8.9, +9.64,-8.72, +9.72,-8.65, +9.7,-8.66, +9.72,-8.64, +9.59,-8.78, +8.98,-9.39, +8.7,-9.67, +8.15,-9.66, +7.75,-9.23, +7.75,-9.23, +7.59,-9.04, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +6.48,-8.31, +5.38,-8.54, +5.04,-8.73, +5.03,-8.71, +5.03,-8.71, +5.06,-8.77, +5,-8.66, +5.07,-8.78, +5.01,-8.67, +4.96,-8.59, +4.96,-8.59, +4.96,-8.59, +4.96,-8.59, +4.96,-8.59, +4.97,-8.61, +5.4,-8.5, +6.66,-11.22, +7.07,-12.24, +7,-12.12, +7.28,-11.92, +8.29,-9.88, +9.14,-7.99, +8.59,-7.21, +7.93,-6.65, +8.06,-4.65, +7.58,-3.38, +7.65,-2.13, +6.94,-0.61, +6.52,0.48, +5.43,2.84, +5.57,4.44, +6.87,3.77, +7.04,3.3, +7.4,2.14, +8.3,2.84, +7.48,4.29, +6.2,4.98, +5.16,5.38, +3.88,5.08, +1.88,4.08, +0.41,3.36, +-1.13,2.46, +-2.12,1.27, +-1.59,-1.88, +-1.23,-3.7, +-1.4,-4.53, +-0.96,-5.59, +-0.78,-5.96, +-0.52,-6.95, +0.46,-7.05, +0.97,-7.62, +1.89,-6.71, +1.62,-6.03, +1.17,-5.88, +-0,-5.58, +-1.13,-5.52, +-2.81,-5.77, +-3.92,-5.62, +-5.64,-4.48, +-5.61,-3.09, +-2.45,-3.32, +2.64,-5.32, +2.43,-3.23, +3.37,-3.37, +4.34,-3.22, +-2.88,-4.38, +-6.9,0.82, +-3.82,3.21, +-0.44,-3.02, +1.4,-1.36, +4.74,-2.19, +6.51,-0.57, +6.22,-2.2, +4.69,-3.74, +5.04,-6.52, +6.28,-2.42, +3.9,-7.96, +3.81,-7.38, +3.82,-5.67, +3.15,-5.46, +3.22,-5.58, +2.8,-4.86, +4.22,-5.29, +4.32,-3.83, +4.25,-3.13, +4.34,-3.31, +3.7,-3.73, +3.73,-4.72, +4.89,-4.81, +6.01,-5.04, +4.52,-5.59, +2.55,-5.46, +5.49,-3.9, +5.77,-1.97, +5.54,-0.82, +5.14,-0.46, +5.06,-1.19, +4.64,-1.84, +2.22,-1.03, +1.84,-1.16, +1.89,-1.53, +2.62,-2.2, +3.1,-1.79, +2.14,-1.02, +1.58,-1.27, +1.88,-2.74, +1.57,-2.97, +2.25,-3.35, +2.79,-2.89, +2.77,-2.03, +4.74,-3.14, +5.28,-3.55, +6.37,-3.19, +7.64,3.52, +3.59,6.71, +2.96,4.97, +1.2,2.08, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.48,-12.96, +-7.25,-13.09, +-5.24,-14.02, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-4.73,-13.73, +-2.7,-13.73, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.33,-13.8, +-0.46,-13.67, +-0,-13.05, +-0,-13.02, +-0,-13, +-0,-12.5, +-0,-12.05, +-0,-12.05, +-0,-11.76, +0.07,-11.1, +1.2,-11, +1.92,-10.91, +1.89,-10.74, +1.74,-9.85, +1.74,-9.84, +1.81,-10.26, +1.92,-10.87, +2.21,-10.85, +3.72,-10.44, +3.79,-10.41, +3.93,-10.8, +4.12,-11.33, +4.65,-11.1, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.33,-10.24, +7.75,-9.23, +8.07,-9.62, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +7.9,-9.42, +8.14,-9.7, +8.58,-9.79, +9.55,-8.82, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.79,-8.21, +8.96,-8.07, +8.29,-8.7, +7.86,-9.13, +7.23,-8.62, +7.12,-8.49, +7.12,-8.49, +6.75,-8.04, +6.39,-7.61, +6.37,-7.59, +5.85,-7.98, +5.37,-8.31, +4.96,-8.59, +4.96,-8.59, +4.9,-8.48, +4.88,-8.46, +4.73,-8.19, +4.79,-8.29, +4.72,-8.18, +4.73,-8.19, +4.86,-8.42, +4.99,-8.51, +5.81,-8, +6.89,-11.37, +7,-12.12, +7.5,-11.77, +7.66,-11.65, +7.84,-11.24, +9.22,-8.15, +9.02,-7.57, +8.39,-7.04, +7.78,-5.63, +7.4,-4.07, +7.15,-2.12, +7.06,-0.89, +6.96,0.41, +6.64,2.05, +5.64,3.63, +4.21,3.76, +5.6,2.8, +6.28,1.85, +7.71,1.36, +8.09,1.43, +8.14,2.96, +6.87,3.97, +6.77,3.91, +4.42,3.61, +1.83,2.82, +-0.48,2.24, +-1.72,0.78, +0.15,-3.06, +0.22,-4.27, +0.69,-5.11, +0.89,-5.56, +1.38,-6.09, +1.48,-6.39, +1.66,-7.11, +2.03,-7.1, +2.24,-6.59, +2.96,-6.04, +2.74,-5.35, +2.95,-5.06, +1.91,-4.6, +1.27,-4.52, +-1.24,-5.28, +-3.72,-4.89, +-4.72,-3.96, +-5.39,-3.61, +-3.67,-1.71, +-2.17,-1.23, +0.67,-4.39, +-3.6,-2.63, +-4.56,-0.47, +-7.93,1.14, +-7.37,0.95, +-6.76,1.56, +-4.08,-2.63, +-6.09,0.78, +-4.06,1.25, +-1.97,2.48, +0.04,0.8, +2.67,-3.63, +2.53,-5.15, +5.48,-2.76, +1.96,-7.46, +2.81,-6.5, +4.12,-5.18, +3.88,-5.06, +4.37,-5.45, +3.87,-4.45, +4.85,-3.76, +5.12,-3.35, +5.15,-3.35, +4.55,-2.16, +4.46,-2.55, +4.3,-4, +5.11,-4.29, +6.01,-4.2, +5.33,-4.68, +5.27,-3.45, +6.92,-1.68, +8.48,-1.49, +5.82,-0.82, +6.03,0, +5.93,-0.87, +4.83,-1.23, +3.44,-0.96, +1.97,-0.69, +2.53,-1.28, +2.84,-2.16, +3.54,-2.04, +2.79,-1.02, +2.15,-0.96, +2.26,-1.58, +2.01,-1.55, +2.66,-2.57, +3.58,-2.54, +3.94,-1.69, +4.95,-1.72, +6.23,-2.3, +6.41,-1.79, +8.29,5.05, +3.64,6.01, +3.56,5.04, +2.02,2.4, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.41,-11.86, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7.02,-12.16, +-7.27,-12.59, +-7.22,-13.08, +-5.23,-14.02, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-4.05,-14.18, +-2.48,-14.08, +-2.48,-14.08, +-2.43,-13.81, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-1.86,-13.85, +-0.12,-13.99, +-0,-13.46, +-0,-13.02, +-0,-13.02, +-0,-12.76, +-0,-12.08, +-0,-12.05, +0.05,-12.05, +1.07,-11.18, +1.92,-10.91, +1.92,-10.91, +1.91,-10.83, +1.78,-10.1, +1.72,-9.76, +1.74,-9.86, +1.8,-10.21, +2.34,-10.7, +3.76,-10.4, +3.79,-10.41, +3.81,-10.48, +4.1,-11.26, +4.56,-11.14, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.44,-10.17, +7.73,-9.25, +7.9,-9.42, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.09,-9.64, +8.26,-9.75, +9.34,-9.02, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.38,-7.87, +9.23,-7.75, +9.23,-7.75, +8.95,-7.79, +7.71,-8.02, +7.23,-8.39, +6.92,-8.25, +6.41,-7.64, +6.37,-7.59, +6.35,-7.57, +6.33,-7.54, +6.14,-7.32, +5.73,-7.31, +5.33,-7.47, +5.06,-7.34, +4.63,-7.67, +4.64,-7.64, +4.51,-7.72, +4.47,-7.74, +4.47,-7.74, +4.76,-7.55, +6.18,-7.62, +7.87,-9.38, +9,-10.72, +9,-10.72, +9,-10.72, +9,-10.72, +8.91,-10.62, +9.14,-7.99, +8.95,-7.51, +8.49,-7.12, +7.32,-6.14, +6.93,-3.6, +6.77,-1.66, +7.48,0, +7.71,1.13, +7.16,2.79, +5.57,4.39, +4.86,3.74, +5.19,1.95, +6.57,1.16, +7.42,1.31, +7.85,1.38, +7.35,1.65, +7.23,2.04, +6.48,2.52, +6.35,1.83, +2.66,1.43, +0.47,-1.31, +0.64,-3.62, +1.61,-4.78, +2.07,-5.24, +2.84,-5.7, +3.03,-6.28, +2.95,-6.32, +2.95,-6.32, +3.03,-6.28, +3.26,-6.3, +3.71,-5.54, +4.53,-4.9, +4.88,-4.64, +4.37,-4.35, +4.3,-3.41, +3.83,-2.99, +2.07,-3.59, +-2.13,-5.28, +-4.64,-3.73, +-5.2,-2.9, +-4.39,-0.86, +-5.71,0.98, +-4.67,-3.64, +-5.76,-3.93, +-3.2,-2.5, +-6.36,1.09, +-6.64,-0.66, +-5.69,-0.39, +-5.92,-1.1, +-3.49,0.21, +-2.22,0.72, +-2.82,2.11, +-3.67,1.55, +-5.18,-0.73, +1.8,-0.41, +0.15,-2.89, +-0.95,-1.24, +0.38,-5.26, +3.19,-2.96, +4.5,-3.34, +4.91,-4.95, +5.49,-4.45, +5.91,-1.69, +5.89,-2.49, +5.88,-2.74, +4.95,-1.51, +4.85,-1.36, +4.64,-3.41, +5.08,-4.26, +6.06,-3.5, +6.63,-2.6, +7.09,-1.72, +6.89,-1.22, +8.41,-1.11, +7.79,-0.52, +5.6,0.13, +5.85,-0.42, +5.77,-1.02, +3.33,-0.3, +2.58,-0.08, +2.73,-1.13, +4.12,-2.48, +3.54,-2.04, +2.37,-0.93, +2.28,-0.83, +3.02,-1.18, +3.11,-1.39, +2.27,-0.97, +3.53,-0.78, +4.57,-0.55, +5.01,-0.5, +5.11,-0.79, +6.76,0.86, +6.17,5.3, +4.07,5.47, +4.52,3.33, +1.4,1.14, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.48,-11.11, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7.01,-12.14, +-7.04,-12.71, +-5.3,-13.99, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-3.74,-14.46, +-2.58,-14.64, +-2.51,-14.23, +-2.51,-14.22, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-1.01,-13.94, +-0,-14, +-0,-13.78, +-0,-13.07, +-0,-13.02, +-0,-13, +-0,-12.32, +-0,-12.05, +0.73,-11.99, +2,-11.64, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.83,-10.39, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +2.92,-9.75, +3.78,-10.39, +3.79,-10.41, +3.79,-10.41, +3.97,-10.82, +5.57,-10.68, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.05,-10.43, +7.39,-9.51, +7.75,-9.23, +8.06,-9.6, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.06,-9.24, +9.36,-8.42, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.94,-8.34, +9.4,-7.89, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +8.62,-7.23, +8.49,-7.12, +8.25,-7.33, +7.01,-7.24, +6.52,-7.47, +6.37,-7.59, +6,-7.15, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.67,-6.76, +5.45,-6.56, +5.37,-6.73, +5.32,-6.86, +6.59,-8.47, +6.87,-8.19, +8.51,-10.14, +9,-10.72, +9,-10.72, +9,-10.72, +9,-10.72, +8.91,-10.62, +8.72,-9.63, +9.23,-7.75, +9.09,-7.31, +8.8,-5.91, +7.84,-4.81, +7.26,-2.97, +7.59,-0.69, +8.18,0.37, +8.58,1.34, +7.87,3.55, +5.95,4.79, +4.83,4.05, +5.37,2.36, +6.77,1.19, +7.26,0.66, +7.17,0.8, +6.82,0.61, +6.4,0.5, +6.55,0.67, +6.28,0.27, +4.07,-1.5, +2.69,-3.51, +2.72,-4.41, +2.73,-4.95, +3.51,-5.38, +4.21,-5.56, +4.12,-5.62, +4.17,-5.59, +4.25,-5.54, +4.23,-5.35, +4.42,-4.81, +5.06,-4.39, +6.11,-3.33, +6.22,-2.74, +5.65,-2.89, +5.98,-1.83, +6.11,-2.18, +3.99,-3.86, +-1.86,-5.53, +-4.29,-3.97, +-5.51,-2.01, +-6.26,-2.05, +-5.7,-0.04, +-3.96,-4.15, +-4.26,-5.65, +-5,-2.46, +-5.96,-0.81, +-5.99,0.17, +-3.74,-1.85, +-1.27,-2.86, +3.13,0.44, +-1.2,-0.51, +-2.59,0.32, +-4.72,0.78, +-3.81,0.69, +-3.62,3.61, +-5.61,-0.74, +-4.51,0.16, +-1.68,0.4, +0.3,-4.94, +3.44,-3.83, +6.67,-2.27, +7.28,-1.95, +6.94,1.46, +6.6,1.02, +6.52,-1.55, +6.21,-0.68, +5.49,-0.59, +5.57,-2.19, +5.54,-2.9, +5.96,-2.57, +6.11,-2.44, +6.81,-1.81, +6.09,-1.07, +6.3,-1, +5.98,-0.5, +6.22,0.52, +5.68,0.21, +5.4,-0.5, +3.21,0.39, +3.18,0.63, +3.48,-0.45, +4.49,-2.22, +4.09,-2.05, +2.13,-0.78, +2.62,-0.51, +3.5,-0.72, +2.61,-0.53, +2.46,-0.34, +3.39,-0.14, +4.29,0.4, +5.53,0.62, +5.68,0.47, +5.72,1.31, +4.27,3.92, +4.38,5.47, +5.04,3.03, +3.15,1.62, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.11,-11.37, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.78,-12.3, +-5.17,-13.65, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-4.37,-14.29, +-2.6,-14.74, +-2.57,-14.57, +-2.55,-14.46, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-1.28,-13.9, +-0,-14, +-0,-13.95, +-0,-13.36, +-0,-13.02, +-0,-13.02, +-0,-12.68, +0.24,-12.04, +1.34,-11.96, +2.07,-11.76, +1.95,-11.03, +1.92,-10.91, +1.92,-10.91, +1.87,-10.6, +1.74,-9.87, +1.72,-9.76, +1.72,-9.76, +3.13,-9.6, +3.67,-10.09, +3.79,-10.41, +3.79,-10.41, +3.97,-10.61, +5.73,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.07,-10.41, +7.35,-9.54, +7.75,-9.23, +7.8,-9.3, +8.27,-9.86, +8.37,-9.98, +8.29,-9.88, +8.03,-9.57, +8.3,-9.15, +9.65,-8.25, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.87,-8.28, +9.72,-8.16, +9.4,-7.89, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9,-7.55, +8.49,-7.12, +8.47,-7.1, +8.15,-6.84, +7.59,-6.37, +7.48,-6.5, +6.82,-6.55, +6.01,-6.61, +5.78,-6.82, +5.75,-6.85, +5.75,-6.85, +5.54,-6.6, +5.28,-6.3, +5.14,-6.12, +5.3,-6.75, +6.71,-8.5, +7.75,-10.41, +8.37,-9.98, +8.68,-10.35, +8.84,-10.54, +8.82,-10.51, +9,-10.72, +9.24,-10.48, +9.57,-9.98, +10.04,-9.16, +9.75,-8.6, +9.23,-7.75, +9.76,-5.94, +9.16,-4.18, +8.08,-2.61, +6.79,-2.07, +7.3,-1.89, +9,-0.89, +9.86,0.65, +8.59,3.86, +6.45,5.12, +5.08,3.96, +6.38,2.81, +6.77,1.02, +6.1,0.12, +6.07,-0.74, +5.86,-1.17, +5.96,-0.78, +6.51,-0.84, +5.97,-1.76, +4.83,-2.81, +3.98,-3.82, +3.47,-4.31, +3.63,-4.79, +4.36,-5.2, +4.62,-5.07, +4.49,-4.34, +4.53,-4.11, +5.01,-3.79, +5.39,-3.07, +6,-2.37, +6.23,-1.95, +6.18,-1.15, +6.21,-0.18, +6.54,0.26, +6.84,-0.13, +6.81,-0.52, +5.04,-3.28, +-3.31,-5.38, +-5.52,-3.39, +-5.94,-2.16, +-5.56,-2.23, +-5.88,-0.65, +-3.64,-1.38, +-4.84,-6.21, +-6.28,-3.47, +-4.88,-0.57, +-4.5,-0.58, +-1.84,-0.91, +2.43,-2.3, +0.31,-1.82, +-1.52,0.11, +0.07,-2.73, +0.04,-2.18, +-0.55,0.23, +-0.44,2.83, +-1.49,-1.44, +-4.46,-0.39, +-4.48,0.81, +-0.83,-4.79, +0.99,-5.62, +1.31,-5.76, +3.94,0.81, +-3.32,2.41, +-2.74,2.48, +0.67,3.18, +2.03,2.62, +1.89,2.45, +2.16,0.69, +5.72,-0.79, +5.82,-0.75, +5,-1.23, +6.67,-1.96, +6.89,-2.45, +7,-1.12, +8.11,0, +7.81,0, +6.57,0.15, +4.85,-0.73, +3.66,0.75, +2.11,0.67, +4.32,-0.66, +9.29,-3.41, +6.26,-2.81, +2.33,-0.65, +2.08,-0.31, +3.09,-0.25, +2.68,-0.19, +3.34,0.15, +3.81,0.62, +4.55,1.22, +5.51,1.24, +4.8,1.21, +4.34,1.64, +3.2,3.03, +2.76,2.78, +4.52,3.65, +2.91,1.89, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.19,-11.32, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.81,-12.22, +-4.98,-13.45, +-5.11,-14.04, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-3.87,-14.43, +-2.6,-14.74, +-2.6,-14.74, +-2.54,-14.43, +-2.43,-13.78, +-2.43,-13.78, +-2.37,-13.79, +-1.07,-13.94, +-0,-14, +-0,-14, +-0,-13.65, +-0,-13.05, +-0,-13.02, +0.08,-12.94, +1.07,-12.35, +2.05,-11.88, +2.09,-11.87, +2.01,-11.42, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.85,-10.5, +1.72,-9.76, +1.72,-9.76, +2.94,-9.46, +3.61,-9.92, +3.79,-10.41, +3.79,-10.41, +3.86,-10.39, +5.63,-10.35, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.05,-10.42, +7.53,-9.4, +7.75,-9.23, +7.75,-9.23, +8.19,-9.76, +8.23,-9.81, +7.79,-9.29, +8.06,-8.95, +9.14,-7.85, +9.73,-8.16, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.92,-8.32, +9.51,-7.98, +9.27,-7.78, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.21,-7.73, +8.59,-7.21, +8.49,-7.12, +8.41,-7.06, +7.61,-6.39, +7.59,-6.37, +7.45,-6.25, +6.85,-5.75, +6.84,-5.76, +6.73,-5.87, +6.29,-6.17, +5.47,-6.11, +5.12,-6.11, +5.12,-6.11, +5.53,-6.59, +7.97,-9.49, +8.37,-9.98, +8.37,-9.98, +8.44,-10.06, +8.64,-10.3, +8.45,-10.07, +8.55,-9.8, +9.96,-8.85, +10.64,-8.93, +10.72,-9, +10.72,-9, +10.23,-8.58, +9.85,-6.38, +9.51,-4.26, +8.42,-1.37, +6.78,-0.87, +5.31,-2.24, +6.91,-4.26, +8.41,-2.6, +9.83,0, +9.51,2.68, +7.32,4.65, +6.23,5.23, +5.8,3.87, +5.56,0.79, +4.8,-1.59, +5.1,-2.11, +5.47,-2.47, +5.64,-2.05, +6.02,-1.66, +5.44,-2.48, +5.13,-3.14, +3.87,-3.44, +4.07,-4.07, +4.34,-4.15, +4.99,-4.19, +4.95,-3.81, +4.96,-3.38, +5.32,-3.07, +5.56,-2.16, +6.34,-1.27, +6.56,0.02, +6.19,0.65, +5.51,0.87, +4.33,1.63, +3.32,3.27, +4.99,2.44, +4.63,-1.08, +-0.24,-3.98, +-3.48,-5.01, +-4.08,-3.39, +-4.98,-2.53, +-4.61,-2.51, +-5.52,-1.71, +-5.63,0.96, +-7.2,-3.9, +-6.59,-2.63, +-4.47,-0.31, +-2.28,-0.05, +-2.95,-0.7, +-1.64,2.18, +-3.39,-1.57, +-2.04,1.28, +1.4,-1.78, +0.01,-2.18, +-1.96,0.05, +-1.44,1.81, +-0.64,0.15, +-4.44,-2.44, +-3.77,0.1, +-0.88,-4.4, +-0.2,-5.24, +-2.51,-5.39, +-6.76,-0.67, +-6.96,0.41, +-6.98,0.17, +-7.05,0.45, +-6.78,2, +-7.24,2.87, +-6.83,2.93, +-3.77,3.79, +-0.45,2.41, +6.69,1.27, +4.85,-2.05, +5.18,-1.84, +5.69,-0.17, +4.7,1.34, +7.95,-0.17, +7.04,-1.09, +5.39,-0.68, +3.32,0.86, +1.87,0.52, +5.04,-0.59, +5.46,-1.67, +2.88,-1.05, +2.6,-0.68, +3.64,0.09, +4.47,0.57, +4.53,0.17, +3.8,0.67, +4.69,1.38, +4.76,1.26, +6.53,1.33, +3.34,0.47, +4.12,1.61, +3.03,3.03, +1.22,1.69, +6.23,5.22, +6.47,2.31, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.97,-9.8, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.14,-11.34, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.81,-12.22, +-4.97,-13.1, +-5.04,-13.84, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-4.09,-14.37, +-2.6,-14.74, +-2.6,-14.74, +-2.55,-14.48, +-2.43,-13.8, +-2.47,-14, +-2.16,-13.82, +-0.03,-14, +-0,-14, +-0,-14, +-0,-14, +-0,-13.22, +-0,-13.02, +0.82,-12.97, +2.18,-12.36, +2.09,-11.87, +2.09,-11.87, +2.07,-11.74, +1.93,-10.97, +1.92,-10.91, +1.92,-10.91, +1.87,-10.61, +1.73,-9.79, +1.74,-9.76, +3.21,-9.38, +3.55,-9.74, +3.75,-10.29, +3.79,-10.41, +3.86,-10.39, +5.47,-9.91, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.36,-10.22, +7.53,-9.41, +7.75,-9.23, +7.75,-9.23, +7.76,-9.25, +7.75,-9.23, +7.75,-9.23, +7.95,-9.04, +9.18,-7.81, +9.41,-7.9, +9.94,-8.34, +9.98,-8.37, +9.98,-8.37, +9.7,-8.14, +9.51,-7.98, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.21,-7.73, +8.69,-7.29, +8.49,-7.12, +8.49,-7.12, +7.91,-6.64, +7.59,-6.37, +7.59,-6.37, +7.13,-5.98, +6.85,-5.75, +6.85,-5.75, +6.72,-5.64, +6.14,-5.15, +6.11,-5.12, +5.88,-5.36, +5.69,-6.57, +7.56,-9.01, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.57,-9.78, +9.75,-8.6, +9.98,-8.37, +10.35,-8.68, +10.9,-8.75, +11.52,-7.85, +11.56,-7.08, +10.08,-5.02, +9.11,-1.88, +7.36,0.67, +4.02,-0.74, +3.97,-2.32, +6.13,-4.12, +8.88,-3.99, +9.64,-1.26, +9.43,1.9, +8.44,4.13, +6.19,5, +3.82,3.69, +3.47,0.27, +2.54,-4.02, +3.7,-5.22, +4.99,-4.39, +5.86,-3.78, +5.85,-3.38, +5.43,-3.13, +5.1,-2.94, +4.32,-2.99, +4.27,-2.93, +4.75,-3.05, +5.27,-2.89, +5.41,-2.61, +5.44,-2.54, +5.7,-1.92, +5.95,-0.65, +6.37,1.01, +5.97,2.17, +3.87,2.17, +3.27,3.27, +2.71,2.66, +-1.37,3.23, +0.66,4.58, +2.5,-0.9, +-2.24,-3.7, +-1.59,-3.92, +-3.63,-3.45, +-3.8,-2.05, +-3.86,-2.49, +-5.14,-3.09, +-5.57,-2.79, +-6.72,-1.53, +-4.79,-1.71, +-2.28,-0.46, +-2.34,0.49, +-2.45,-0.44, +0.1,-0.21, +-1.37,-1.29, +-0.24,1.59, +-0.51,-0.6, +0.6,-1.89, +-2.99,-2.32, +-2.97,1.6, +-1.05,1.38, +-6.28,-1.8, +-3.19,0.36, +-1.37,-3.77, +-0.75,-4.26, +-2.06,-5.15, +-5.06,-4.14, +-6.83,-2.73, +-6.91,-0.2, +-6.67,0.14, +-7.06,-0.15, +-7.13,0.67, +-6.94,0.61, +-6.67,1.94, +-2.13,4.51, +1.72,1.93, +2.49,-0.88, +4.32,-0.99, +-4.1,2.42, +-3.79,5.07, +2.47,3.96, +7.47,-0.19, +4.25,3.01, +4.17,1.29, +1.56,0.42, +2.08,0.05, +5.97,-0.35, +3.11,-0.85, +3.39,-0.14, +4.97,0.67, +5.34,1.91, +4.56,0.94, +4.67,1.49, +4.57,1.66, +7.22,3.31, +6.46,1.36, +2.95,1.35, +3.51,1.54, +2.94,3.31, +2.9,3.59, +6.28,6.79, +6.53,4.51, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.18,-10.56, +-9.02,-10.71, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.36,-11.2, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.71,-12.27, +-4.87,-13.12, +-5.05,-13.88, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-4.79,-14.17, +-3.11,-14.62, +-2.6,-14.74, +-2.6,-14.74, +-2.56,-14.53, +-2.47,-14.02, +-2.54,-14.41, +-1.97,-13.84, +-0.07,-13.99, +-0,-14, +-0,-14, +-0,-13.97, +-0,-13.35, +-0,-13.02, +1.38,-12.93, +2.25,-12.78, +2.12,-12.01, +2.09,-11.87, +2.09,-11.87, +2.01,-11.4, +1.92,-10.91, +1.92,-10.91, +1.91,-10.84, +1.79,-10.17, +2.19,-9.66, +3.18,-9.38, +3.41,-9.36, +3.73,-10.25, +3.79,-10.41, +3.85,-10.39, +5.3,-9.75, +5.9,-10.21, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.85,-9.88, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.76,-9.22, +8.83,-8.19, +9.23,-7.75, +9.23,-7.75, +9.38,-7.87, +9.7,-8.14, +9.34,-7.84, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.21,-7.73, +8.62,-7.2, +8.62,-6.95, +8.71,-6.81, +8.28,-6.63, +7.7,-6.27, +7.59,-6.37, +7.3,-6.12, +6.85,-5.75, +6.85,-5.75, +6.81,-5.72, +6.16,-5.17, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.97,-7.31, +8.06,-9.6, +8.29,-9.88, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.28,-9.87, +9.78,-8.57, +9.98,-8.37, +9.98,-8.37, +10.29,-8.3, +11.8,-7.21, +11.7,-6.75, +11.08,-5.51, +9.83,-2.07, +7.35,0.33, +4.91,2.29, +3.48,-0.42, +3.55,-3.52, +6.26,-4.19, +8.38,-3.62, +8.55,-1.98, +9.25,-0.17, +8.56,2.47, +6.34,4.06, +1.77,2.28, +-1.51,-1.27, +0.54,-4.95, +3.37,-5.23, +4.87,-4.99, +5.52,-4.06, +5.82,-3.36, +5.22,-3.01, +4.89,-2.57, +4.56,-2.13, +4.66,-1.93, +5.18,-1.73, +5.54,-1.6, +5.8,-1.55, +5.82,-1.48, +5.89,-0.33, +6.15,0.15, +6.32,1.75, +5.45,2.9, +2.06,1.74, +-2.94,1.57, +-4.64,1.53, +-4.26,2.84, +-1.67,4.47, +-1.52,1.08, +-2.17,-1.52, +-0.88,-3.41, +-2.94,-2.22, +-2.15,-1.37, +-4.06,-2.3, +-3.58,-3.18, +-2.71,-4.08, +-3.05,-2.04, +-3.77,-0.27, +-3.11,2.3, +-2.38,1.88, +-0.35,1.67, +1.49,0.68, +-1.78,-0.91, +-1.1,1.42, +-0.49,1.55, +2.48,1.12, +-6.62,-3.03, +-4.28,2.45, +-0.86,3.45, +-7.18,1.84, +-2.21,3.79, +-2.31,-3.1, +-1.38,-4.3, +-1.62,-5.41, +-2.95,-6.85, +-4.06,-6.7, +-4.87,-5.16, +-5.09,-4.66, +-6.08,-3.33, +-6.76,-1.72, +-6.72,-0.86, +-6.24,0.47, +-6.63,1.5, +-7.38,2, +-5.95,-0.24, +-5.31,-0.09, +-4.16,3.93, +-2.66,5.2, +-0.52,6.54, +2.44,6.29, +2.76,5.7, +3.86,4.79, +2.54,3.86, +2.24,5.71, +1.11,4.76, +2.26,2.91, +2.2,1.84, +3.32,4.55, +5.6,2.45, +6.39,1.91, +2.56,0.8, +4.99,2.07, +9.42,3.75, +7.17,1.9, +5,1.69, +7.07,4.14, +1.41,1.35, +3.27,5.2, +4.76,7.16, +5.41,4.21, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.19,-11.31, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.91,-12.17, +-5.01,-13.06, +-4.84,-13.29, +-5.1,-14.02, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-4.87,-14.15, +-2.77,-14.71, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.72, +-2.59,-14.69, +-2.55,-14.49, +-1.99,-14.16, +-0.03,-14, +-0,-14, +-0,-14, +-0,-14, +-0,-13.75, +0.09,-13.02, +2.11,-12.85, +2.26,-12.83, +2.17,-12.33, +2.09,-11.87, +2.09,-11.87, +2.03,-11.51, +1.94,-11.02, +1.92,-10.91, +1.92,-10.91, +1.82,-10.34, +2.55,-9.61, +3.39,-9.32, +3.4,-9.34, +3.53,-9.71, +3.79,-10.41, +3.86,-10.39, +5.24,-9.75, +5.75,-9.95, +6,-10.39, +6.03,-10.44, +6.03,-10.44, +6.55,-10.09, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.85,-8.16, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.46,-7.43, +9.87,-6.82, +9.5,-6.07, +9.51,-5.68, +9.57,-5.58, +9.45,-5.45, +8.64,-4.99, +8.53,-5.05, +8.38,-5.18, +7.72,-4.69, +7.63,-4.64, +7.57,-4.69, +6.86,-4.45, +6.2,-5, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +7.75,-9.23, +7.75,-9.23, +7.82,-9.33, +8.1,-9.66, +8.37,-9.98, +8.27,-9.85, +8.46,-8.86, +9.45,-7.93, +9.88,-8.29, +9.98,-8.37, +10.35,-7.84, +11.28,-6.51, +11.53,-5.4, +11.19,-4.07, +8.29,1.53, +6.82,2.85, +4.88,2.03, +3.29,-0.66, +4.3,-2.3, +6.83,-2.51, +7.23,-2.63, +7.88,-2.87, +8.19,-1.42, +4.96,-0.97, +4.26,0.87, +0.15,-1.21, +-0.54,-3.41, +1.95,-5.21, +3.71,-5.03, +5.25,-4.61, +5.56,-4.21, +5.41,-3.12, +5.54,-2.33, +4.86,-1.77, +4.83,-1.41, +4.85,-1.36, +5.81,-1.03, +5.96,-0.75, +6.21,-0.65, +6.27,0, +6.77,1.02, +6.39,1.7, +6.69,2.8, +4.16,3.27, +-0.92,0.72, +-4.22,-0.9, +-4.42,0.93, +-4.52,1.6, +-3.88,4.38, +-4.38,1.59, +-4.65,-1.03, +-4.05,-0.96, +-3.62,-1.51, +-3.87,-1.6, +-3.79,-1.23, +-3.1,-2.14, +-2.83,-1.98, +-2.99,-1.54, +-4.57,-0.42, +-4.13,1.58, +-0.85,1.64, +-1.31,2.71, +-1.86,-0.13, +-1.65,-0.37, +-0.1,-0.03, +-1.88,-1.19, +-3.5,-0.77, +-6.04,-1.78, +-5.56,0.06, +-4.84,3.07, +-5.43,3.68, +-3.29,2.57, +-3.72,-3.4, +-2.72,-4.21, +-2.23,-5.55, +-1.17,-7.63, +-0.69,-7.91, +-1.37,-7.79, +-2.55,-6.42, +-4.04,-5.61, +-6.01,-3.47, +-6.23,-1.44, +-5.47,-1.3, +-5.92,-0.69, +-6.4,-0.47, +-5.63,-1.27, +-5.09,-0.33, +-2.81,4.73, +-1,5.78, +-0.87,5.95, +-0.41,6.96, +0.69,7.19, +0.51,7.27, +0.66,6.38, +0.18,6.64, +-0.87,5.56, +-2.42,5.07, +0.17,5.92, +1.04,7.02, +3.42,6.71, +6.09,2.11, +2.27,0.51, +4.2,1.8, +6.75,3.22, +11.08,4.85, +3.67,1.58, +8.62,3.98, +4.64,3.56, +4.81,6.19, +6.31,9.83, +6.55,7.64, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.95,-10.67, +-8.59,-10.24, +-8.58,-10.22, +-8.51,-10.14, +-8.86,-10.56, +-8.14,-11.35, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.81,-12.22, +-4.94,-13.09, +-4.81,-13.2, +-5.01,-13.77, +-5.12,-14.07, +-5.12,-14.07, +-5.12,-14.07, +-4.75,-14.18, +-2.68,-14.72, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.59,-14.72, +-1.31,-14.42, +-0,-14, +-0,-14, +-0,-14, +-0,-14, +-0,-13.97, +0.73,-13.33, +2.19,-12.84, +2.26,-12.83, +2.24,-12.69, +2.12,-12, +2.09,-11.87, +2.09,-11.87, +1.98,-11.23, +1.92,-10.91, +1.92,-10.91, +2.18,-10.73, +2.88,-9.65, +3.39,-9.32, +3.39,-9.32, +3.44,-9.46, +3.7,-10.18, +3.83,-10.39, +5.39,-9.68, +5.58,-9.66, +5.98,-10.36, +6.03,-10.44, +6.05,-10.43, +7.03,-9.76, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.15,-8.85, +9.17,-7.81, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.67,-7.15, +10.31,-6.23, +10.42,-6.01, +9.87,-5.7, +9.6,-5.54, +9.6,-5.54, +9.57,-5.53, +8.84,-5.1, +8.59,-4.96, +8.59,-4.96, +7.93,-4.58, +7.74,-4.47, +7.74,-4.47, +7.41,-4.28, +6.9,-3.98, +6.82,-4.12, +6.4,-4.71, +6.11,-5.12, +7.28,-8.06, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.82,-9.33, +7.75,-9.23, +7.75,-9.23, +8.74,-8.24, +9.23,-7.75, +9.23,-7.75, +9.55,-8.01, +10.31,-6.78, +10.43,-5.39, +10.37,-3.77, +7.96,2.84, +5.21,5.63, +6.59,2.87, +4.98,1.41, +4.9,-0.11, +6.1,-0.77, +6.88,-1.04, +6.12,-2.62, +5.54,-3.46, +3.87,-4.29, +0.53,-5.15, +2.19,-5.18, +2.33,-4.3, +1.65,-3.83, +3,-4.66, +4.69,-4.78, +5.36,-4.5, +5.8,-3.87, +5.71,-2.95, +5.66,-2.06, +5.32,-1.22, +5.39,-0.95, +5.81,-1.03, +5.85,-0.42, +6.67,0, +7,0, +6.97,0.3, +6.76,1.72, +6.16,2.96, +5.71,4, +3.65,2.11, +-2.81,-1.25, +-4.06,-0.37, +-3.37,0.88, +-4.87,0.48, +-6.24,2.07, +-7,0, +-6.1,-0.5, +-5.38,-0.8, +-4.52,-1.18, +-4.75,-1.73, +-3.37,0.51, +-4.36,-2.36, +-4.36,-0.74, +-4.03,-1.02, +-4.73,-0.02, +-2.96,1.89, +-2.46,2.14, +-1.62,2.69, +-2.05,-0.45, +-1.03,0.7, +1.65,-0.08, +-0.1,-3.83, +-0.7,-5.12, +-4.09,-4.73, +-5.4,-1.52, +-4.12,0.55, +-3.91,3.13, +-3.04,3.15, +-4.08,-2.16, +-3.24,-3.86, +-3.11,-5.85, +-1.86,-7.43, +-0,-7.97, +-0.17,-7.71, +-0.3,-6.97, +-2.75,-6.37, +-4.51,-4.97, +-5.13,-2.96, +-4.69,-2.43, +-5,-1.84, +-5.6,-1.91, +-4.48,-2.16, +-4.6,1.11, +-2.44,4.82, +-0.25,5.73, +-0.52,5.98, +-0.84,7.41, +-1.04,7.88, +-1,7.92, +-1.49,7.07, +-2.8,6.42, +-3.11,5.5, +-3.83,5.61, +-2.39,6.12, +0.4,7.05, +2.08,6.8, +4.48,4.33, +2.62,1.74, +1.03,4.35, +3.5,2.34, +9.52,4.46, +5.62,2.31, +9,5.03, +8.85,6.75, +6.58,7.27, +5.61,7.73, +3.19,5.03, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.98,-10.71, +-8.56,-10.2, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.47,-10.09, +-8.01,-11.14, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.93,-12.16, +-6.09,-12.57, +-4.79,-13.15, +-4.89,-13.43, +-5.11,-14.04, +-5.12,-14.07, +-5.12,-14.07, +-4.87,-14.15, +-2.7,-14.72, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.59,-14.68, +-2.53,-14.33, +-0.53,-14.16, +-0,-14, +-0,-14, +-0,-14, +-0,-14, +-0,-14, +1.53,-13.44, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.14,-12.15, +2.09,-11.87, +2.09,-11.87, +2.05,-11.63, +1.93,-10.94, +1.92,-10.91, +2.96,-10.63, +3.59,-9.86, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.47,-9.54, +3.84,-10.34, +5.24,-9.75, +5.55,-9.61, +5.62,-9.74, +5.98,-10.36, +6.1,-10.39, +7.39,-9.51, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.32,-8.69, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +10.02,-6.65, +10.44,-6.03, +10.42,-6.02, +9.76,-5.64, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +9.03,-5.22, +8.59,-4.96, +8.59,-4.96, +8.23,-4.75, +7.77,-4.48, +7.74,-4.47, +7.74,-4.47, +7.03,-4.06, +6.9,-3.98, +6.9,-3.98, +6.9,-3.98, +6.56,-4.29, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.25,-8.42, +8.81,-7.39, +8.95,-7.51, +9.23,-7.75, +9.35,-7.1, +8.89,-4.71, +8.46,-0.63, +5.82,5.05, +3.82,7.35, +6.35,2.98, +6.11,1.21, +6.77,0.78, +6.97,-0.27, +5.32,-1.99, +2.93,-4.18, +1.91,-4.96, +0.57,-5.25, +1.64,-5.42, +3.56,-5.51, +4.62,-5.24, +4.67,-4.57, +4.99,-4.41, +5.87,-4.33, +6.78,-3.92, +6.27,-3.62, +5.99,-2.78, +5.66,-2.06, +6.01,-1.18, +6.08,-0.92, +6.71,-0.17, +6.74,-0.13, +7,0, +6.96,0.46, +7.17,1.26, +7.1,1.7, +6.42,3.31, +4.62,3.55, +1.17,1.55, +-3.24,-2.06, +-1.91,-2.65, +-4.45,0.59, +-6.1,-0.04, +-7.11,-0.15, +-7.28,0, +-6.78,0, +-5.73,-0.61, +-5.11,-0.9, +-4.6,-1.47, +-3.22,3.55, +-5.15,-2.7, +-4.73,-0.5, +-5.02,-0.13, +-5.31,-0.19, +-3.93,2.1, +-4.18,1.31, +-3.55,2.13, +-1.05,-0.86, +1.17,2.1, +1.2,1, +0.2,-2.57, +-0.75,-2.59, +-4.4,-2.93, +-5.81,-0.52, +-4.37,1.05, +-2.38,3.73, +-1.31,4.24, +-3.89,0.34, +-4.18,-2.54, +-3.8,-5.15, +-3.78,-5.68, +-1.33,-7.68, +-2.03,-6.93, +-1.49,-6.74, +-1.21,-6.87, +-3.61,-5.7, +-4.84,-3.55, +-4.79,-3.34, +-4.66,-3, +-6.04,-1.08, +-4.99,-1.81, +-4.67,1.29, +-2.04,5.61, +0,6.3, +-0.91,6.17, +-2.16,5.99, +-2.64,6.16, +-3.24,6.79, +-3.26,7.02, +-3.63,6.37, +-3.76,6.17, +-4.55,6.26, +-2.25,5.79, +-0.13,7.23, +2.68,6.85, +1.81,3.18, +0.43,3.37, +-0.41,2.58, +3.48,-1.08, +5,0.1, +5.29,-0.75, +6.2,-1.72, +7.65,4.92, +3.49,3.1, +4.77,5.32, +3.85,5.42, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.84,-10.54, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-7.76,-10.64, +-6.76,-11.71, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.09,-12.56, +-4.79,-13.15, +-4.81,-13.2, +-5.06,-13.9, +-5.12,-14.07, +-5.12,-14.07, +-3.92,-14.42, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.59,-14.69, +-2.48,-14.21, +-0.51,-14.26, +-0,-14.3, +-0,-14, +-0,-14, +-0,-14, +0.03,-14, +1.11,-13.93, +2.35,-13.33, +2.26,-12.83, +2.26,-12.83, +2.25,-12.78, +2.13,-12.1, +2.09,-11.87, +2.09,-11.84, +1.98,-11.23, +2.14,-10.87, +3.5,-10.5, +3.74,-10.27, +3.45,-9.48, +3.39,-9.32, +3.39,-9.32, +3.41,-9.38, +3.76,-10.11, +5.37,-9.69, +5.54,-9.6, +5.54,-9.6, +5.77,-10, +6.17,-10.32, +7.66,-9.31, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.37,-8.63, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.27,-7.71, +10.44,-6.03, +10.44,-6.03, +10.06,-5.81, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +9.41,-5.43, +8.61,-4.97, +8.59,-4.96, +8.5,-4.91, +7.77,-4.48, +7.74,-4.47, +7.72,-4.46, +7.2,-4.12, +6.97,-3.86, +6.96,-3.88, +6.9,-3.98, +6.75,-3.9, +6.92,-5.49, +8.6,-8.38, +8.9,-8.08, +8.81,-8.17, +9.05,-7.93, +8.99,-8.45, +8.15,-9.22, +7.68,-9.15, +7.12,-8.49, +8.49,-7.12, +8.63,-6.92, +9.78,-5.99, +8.64,-4.99, +7.36,-2.2, +6.13,4.97, +3.97,7.33, +2.77,7.6, +7.1,2.44, +6.75,1.19, +6.69,1.06, +5.15,-1.6, +2.8,-4.11, +-0.18,-5.59, +-0.45,-6.4, +0.72,-6.45, +2.24,-6.08, +4.05,-5.67, +5.52,-5.03, +6.28,-4.03, +5.93,-4.32, +6.9,-3.98, +7.03,-3.71, +6.95,-2.9, +6.92,-2.52, +6.77,-1.68, +6.89,-1.22, +6.93,-0.81, +7,0, +6.98,-0.15, +7.48,0, +7.44,1.31, +7.44,1.99, +6.98,2.14, +6.11,2.53, +2.68,2.5, +-1.97,1.56, +-3.8,-0.04, +-3.45,-0.69, +-5.27,-1.07, +-6.94,-0.69, +-7.64,-0.54, +-6.98,0.15, +-7,0, +-7,0, +-6.37,-0.26, +-5.37,-0.92, +-2.17,2.31, +-5.55,-1.86, +-3.85,0.41, +-4.71,0.73, +-5.35,0.79, +-4.98,1.72, +-5.02,-0.15, +-5.3,2.05, +-3.14,1.41, +-3.59,0.82, +-2.39,1.74, +-5.58,-0.43, +-4.79,1.48, +-6.82,4.8, +-5.26,1.96, +-4.74,1.61, +-2.37,3.4, +-1.58,4.4, +-3.57,1.71, +-4.69,-0.52, +-4.9,-2.27, +-4.7,-4.88, +-3.7,-5.45, +-3.64,-5.62, +-3.42,-5.28, +-2.31,-6.42, +-2.95,-6.32, +-4.98,-4.26, +-5.14,-3.12, +-5.32,-2.73, +-4.63,-2.95, +-4.97,-2, +-4.83,-0.86, +-2.36,5.48, +-0.3,6.66, +-1.29,6, +-3.14,4.7, +-3.87,4.23, +-3.23,5.63, +-2.84,6.2, +-3.15,6.68, +-3.65,6.66, +-3.39,6.26, +-2.09,5.86, +-0.99,6.75, +4.43,8.07, +0.86,5.73, +-0.1,3.56, +-0.65,3.24, +1.95,-0.28, +4.28,-2.22, +3.9,-2.18, +4.98,-2.87, +7.94,3.58, +4.45,3.27, +6.18,4.73, +5.73,2.69, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.98,-10.71, +-8.5,-10.13, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-7.7,-10.48, +-6.51,-11.28, +-6.71,-11.62, +-6.99,-12.1, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.35,-12.45, +-4.79,-13.15, +-4.79,-13.15, +-4.88,-13.4, +-5.12,-14.07, +-5.12,-14.07, +-3.52,-14.52, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-1.6,-14.44, +-0.13,-14.57, +-0,-14.17, +-0,-14.02, +-0,-14, +-0,-14, +0.38,-13.98, +2.3,-13.8, +2.39,-13.54, +2.28,-12.94, +2.26,-12.83, +2.26,-12.83, +2.16,-12.28, +2.1,-11.89, +2.09,-11.87, +2.22,-11.3, +3.48,-10.56, +3.79,-10.41, +3.78,-10.39, +3.69,-10.14, +3.48,-9.57, +3.41,-9.38, +3.39,-9.32, +3.55,-9.75, +5.24,-9.75, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.82,-9.95, +6.65,-9.99, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.48,-8.54, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.71,-7.09, +10.44,-6.03, +10.37,-5.98, +9.74,-5.62, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +8.83,-5.1, +8.59,-4.96, +8.59,-4.96, +8.23,-4.75, +7.88,-4.19, +8.07,-3.79, +7.97,-3.38, +7.45,-2.84, +7.48,-2.74, +7.39,-2.94, +6.97,-3.86, +6.29,-3.63, +7.42,-5.32, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.6,-8.06, +9.98,-8.37, +9.78,-8.57, +8.15,-9.22, +7.18,-8.55, +8.29,-7.32, +9.04,-6.33, +9.1,-4.83, +7.57,-2.4, +6.36,-2.27, +6.17,5.31, +4.59,8.33, +2.32,8.17, +6.84,2.71, +6.09,1.29, +4.08,0.36, +0.54,-3.59, +-0.37,-5.56, +-1.36,-6.95, +0.32,-7.33, +1.74,-6.89, +3.17,-6.33, +4.48,-5.34, +5.72,-4.2, +6.21,-3.18, +6.6,-3.01, +7.27,-3.2, +7.55,-2.5, +7.3,-1.76, +7.11,-2.01, +7.13,-1.72, +7.49,-1.32, +7.17,-1.26, +7.47,-0.17, +7.97,0, +7.36,0, +7.5,2.2, +7.14,2.42, +6.92,2.37, +4.9,1.78, +1.55,0.43, +-4.48,-0.84, +-4.3,-1.55, +-5.13,-1.73, +-6.27,-1.67, +-7.53,-1.33, +-7.06,-0.63, +-7.35,0.17, +-7.21,0.32, +-6.67,0, +-6.15,0, +-5.54,-0.85, +-6.2,0.61, +-5.86,-2, +-4.64,0.74, +-4.69,0.49, +-4.87,0.48, +-5.25,1.56, +-5.55,0.34, +-4.98,2.58, +-3.44,1.65, +-3.6,-1.92, +-4.66,0.66, +-2.47,-2.07, +-1.41,0.16, +-1.63,3.04, +-4.17,1.95, +-4.75,1.47, +-3.13,3.18, +-1.45,3.38, +-2.76,2.41, +-4.15,1.14, +-5.1,0.6, +-5.58,-2.35, +-5.29,-3.09, +-5.14,-3.29, +-4.26,-4.15, +-2.92,-5.75, +-3.49,-6.04, +-4.79,-5.07, +-5.5,-4.05, +-6.25,-2, +-5.92,-0.89, +-5.25,-1.26, +-5.33,-0.5, +-2.39,6.12, +-1.91,6.08, +-3.36,4.27, +-3.54,3.65, +-3.34,3.75, +-2.72,4.99, +-2.65,5.54, +-2.86,5.56, +-3.38,5.6, +-2.4,5.05, +-1.53,6.09, +-0.75,6.55, +0.42,5.42, +-0.8,3.31, +-0.72,2.82, +-1.08,3.05, +-1.28,3.41, +2.06,-0.35, +3.2,-1.34, +3.85,-0.06, +6.61,3.02, +6.8,3.56, +8.16,2.73, +4.25,1.55, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.98,-10.7, +-8.47,-10.09, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.26,-10.06, +-6.94,-11.01, +-6.51,-11.28, +-6.52,-11.3, +-6.93,-12.01, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.04,-12.59, +-4.79,-13.15, +-4.79,-13.15, +-4.84,-13.29, +-5.05,-13.86, +-5.12,-14.07, +-3.39,-14.55, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-1.49,-14.67, +-0,-14.75, +-0,-14.51, +-0,-14.19, +-0,-14, +-0,-14, +0.3,-13.99, +2.25,-13.81, +2.43,-13.78, +2.32,-13.16, +2.26,-12.83, +2.26,-12.83, +2.26,-12.8, +2.12,-12, +2.12,-11.86, +2.97,-11.63, +3.92,-10.78, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.76,-10.33, +3.44,-9.45, +3.4,-9.34, +3.58,-9.63, +5.25,-9.67, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.63,-9.7, +6.86,-9.79, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.9,-9.09, +9.21,-7.77, +9.23,-7.75, +9.34,-7.84, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +10.15,-6.46, +10.44,-6.03, +10.14,-5.85, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +9.33,-5.39, +8.59,-4.96, +8.59,-4.96, +8.52,-4.92, +8.13,-3.84, +8.37,-3.13, +8.4,-3.06, +7.59,-2.76, +7.49,-2.73, +7.49,-2.73, +7.49,-2.73, +6.96,-2.69, +6.17,-3.28, +8.36,-6.66, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.7,-8.14, +10.7,-8.18, +10.4,-8.73, +8.97,-7.83, +7.12,-6.63, +9.53,-6.06, +8.54,-4.93, +7.68,-0.96, +6.29,3.28, +6.33,2.57, +6.2,5.45, +4.89,8.07, +3.01,8.26, +5.41,0.89, +3.9,-0.27, +-1.35,-3.37, +-1.92,-5.51, +-1.57,-7.35, +-0.59,-8.06, +0.37,-8.06, +2.28,-7.61, +3.69,-6.86, +4.98,-5.22, +5.76,-3.93, +6.46,-2.64, +6.81,-1.99, +7.13,-1.74, +7.5,-1.17, +6.97,-0.35, +6.95,-0.59, +7.78,-0.59, +7.47,-0.15, +8.08,-0.17, +8.33,-0.62, +8.8,0.35, +7.95,0.26, +6.91,1.51, +6.7,2.78, +5.21,3.5, +4.14,0.24, +-2.25,-2.35, +-5.14,-2.33, +-5.44,-2.54, +-5.86,-2.29, +-6.62,-2.25, +-7.21,-1.27, +-6.93,-0.81, +-7.32,0, +-7.17,0.8, +-6.75,0, +-6.03,0, +-5.85,-0.65, +-5.67,1.32, +-1.65,1.9, +-4.39,0.42, +-4.32,0.79, +-5.03,-0.15, +-5.61,2.1, +-3.95,1.67, +-5.22,-0.35, +-3.95,0.72, +-4.13,-2.42, +-4.78,-0.46, +-2.18,-0.23, +-1.03,2.34, +-0.84,2.97, +-3.56,2.44, +-3.48,2.01, +-2.23,3.21, +-1.18,3.24, +-2.08,2.21, +-3.33,2.33, +-3.68,1.96, +-4.75,1.42, +-5.4,-0.51, +-5.68,-1.91, +-4.92,-3.44, +-3.71,-4.69, +-2.66,-6.02, +-3.77,-5.68, +-3.98,-5.21, +-4.09,-4.56, +-5.19,-2.57, +-4.99,-1.68, +-5.25,-1.03, +-3.5,5.01, +-3.3,5.46, +-4.08,3.46, +-3.95,2.66, +-2.19,4.38, +-1.65,5.34, +-2.06,5.66, +-3.37,4.94, +-3.47,4.31, +-2.18,4.54, +-1.04,7.44, +-0.41,6.35, +-0.29,4.79, +-0.66,4.39, +-0.75,3.81, +-0.73,4.95, +-0.92,4.76, +-0.97,3.56, +0.71,5.37, +3.49,3.48, +5.24,3.68, +5.56,3.26, +6.95,3.29, +4.21,1.96, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.75,-10.42, +-8.39,-10, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.01,-10.25, +-6.65,-11.19, +-6.51,-11.28, +-6.51,-11.28, +-6.63,-11.49, +-6.94,-12.03, +-7,-12.12, +-7,-12.12, +-6.21,-12.51, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.95,-13.6, +-5.12,-14.07, +-3.36,-14.56, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.31,-14.56, +-0.51,-14.26, +-0,-14.15, +-0,-14.07, +-0,-14.02, +-0,-14, +0.03,-14, +1.92,-13.85, +2.43,-13.78, +2.43,-13.78, +2.4,-13.59, +2.29,-12.96, +2.26,-12.83, +2.26,-12.8, +2.19,-12.42, +2.39,-11.88, +3.92,-11.39, +4.05,-11.12, +3.82,-10.5, +3.79,-10.41, +3.79,-10.41, +3.78,-10.39, +3.71,-10.19, +3.54,-9.73, +3.64,-9.76, +5.25,-9.67, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.6,-9.56, +7.02,-8.9, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.34,-8.64, +9.23,-7.75, +9.7,-8.14, +9.92,-8.32, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +10.17,-6.42, +10.44,-6.03, +9.81,-5.66, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +8.98,-5.18, +8.59,-4.96, +8.59,-4.96, +8.45,-4, +8.4,-3.06, +8.4,-3.06, +8.17,-2.97, +7.49,-2.73, +7.49,-2.73, +7.44,-2.71, +6.83,-2.48, +6.6,-2.4, +6.57,-2.42, +7.59,-5.27, +8.98,-7.54, +9.23,-7.75, +9.68,-7.1, +10.95,-6.98, +12.02,-6.94, +12.02,-6.94, +9.37,-5.95, +7.02,-5.64, +8.51,-4.92, +7.98,-4.6, +7.19,0.44, +6.77,3.71, +6.48,3.74, +7.23,4.72, +6.5,6.4, +4.77,6.12, +3.59,-1.46, +-0.71,-3.62, +-3.41,-4.64, +-2.08,-7.17, +-1.61,-7.79, +-0.15,-8.06, +1.6,-7.93, +2.91,-7.41, +4.18,-5.73, +5.11,-4.74, +6.04,-3.49, +6.67,-2.06, +6.85,-1.36, +6.92,-0.87, +6.97,-0.35, +7.23,0.17, +7.55,0, +7.9,0.77, +8.37,0.2, +8.18,0.39, +8.3,-0.35, +7.66,-0.35, +7.05,0.69, +5.76,2.07, +4.76,3.6, +2.13,4.09, +-1.23,-0.83, +-5.09,-2.09, +-5.22,-3.01, +-5.22,-3.01, +-5.97,-3.04, +-6.62,-2.25, +-6.77,-1.66, +-6.92,-0.87, +-7,0, +-6.98,0.2, +-6.75,0, +-6.3,0, +-5.96,-0.75, +0.61,4.17, +1.77,0.03, +-4.79,1.24, +-4,2.73, +-5.66,0.47, +-1.51,0.21, +-0.39,1.75, +-3.73,-0.59, +-3.27,-1.46, +-4.18,-3.68, +-3.78,-0.28, +-3.53,2.2, +-3.85,2.83, +-2.57,3.45, +-3.06,2.68, +-1.89,2.71, +-1.66,3.11, +-0.32,3.01, +-2.38,1.59, +-2.49,2.83, +-3.04,2.68, +-3.28,3.24, +-4.41,1.63, +-4.83,0.13, +-5.18,-1.87, +-4.06,-4.31, +-2.81,-5.6, +-2.98,-6.09, +-3.76,-5.64, +-3.82,-5.57, +-4.52,-3.97, +-4.53,-2.82, +-5.19,-1.17, +-4.52,3.8, +-3.77,4.69, +-3.87,3.7, +-2.9,3.05, +-0.95,4.77, +-1.29,5.58, +-1.18,5.71, +-3.11,4.2, +-3.25,3.87, +-1.77,4.86, +-1.65,7.21, +-0.34,6.33, +0,5.05, +-0.35,5.67, +-0.82,5.31, +-0.93,5.93, +-0.93,5.3, +-0.65,7.55, +-0.4,8.07, +0.97,4.61, +1.81,3.88, +3.92,5.57, +5.32,5.26, +3.34,3.51, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.94,-10.66, +-8.57,-10.21, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-6.96,-10.99, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.52,-11.3, +-6.85,-11.86, +-7,-12.12, +-7,-12.12, +-6.12,-12.55, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.81,-13.2, +-4.99,-13.72, +-3.25,-14.59, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.58,-14.66, +-2.3,-14.1, +-0.19,-13.99, +-0,-14, +-0,-14, +-0,-14, +-0,-14, +-0,-14, +1.55,-13.89, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.39,-13.56, +2.27,-12.89, +2.26,-12.83, +2.48,-12.78, +3.93,-11.95, +4.1,-11.33, +4.11,-11.3, +3.96,-10.88, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.78,-10.39, +4.68,-9.99, +5.52,-9.61, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.79,-9.44, +7.06,-8.54, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.89,-8.11, +9.25,-7.76, +9.77,-8.2, +9.84,-8.26, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +10.19,-6.4, +10.44,-6.03, +9.89,-5.71, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +8.65,-4.99, +8.59,-4.96, +8.47,-4.89, +8.25,-3.58, +8.4,-3.06, +8.32,-3.03, +7.51,-2.73, +7.49,-2.73, +7.45,-2.71, +6.84,-2.06, +6.69,-2.01, +6.59,-2.34, +6.58,-2.39, +8.06,-6.07, +9.04,-6.33, +10.08,-6.35, +10.44,-6.03, +10.92,-6.3, +12.12,-7, +11.8,-5.72, +7.79,-4.1, +7.74,-4.47, +7.74,-4.47, +7.81,-4, +7.72,0.76, +7.76,2.33, +7.54,1.08, +8.04,1.12, +6.99,3.72, +5.38,3.44, +0.54,-3.63, +-3.11,-5.12, +-2.78,-5.73, +-2.08,-6.8, +-0.69,-7.91, +0.46,-7.93, +1.79,-7.63, +3.23,-6.59, +4.25,-5.54, +5.54,-4.25, +6.28,-3.03, +6.81,-1.51, +6.49,-0.99, +7,0, +7.35,-0.15, +7.9,0.79, +8.34,1.3, +7.85,1.38, +8.62,1.19, +8.57,1.06, +8.37,-0.65, +6.8,-0.67, +6.36,-0.59, +4.14,2.15, +0.76,5.27, +-3.42,-0.1, +-5.01,-1.82, +-5.44,-2.26, +-5.22,-3.01, +-5.44,-2.54, +-6.46,-2.64, +-6.58,-2.39, +-6.65,-2.1, +-6.89,-1.22, +-6.98,-0.17, +-7,0, +-6.58,0, +-6.15,0, +-5.96,-0.75, +-2.08,2.92, +-1.84,2.3, +-8.38,1.44, +-6.23,1.4, +-7.78,3.63, +-5.73,3.38, +-1.3,1.67, +-5.87,2.44, +-1.58,-1.82, +-1.67,-5.83, +-2.28,-4.15, +-3.04,1.01, +-3.62,1.47, +-3.45,2.22, +-1.78,4.1, +0.57,3.52, +0.84,2.72, +-0.25,1.81, +-2.65,1.03, +-1.88,2.12, +-1.74,3.63, +-0.4,3.94, +-1.95,3.39, +-3.65,2.21, +-4.73,1.82, +-5,-2.09, +-4.19,-4.34, +-3.46,-5.77, +-3.22,-6.19, +-2.76,-6.4, +-4.02,-4.82, +-4.05,-4.21, +-4.73,-2.14, +-3.97,4.35, +-3.87,4.62, +-3.58,4.16, +-2.28,3.46, +-0.11,5.04, +-0.63,5.85, +-0.64,5.65, +-2.99,4.3, +-3.4,4.06, +-1.74,6.53, +-1.66,7.53, +-0.17,6.33, +0,5.92, +0,6.15, +-0.17,6.01, +-0.6,6.39, +-0.89,5.95, +-1.51,8.59, +-0.87,6.81, +0,4.06, +1.01,4.44, +2.8,6.12, +3.98,7.24, +3.13,4.77, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.61,-10.26, +-8.31,-10.03, +-8.17,-10.13, +-8.19,-10.12, +-8.08,-10.2, +-8.22,-10.1, +-7.87,-10.36, +-6.62,-11.21, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.57,-11.38, +-6.95,-12.05, +-7,-12.12, +-6.2,-12.52, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.81,-13.21, +-3.39,-14.31, +-2.59,-14.68, +-2.6,-14.74, +-2.59,-14.68, +-2.46,-13.95, +-2.19,-13.82, +-0.09,-13.99, +-0,-14, +-0,-14, +-0,-14, +-0,-14, +0.94,-13.93, +2.35,-13.8, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.37,-13.42, +2.47,-12.86, +4.02,-12.38, +4.39,-12.06, +4.16,-11.42, +4.12,-11.33, +4.11,-11.3, +3.96,-10.87, +3.8,-10.43, +3.79,-10.41, +3.79,-10.41, +3.81,-10.4, +4.78,-9.97, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.56,-9.58, +6.99,-8.6, +7.64,-9.11, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.9,-8.09, +9.25,-7.76, +9.88,-8.29, +9.98,-8.37, +9.38,-7.87, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +10.1,-6.53, +10.44,-6.03, +9.86,-5.69, +9.6,-5.54, +9.6,-5.54, +9.36,-5.4, +8.59,-4.96, +8.59,-4.96, +8.27,-4.73, +8.34,-3.2, +8.4,-3.06, +7.85,-2.86, +7.49,-2.73, +7.51,-2.67, +7.44,-1.75, +6.88,-1.27, +6.89,-1.22, +6.88,-1.28, +6.8,-1.57, +8.6,-4.78, +9.6,-5.54, +10.32,-5.96, +10.23,-5.9, +10.8,-6.23, +11.86,-5.54, +11.23,-1.33, +7.53,2.98, +7.65,-3.64, +7.98,-4.61, +7.67,-3.78, +8.46,-1.98, +8.79,-1.2, +7.82,-4.31, +7.82,-3.92, +4.09,-3.26, +-3.11,-3.39, +-3.22,-4.02, +-2.72,-5.36, +-2.01,-6.53, +-0.65,-7.67, +0.4,-7.94, +1.5,-7.71, +2.54,-6.47, +3.39,-5.66, +4.09,-4.4, +5.22,-3.21, +5.94,-1.88, +6.18,-1.09, +6.54,-0.25, +6.35,-0.85, +6.61,-0.3, +7.38,1.17, +8.57,1.51, +7.25,1.65, +7.72,2.39, +7.62,1.17, +6.89,-0.64, +4.39,-2.83, +-1.17,-1.96, +-3.64,-1.91, +-4.98,-1.81, +-5.4,-1.78, +-5.66,-2.06, +-6.17,-2.03, +-6.05,-2.32, +-5.27,-2.89, +-5.85,-2.63, +-6.43,-2.46, +-6.67,-2.06, +-6.89,-1.22, +-6.94,-0.69, +-7,0, +-5.99,-0.39, +-5.97,-0.6, +-5.93,-1.05, +-6.46,0.17, +-5.04,-0.1, +-4.86,-1.25, +-4.75,-1.73, +-5.15,-0.58, +-5.64,0.76, +-6.16,2.68, +-5.26,3.38, +-5.85,3.75, +-6.78,1.98, +-4.97,-0.92, +-3.9,0.31, +-3.01,-1.73, +-1.75,1.62, +-0.42,3.41, +0.38,3.72, +1.8,2.8, +0.32,1.59, +-1.64,2.17, +-0.97,2.66, +0.75,3.26, +2.11,3.1, +0.35,3.45, +-1.4,3.59, +-2.62,3.81, +-4.41,2.01, +-4.93,-1.91, +-3.49,-4.78, +-1.73,-6.73, +-1.89,-6.71, +-3.53,-5.59, +-3.87,-4.62, +-4.42,-3.32, +-4.88,1.84, +-3.92,4.47, +-2.6,4.75, +-1.32,4.55, +0,5.05, +-0.65,5.73, +-1.42,5.54, +-3.46,4.12, +-3.48,4.15, +-2.49,5.05, +-1.75,5.33, +-0.5,6.68, +0,7.16, +0,7.48, +0,6.39, +-0.3,6.42, +-0.67,7.66, +-0.9,6.36, +-0.81,5.74, +0.15,5.2, +1.09,6.21, +1.84,7.01, +2.37,6.96, +2.1,4.64, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.51,-10.87, +-7.84,-10.56, +-7.15,-10.86, +-6.63,-11.2, +-6.64,-11.2, +-6.54,-11.26, +-6.71,-11.16, +-6.53,-11.27, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.54,-11.33, +-6.9,-11.95, +-7,-12.12, +-6.29,-12.47, +-5.07,-13.03, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-3.6,-13.55, +-2.55,-14.04, +-2.54,-14.42, +-2.48,-14.05, +-2.43,-13.78, +-2.24,-13.81, +-0.24,-13.99, +-0,-14, +-0,-14, +-0,-14, +-0,-14, +0.73,-13.96, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +3.35,-12.98, +4.45,-12.24, +4.45,-12.24, +4.35,-11.96, +4.13,-11.35, +4.12,-11.33, +4.12,-11.33, +3.92,-10.76, +3.79,-10.41, +3.79,-10.41, +4.12,-10.27, +5.43,-9.65, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.7,-9.49, +7.1,-8.51, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.87,-9.13, +9.19,-7.79, +9.53,-8, +9.98,-8.37, +9.89,-8.3, +9.26,-7.77, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.81,-6.94, +10.34,-5.97, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +9.09,-5.25, +8.59,-4.96, +8.52,-4.92, +8,-4.04, +8.4,-3.06, +8.14,-2.96, +7.49,-2.73, +7.56,-2.5, +7.81,-1.44, +7.02,-1.24, +6.89,-1.22, +6.89,-1.22, +6.89,-1.22, +7.34,-1.94, +9.6,-5.54, +9.84,-5.68, +10.44,-6.03, +10.44,-6.03, +10.88,-5.65, +11.67,-2.19, +10.03,4.47, +6.62,3.82, +8.25,-3, +6.92,-4.33, +7.26,-4.19, +8.4,-4.85, +8.71,-5.28, +6.84,-5.45, +3.2,-5.94, +-2.5,-3.92, +-3.32,-3.8, +-3.2,-4.75, +-2.35,-5.76, +-1.26,-7.27, +-0,-7.94, +1.65,-7.67, +2.39,-7.24, +2.89,-6.21, +3.86,-4.6, +4.71,-3.14, +5.54,-1.51, +5.84,-0.52, +6.16,0, +5.88,-0.13, +6.51,0.39, +7.2,0.92, +7.31,2.48, +8.55,1.96, +8.17,0.84, +6.62,2.23, +6.26,1.43, +4.2,-1.35, +-0.99,-3.34, +-4.04,-2.16, +-4.98,-1.81, +-5.55,-2.02, +-5.89,-2.14, +-6.58,-1.84, +-6.41,-2.19, +-5.67,-2.38, +-5.12,-2.65, +-5.52,-2.62, +-5.35,-2.74, +-6.46,-2.35, +-6.71,-1.89, +-6.89,-1.22, +-6.63,-0.99, +-5.93,-1.05, +-5.93,-1.05, +-5.93,-1.05, +-5.7,-1.93, +-5.66,-2.06, +-5.09,-1.85, +-4.75,-1.73, +-4.75,-1.73, +-4.69,-1.52, +-4.52,1.12, +-4.11,4.2, +-6.39,1.1, +-5.55,1.34, +-5.4,-0.55, +-4.94,2.14, +-1.05,0.55, +1.99,-0.54, +0.91,1.03, +1.29,2.28, +1.66,1.24, +0.8,1.83, +-1,2.74, +-0.77,2.77, +0.83,2.67, +1.78,2.21, +-0.04,3.24, +-2.22,3.38, +-2.1,3.46, +-3.14,3.39, +-2.68,3.92, +-3.56,-2.87, +-0.7,-6.29, +-0.89,-6.77, +-3.26,-5.69, +-3.78,-4.68, +-4.65,-3.54, +-5.33,0.4, +-4.41,4.04, +-2.94,4.74, +-1.4,4.82, +-0.42,5.43, +-1.74,5.3, +-2.2,5.33, +-2.69,4.35, +-2.36,4.29, +-1.77,5.18, +-1.07,5.35, +-0.57,6.71, +0,8.33, +0,7.41, +0,7.14, +0,7, +-0.28,7.36, +-0.75,6.61, +-0.3,8.11, +0.38,8.41, +1.96,11.1, +1.09,8.99, +0.87,7.2, +-0.15,4.97, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9,-10.72, +-8.97,-10.69, +-8.81,-10.5, +-8.55,-10.31, +-6.92,-11.15, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.6,-11.43, +-7,-12.12, +-7,-12.12, +-5.31,-12.93, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.5,-13.24, +-2.52,-13.77, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.02,-13.84, +-0.08,-13.99, +-0,-14, +-0,-14, +-0,-14, +0.18,-13.99, +2,-13.84, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.83,-13.69, +4.5,-12.97, +4.49,-12.33, +4.45,-12.24, +4.44,-12.21, +4.3,-11.82, +4.13,-11.36, +4.12,-11.33, +4.08,-11.22, +3.9,-10.72, +3.87,-10.43, +4.72,-10.01, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.3,-9.09, +7.1,-8.5, +7.92,-9.44, +7.84,-9.34, +7.86,-9.36, +7.86,-9.36, +8.98,-8.75, +9.81,-8.23, +9.98,-8.37, +9.85,-8.26, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.25,-7.73, +9.92,-6.45, +9.6,-5.54, +9.6,-5.54, +9.56,-5.52, +8.75,-5.05, +8.59,-4.96, +8.25,-4.76, +7.85,-4.33, +8.4,-3.06, +7.96,-2.9, +7.49,-2.73, +7.75,-1.77, +7.73,-1.36, +6.89,-1.22, +6.89,-1.22, +6.92,-0.99, +6.91,-1.06, +8.78,-4.24, +9.6,-5.54, +9.78,-5.65, +10.44,-6.03, +10.44,-6.03, +10.99,-4.84, +11.38,1.42, +9.21,4.58, +6.78,3.92, +7.78,-1.56, +6.19,-4.65, +5.7,-4.77, +5.93,-6.16, +6.48,-6.31, +2.59,-6.99, +-2.84,-5.14, +-4.06,-4.09, +-3.36,-4.47, +-2.32,-5.75, +-1.04,-6.88, +0.35,-7.43, +1.72,-7.13, +2.3,-6.89, +3.09,-5.69, +3.93,-4.52, +4.4,-2.81, +4.89,-1.12, +5.14,0, +5.65,0.82, +5.13,1.24, +5.97,0.65, +6.14,1.18, +5.12,-1.12, +7.03,1.53, +4.32,-0.67, +7.43,0.15, +5.07,-0.59, +4.11,-2.05, +-2.02,-2.86, +-4.58,-2.08, +-5.66,-2.06, +-5.21,-1.89, +-5.7,-1.92, +-5.93,-2, +-6.68,-2.41, +-5.73,-2.56, +-5.33,-2.78, +-5.1,-2.94, +-4.92,-3.07, +-4.79,-2.91, +-5.17,-2.83, +-6.23,-2.57, +-6.36,-2.13, +-5.87,-1.3, +-5.84,-1.38, +-5.8,-1.55, +-5.73,-1.81, +-5.66,-2.06, +-5.66,-2.06, +-5.66,-2.06, +-4.61,-2.03, +-4.63,-1.99, +-4.61,-2.03, +-2.61,-1.37, +-1.1,-0.9, +-0.83,0.73, +-0.84,-2.75, +-2.84,-1.33, +-4.03,2.16, +-4.34,2.82, +-4.09,2.94, +-3.91,2.95, +-1.27,3.12, +-0.41,2.79, +0.72,2.73, +-1.36,1.98, +-2.1,1.95, +-1.02,2.63, +0.49,2.81, +-1.39,3.09, +-2.92,2.79, +-2.87,2.8, +-2.77,2.98, +-0.59,4.01, +-2.44,2.84, +0.34,-5.24, +0.11,-6.21, +-2.74,-5.65, +-3.67,-4.96, +-4.43,-4.06, +-5.65,-1.39, +-5.52,2.01, +-3.78,3.5, +-2.71,4.2, +-1.83,4.73, +-2.52,5.16, +-2,4.32, +-1.46,5.39, +-1.05,5.22, +-1.03,5.81, +-1.05,5.93, +-0.17,8.23, +0,7.55, +0,8.09, +0.35,7.94, +0.23,7.46, +0,7, +0,7.36, +0,7.97, +0.32,9.8, +0.59,9.2, +0.72,10.61, +-0.79,7.76, +-1.06,4.86, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.91,-10.62, +-8.51,-10.14, +-8.43,-10.05, +-7.75,-10.45, +-6.59,-11.24, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.64,-11.5, +-6.95,-12.04, +-7,-12.12, +-5.31,-12.93, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.52,-13.24, +-2.6,-13.75, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.4,-13.79, +-0.19,-13.99, +-0,-14, +-0,-14, +-0,-14, +0.2,-13.99, +2.19,-13.82, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +3.35,-13.57, +4.75,-13.16, +4.7,-12.91, +4.47,-12.28, +4.45,-12.24, +4.44,-12.21, +4.28,-11.75, +4.16,-11.44, +4.12,-11.33, +4.14,-11.25, +4.84,-10.68, +5.53,-9.67, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.54,-8.92, +7.12,-8.49, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +9.12,-9.25, +9.98,-8.37, +9.98,-8.37, +9.62,-8.07, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.21,-7.73, +9.12,-6.8, +9.6,-5.54, +9.6,-5.54, +8.96,-5.18, +8.59,-4.96, +8.53,-4.92, +7.86,-4.54, +7.94,-4.06, +8.12,-2.95, +7.49,-2.73, +7.59,-2.38, +7.85,-1.38, +7.2,-1.27, +6.89,-1.22, +6.95,-0.67, +6.99,-0.16, +7.86,-1.85, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +10.23,-5.9, +10.44,-6.03, +11.35,-3.85, +10.52,3.29, +9.11,4.62, +7.35,3.36, +7.38,-0.91, +4.06,-6.84, +3.05,-7.3, +3.09,-6.86, +2.07,-6.69, +-2.38,-6.33, +-3.18,-4.51, +-2.71,-5.03, +-2.04,-5.61, +-0.52,-5.98, +0.23,-6.2, +1.84,-6.58, +2.32,-6.05, +3.09,-5.69, +3.77,-4.35, +4.04,-2.37, +4.05,-0.35, +4.73,0.72, +5.41,1.39, +5.51,1.8, +5.11,1.44, +3.85,-1.74, +3.37,-0.9, +0.83,-3.06, +-1.17,-1.99, +-4.53,-2.65, +6.94,-0.04, +3.65,-1.76, +0.21,-2.63, +-4.96,-2.71, +-5.22,-3.01, +-5.39,-2.66, +-5.47,-2.47, +-5.44,-2.54, +-5.79,-2.77, +-5.89,-3.09, +-5.07,-3.23, +-4.79,-3.63, +-4.67,-3.58, +-5.27,-3.38, +-3.86,-3.24, +-4.55,-3.13, +-5.14,-3.12, +-5.47,-2.48, +-5.61,-2.18, +-5.66,-2.06, +-5.66,-2.06, +-5.66,-2.06, +-5.66,-2.06, +-5.33,-2.78, +-5.22,-3.01, +-4.86,-2.8, +-4.29,-2.48, +-3.9,-2.25, +-2.05,0.39, +-1.74,0.93, +-2.89,2.84, +-3.17,2.41, +-2.38,-3.4, +-3.93,1.05, +-4.59,3.71, +-4.25,2.71, +-3.93,2.44, +-2.67,3.03, +-0.71,2.79, +0.34,2.89, +0.81,2.49, +2.06,2.01, +-2,1.13, +-1.84,1.98, +-2.63,1.95, +-3.24,1.44, +-2.75,1.83, +-2.13,2.94, +-0.59,3.79, +-1.98,2.56, +-0.41,-3.72, +1.14,-5.74, +-1.17,-5.86, +-3.57,-5.1, +-4.3,-4.19, +-5.41,-2.61, +-5.52,-0.87, +-4.88,0.64, +-4.52,1.91, +-4.38,2.38, +-3.28,3.72, +-1.99,4.2, +-1.09,5.55, +-0.99,5.62, +-0.83,5.47, +-0.15,6.15, +0,8.7, +0.4,7.52, +0.72,8.23, +1.21,7.86, +1.38,7.85, +1.14,7.73, +0,6.88, +0,7.83, +0,9.09, +0,9.22, +0.32,10.11, +-1.57,8.93, +-1.61,5.73, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-7.1,-10.89, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.64,-11.5, +-6.95,-12.05, +-5.46,-12.86, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.58,-13.22, +-2.64,-13.74, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.19,-13.82, +-0.18,-13.99, +-0,-14, +-0,-14, +-0,-14, +0.37,-13.98, +2.34,-13.8, +2.43,-13.78, +2.43,-13.78, +2.44,-13.85, +2.43,-13.78, +2.5,-13.77, +4.52,-13.24, +4.79,-13.15, +4.79,-13.15, +4.68,-12.85, +4.57,-12.56, +4.46,-12.26, +4.45,-12.24, +4.4,-12.08, +4.21,-11.5, +4.83,-11.03, +5.99,-10.46, +5.83,-10.1, +5.57,-9.65, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.68,-9.51, +6.9,-8.66, +7.12,-8.49, +8.12,-9.68, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +9.64,-8.72, +9.98,-8.37, +9.4,-7.89, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.22,-7.73, +8.59,-7.21, +8.91,-6.53, +9.49,-5.48, +8.73,-5.04, +8.59,-4.96, +8.39,-4.85, +7.74,-4.47, +7.78,-4.41, +7.82,-2.95, +7.49,-2.73, +7.6,-2.34, +7.82,-1.38, +7.27,-1.28, +6.93,-0.9, +6.99,-0.17, +7,0, +8.73,-3.69, +9.29,-5.37, +9.6,-5.54, +9.6,-5.54, +10.02,-5.78, +10.55,-5.79, +11.58,-2.95, +10.36,1.99, +8.2,4.51, +7.18,4.15, +6.33,-2.24, +1.3,-9.07, +0.71,-8.36, +-0.72,-7.07, +-1.63,-6.37, +-2.74,-5.35, +-1.85,-5.07, +-0.69,-5.9, +0.7,-5.96, +1.27,-5.85, +2.38,-5.67, +2.92,-5.37, +3.33,-4.66, +3.78,-4.3, +4.22,-2.97, +4.21,-1.37, +4,0.61, +4.58,1.29, +5.32,2.29, +4.44,1.87, +3.53,0.56, +4.04,-1.87, +-2.83,-4.46, +-3.13,-3.93, +-4.44,-2.62, +-4.93,-2.35, +-2.7,-2.23, +-2.55,-2.39, +-4.16,-3.32, +-4.78,-3.84, +-5.09,-3.68, +-4.79,-3.63, +-4.84,-3.55, +-4.87,-3.5, +-5.12,-3.79, +-4.74,-3.98, +-4.37,-4.12, +-4.15,-4.34, +-3.82,-4.15, +-3.25,-3.87, +-3.35,-3.77, +-3.89,-3.39, +-4.79,-3.34, +-5.07,-3.23, +-5.22,-3.01, +-5.22,-3.01, +-5.33,-2.78, +-5.35,-2.74, +-5.22,-3.01, +-5.22,-3.01, +-4.99,-3.34, +-4.31,-3.1, +-4.09,-2.94, +-3.5,-2.81, +-2.49,-0.71, +-3.71,0.73, +-4.03,2.12, +-4.28,2.51, +-3.54,1.26, +-3.84,1.59, +-4.08,3.62, +-4.27,2.93, +-4.31,2.11, +-2.59,2.47, +-0.44,3.15, +0.62,3.34, +1.19,2.84, +1.91,2.17, +-1.29,-0.09, +-2.84,-0.25, +-2.67,1.12, +-1.28,-0.72, +-2.21,-1.22, +-1.45,1.82, +-0.38,3.16, +-1.15,2.6, +2.08,-3.31, +1.24,-5.45, +0.75,-5.96, +-2.18,-5.77, +-4.04,-4.41, +-4.84,-3.55, +-4.9,-2.86, +-5.01,-1.72, +-5.03,-0.11, +-4.98,0.44, +-4.41,2.4, +-3.22,3.46, +-0.88,4.98, +-0.46,5.14, +0,5.89, +0,6.39, +0,7.81, +1.16,8.72, +1.57,8.93, +1.47,8.33, +1.49,8.45, +1.38,7.85, +1.3,8.22, +0,8.11, +0,8.53, +0,9.19, +0,9.8, +-1.42,9.45, +-1.19,6.73, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.37,-9.98, +-7.68,-10.49, +-6.61,-11.22, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.36,-11.46, +-5.03,-12.71, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-4.72,-13.18, +-3.58,-13.5, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.22,-13.81, +-0.21,-13.99, +-0,-14, +-0,-14, +-0,-14, +0.1,-13.99, +2.19,-13.82, +2.43,-13.78, +2.48,-14.08, +2.54,-14.41, +2.43,-13.78, +2.43,-13.78, +4.22,-13.33, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.78,-13.13, +4.56,-12.52, +4.45,-12.24, +4.45,-12.24, +4.8,-11.89, +5.97,-10.66, +6.03,-10.44, +6.03,-10.44, +5.94,-10.29, +5.66,-9.81, +5.57,-9.65, +5.54,-9.6, +5.97,-9.31, +7.12,-8.49, +7.12,-8.49, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.76,-9.61, +9.89,-8.43, +9.27,-7.78, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +8.86,-7.43, +8.49,-7.12, +8.53,-7.07, +9.11,-5.48, +8.59,-4.96, +8.59,-4.96, +7.93,-4.58, +7.74,-4.47, +7.82,-4.16, +7.49,-2.73, +7.49,-2.73, +7.75,-1.75, +7.85,-1.38, +7.44,-1.3, +6.99,-0.44, +7,0, +7.03,0, +8.81,-5.09, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +10.02,-5.78, +10.88,-5.07, +11.39,-3.87, +10.45,1.73, +7.81,3.68, +5.13,3.32, +4.13,-5.26, +0.73,-9.63, +-0.91,-9.14, +-1.24,-7.03, +-1.19,-5.9, +-1.3,-5.87, +-0.65,-4.98, +0.65,-5.09, +1.82,-5.35, +2.54,-5.44, +3,-5.2, +3.87,-4.62, +4.31,-3.87, +4.12,-2.89, +4.54,-2.18, +4.64,-0.11, +5.02,1.13, +5.51,2.13, +5.08,1.92, +3.08,0.88, +-0.93,-2.89, +-2.92,-4.13, +-4.38,-3.64, +-4.1,-3.61, +-3.71,-3.28, +-3.5,-2.19, +-3.18,-2.49, +-2.48,-2.8, +-3.32,-3.5, +-3.78,-4.51, +-3.93,-4.68, +-4.11,-4.69, +-3.97,-4.52, +-3.77,-4.52, +-4.09,-4.4, +-3.87,-4.62, +-3.77,-4.49, +-3.35,-4.54, +-3.17,-4.06, +-2.56,-3.47, +-2.94,-3.64, +-3.25,-3.87, +-4.05,-3.98, +-4.51,-3.98, +-4.84,-3.55, +-5.12,-3.88, +-5.22,-3.01, +-4.87,-3.5, +-4.96,-3.38, +-4.82,-3.59, +-4.62,-3.87, +-4.06,-3.4, +-3.87,-3.25, +-3.87,-3.25, +-3.06,1.74, +-3.58,1.12, +-1.25,-0.58, +-2.77,0.18, +-2.57,1.72, +-2.91,2.69, +-3.72,3.4, +-4.57,2.38, +-4.01,1.68, +-2.05,2.47, +-0.6,3.33, +0.48,3.65, +1.06,2.7, +1.52,1.96, +-2.13,-1.72, +-1.56,-2.41, +-2.12,-1.28, +0.1,-0.64, +0.01,0.41, +1.9,-0.86, +1.9,1.72, +1.49,2.44, +-1.7,-0.68, +0.37,-4.82, +1.72,-5.61, +-0.13,-5.9, +-2.98,-5.17, +-3.84,-4.58, +-4.11,-4.35, +-4.6,-3.53, +-4.39,-2.24, +-4.53,-1.96, +-4.95,0, +-3.82,2.5, +0,4.78, +0,5.3, +0,5.75, +0,6.44, +0.46,6.96, +1.41,7.99, +1.65,9.35, +1.49,8.45, +1.48,8.4, +1.52,8.65, +1.58,8.94, +1.32,8.58, +0.86,8.87, +0,9.22, +0,9.36, +-0.41,9.51, +-0.91,5.17, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.37,-9.98, +-7.9,-10.34, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.7,-11.68, +-4.48,-12.3, +-4.6,-12.63, +-4.65,-12.79, +-4.77,-13.11, +-4.79,-13.15, +-4.79,-13.15, +-3.88,-13.42, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-2.34,-13.8, +-0.76,-13.95, +-0,-14, +-0,-14, +-0,-14, +0.24,-13.99, +2.4,-13.79, +2.43,-13.78, +2.45,-13.88, +2.53,-14.36, +2.43,-13.8, +3.08,-13.64, +4.66,-13.19, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.74,-13.01, +4.63,-12.72, +4.52,-12.26, +5.39,-11.83, +6.48,-11.22, +6.25,-10.82, +6.04,-10.46, +6.03,-10.44, +6.02,-10.42, +5.82,-10.07, +5.69,-9.86, +6.46,-9.01, +7.12,-8.49, +7.12,-8.49, +8.06,-9.6, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.7,-9.67, +9.38,-8.05, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +8.93,-7.5, +8.49,-7.12, +8.49,-7.12, +8.28,-6.27, +8.59,-4.96, +8.59,-4.96, +8,-4.62, +7.74,-4.47, +7.87,-4.23, +7.87,-2.87, +7.49,-2.73, +7.78,-1.67, +7.85,-1.38, +7.85,-1.38, +7.79,-0.15, +7.47,0, +7.81,-1.24, +8.73,-5.04, +9.47,-5.47, +9.6,-5.54, +9.6,-5.54, +9.96,-5.75, +10.88,-3.45, +11.62,-3.95, +10.92,0.9, +7.03,4.37, +3.57,3.91, +2.56,-5.51, +1.32,-9.21, +-0.52,-9.06, +0.03,-6.12, +-0.23,-6.01, +0.15,-5.75, +0.86,-5.32, +2.11,-5.08, +3.06,-4.65, +3.25,-4.92, +3.59,-4.82, +4.37,-4.12, +4.25,-2.99, +4.52,-2.23, +4.85,-1.36, +5.02,0.22, +4.99,1.61, +5.01,2.43, +2.05,-0.69, +1.38,0.97, +1.37,2.37, +-2.19,-1.35, +-4.55,-2.05, +-3.64,-3.05, +-3.13,-2.03, +-2.62,-2.24, +-2.3,-2.74, +-2.47,-3.21, +-2.73,-4.43, +-2.86,-5.41, +-2.85,-5.29, +-2.78,-5.6, +-2.88,-5.28, +-2.95,-5.11, +-3.23,-5.07, +-2.94,-5.1, +-2.96,-5.13, +-2.53,-4.38, +-2.47,-4.27, +-2.53,-4.38, +-2.53,-4.38, +-2.94,-4.09, +-3.53,-3.98, +-3.89,-4.4, +-4.3,-4.19, +-4.62,-3.87, +-4.64,-3.57, +-5.08,-1.54, +-4.71,-1.91, +-4.41,-2.02, +-4.45,-1.66, +-4.12,-1.12, +-3.99,-1.4, +-3.42,-1.2, +-3.66,3.91, +-3.85,3.23, +-3.88,3.64, +-4.35,2.8, +-3.24,2.8, +-3.17,3.27, +-3.69,3.23, +-4.19,2.32, +-3.55,1.75, +-1.71,2.47, +-0.41,4.05, +0.49,3.37, +0.9,2.48, +1.08,1.61, +-0.57,-2.17, +-0.83,-2.43, +-0.44,-1.59, +1.95,-0.55, +0.84,1.4, +2.26,0.5, +2.47,1.29, +0.29,1.97, +-3.23,-0.87, +-0.7,-4.29, +1.94,-5.4, +0.89,-5.92, +-1.24,-5.74, +-2.21,-5.56, +-2.54,-5.44, +-3.58,-4.73, +-2.23,-4.43, +-3.17,-3.78, +-4.55,-1.96, +-4.88,1.08, +-1.3,4.08, +0,5.05, +0.15,5.6, +0.8,6.23, +1.22,6.89, +1.41,7.97, +1.47,8.33, +1.55,8.81, +2.1,8.54, +2.04,8.57, +1.5,8.52, +1.51,8.56, +1.53,8.67, +1.5,9.59, +0,9.53, +0,11.53, +-0.16,7.74, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.37,-9.98, +-7.59,-10.54, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.85,-11.61, +-4.45,-12.24, +-4.45,-12.24, +-4.47,-12.28, +-4.52,-12.42, +-4.57,-12.57, +-4.74,-13.02, +-4,-13.36, +-2.55,-13.76, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-1.4,-13.9, +-0,-14, +-0,-14, +-0,-14, +0.09,-13.99, +2.07,-13.83, +2.43,-13.78, +2.43,-13.78, +2.57,-14.57, +2.52,-14.29, +3.32,-13.77, +4.85,-13.31, +4.82,-13.23, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +5.19,-12.56, +6.53,-11.47, +6.51,-11.28, +6.49,-11.24, +6.37,-11.03, +6.07,-10.51, +6.03,-10.44, +6.03,-10.44, +6.01,-10.35, +7.3,-9.3, +7.64,-9.1, +7.42,-8.84, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.48,-8.8, +9.19,-7.8, +9.23,-7.75, +9.23,-7.75, +9.23,-7.75, +9.16,-7.68, +8.53,-7.15, +8.49,-7.12, +8.49,-7.12, +7.81,-6.55, +8.55,-5.02, +8.59,-4.96, +7.82,-4.51, +7.74,-4.47, +8.06,-3.82, +8.27,-3.01, +7.61,-2.58, +7.84,-1.42, +7.85,-1.38, +7.88,-1.07, +7.97,-0.04, +7.97,0, +8.15,-1.42, +8.59,-4.96, +9.02,-5.21, +9.6,-5.54, +9.25,-4.83, +10.41,-5.11, +11.2,-1.29, +10.81,0.17, +9.47,3.42, +7.74,4.95, +4.12,3.83, +3.04,-1.31, +1.41,-7.45, +-1.85,-9.96, +1.5,-6.41, +1.76,-5.72, +2.27,-5.49, +3.3,-4.94, +4.18,-4.47, +4.29,-4.2, +4.09,-4.4, +4.2,-4.29, +4.72,-3.73, +5.13,-2.69, +5.18,-1.73, +5.08,-0.56, +5.12,1.33, +4.43,0.73, +4.12,1.94, +-3,-1.7, +-5.06,-1.96, +-5.32,-1.54, +-4.84,-1.15, +-2.92,-1.42, +-1.87,-2.63, +-2.33,-2.33, +-2.1,-2.77, +-2.24,-3.22, +-2.34,-4.06, +-2.33,-5, +-1.94,-5.7, +-1.72,-5.75, +-2.06,-5.66, +-1.93,-5.7, +-2.2,-5.6, +-2.54,-5.44, +-3.01,-5.22, +-2.94,-5.1, +-2.71,-4.69, +-2.53,-4.38, +-2.53,-4.38, +-2.53,-4.38, +-2.53,-4.38, +-2.63,-4.3, +-3.26,-4.52, +-3.87,-4.62, +-3.71,-4.19, +-4.47,-2.71, +-5.09,-0.92, +-5.15,0.11, +-4.23,0.93, +-4.26,0.31, +-4.64,0, +-4.29,0.18, +-2.85,0.58, +-3.34,3.78, +-4.07,3.56, +-4,3.35, +-3.25,3.54, +-2.02,3.5, +-1.85,3.11, +-2.91,2.84, +-3.31,2.27, +-3.37,0.42, +-2.07,1.76, +-0.33,3, +0.65,2.83, +0.86,2, +1.18,1.71, +-0.32,-1.37, +0.14,-2.02, +-0.26,-0.4, +2.43,0.39, +1.04,1.79, +2.78,1.4, +3.62,0.75, +0.86,2.63, +-2.72,-0.59, +1.31,-4.15, +1.88,-5.23, +1.22,-5.89, +1.49,-6.07, +-0.39,-5.95, +-1.04,-5.91, +-2.16,-5.54, +1.84,-4.61, +-0,-4.92, +-3.99,-2.92, +-4.82,-0.96, +-1.39,3.98, +0,5.22, +0.58,5.87, +1.09,6.21, +1.22,6.89, +2.05,7.67, +2.85,8.33, +3.06,8.4, +2.84,7.79, +3.4,9.34, +2.37,8.21, +1.85,9.25, +1.63,9.24, +1.6,9.08, +0.88,10.98, +0,11.57, +0,7.11, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.28,-10.05, +-7.41,-10.69, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.77,-11.65, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.48,-12.32, +-4.6,-12.65, +-2.89,-13.61, +-2.43,-13.78, +-2.43,-13.78, +-2.43,-13.78, +-1.92,-13.85, +-0.19,-13.99, +-0,-14, +-0,-14, +0.15,-13.99, +1.76,-13.86, +2.43,-13.78, +2.43,-13.8, +2.55,-14.48, +2.59,-14.72, +4.02,-14.38, +5.1,-14.02, +5.02,-13.78, +4.87,-13.38, +4.8,-13.17, +4.79,-13.15, +4.79,-13.15, +4.87,-13.12, +6.31,-12.44, +6.91,-11.98, +6.73,-11.65, +6.54,-11.32, +6.51,-11.28, +6.33,-10.96, +6.21,-10.75, +6.03,-10.44, +6.32,-10.26, +7.62,-9.33, +7.75,-9.23, +7.73,-9.21, +8.23,-9.81, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.07,-9.62, +8.14,-8.84, +8.92,-8.07, +9.23,-7.75, +9.23,-7.75, +9.17,-7.69, +8.49,-7.12, +8.49,-7.12, +8.47,-7.1, +7.66,-6.4, +8.17,-5.57, +8.59,-4.96, +7.93,-4.58, +7.74,-4.47, +8.11,-3.7, +8.4,-3.06, +8.23,-2.76, +8.23,-1.45, +8.06,-1.42, +7.9,-0.9, +7.97,0, +7.97,0, +8,-2.78, +8.83,-4.43, +9.13,-4.39, +9.6,-5.54, +10,-4.66, +10.78,-2.5, +10.02,3.35, +9.83,3.87, +8.77,3.42, +8.28,5.42, +4.44,4.3, +-0.49,-0.82, +0.49,-7.93, +-0.2,-9.84, +3.59,-5.88, +3.62,-5.18, +4.49,-3.96, +5.23,-2.87, +5.84,-2.54, +5.54,-2.65, +4.99,-3.34, +4.62,-3.87, +4.77,-3.66, +5.29,-2.54, +5.17,-1.35, +5.37,0.15, +4.02,-0.32, +2.67,-1.72, +-0.18,-1.13, +-4.98,-2.62, +-5.5,-2.42, +-5.25,-3.18, +-3.98,-2.85, +-2.71,-2.33, +-2.25,-2.68, +-2.54,-3.03, +-1.64,-2.4, +-1.65,-3.56, +-1.46,-4.39, +-1.37,-5.1, +-0.85,-5.67, +-0.57,-5.84, +-0.72,-5.6, +-0.97,-5.52, +-1.22,-5.39, +-1.65,-5.34, +-1.96,-5.39, +-2.03,-5.22, +-2.07,-4.59, +-2.3,-4.48, +-2.18,-4.54, +-2.33,-4.47, +-2.25,-3.9, +-2.28,-3.96, +-2.67,-4.62, +-3.04,-4.41, +-3.07,-3.66, +-3.44,-3.09, +-4.67,-1.31, +-5.14,-0.38, +-5.14,0.35, +-5.51,0.38, +-5.01,-0.55, +-4.7,0.71, +-3.18,1.23, +-3.48,3.25, +-4.06,3.4, +-3.44,3.48, +-1.94,3.71, +-0.67,3.12, +-0.25,2.86, +-2.43,1.57, +-2.89,-0.29, +-2.76,-0.36, +-1.48,1.3, +-0.22,3.06, +0.56,3.2, +0.65,2.08, +1.01,1.66, +1.45,-1.67, +2.21,-0.41, +-0.71,1.18, +2.79,1.18, +0.81,2.52, +2.02,2.52, +3.42,1.87, +2.04,1.8, +2.49,-0.09, +3.56,-3.31, +2.36,-5.34, +1.1,-6.25, +2.17,-5.97, +1.17,-5.88, +0.3,-5.97, +0.01,-5.5, +2.43,-4.42, +1.23,-4.84, +-2.36,-4.34, +-3.69,-2.98, +-0.55,4.29, +0,5.7, +0.9,5.95, +1.12,6.34, +1.93,7.08, +2.73,7.49, +3.06,8.4, +3.06,8.4, +2.85,7.83, +3.05,7.87, +2.96,8.14, +2.74,9.55, +2.05,10.02, +1.41,8.01, +1.02,9.6, +0,9.48, +0,6.39, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.78,-10.45, +-6.6,-11.23, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.49,-11.29, +-5.32,-11.86, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.42,-12.25, +-2.83,-12.91, +-2.36,-13.37, +-2.4,-13.63, +-2.43,-13.76, +-2.4,-13.79, +-0.64,-13.96, +-0,-13.95, +-0,-13.87, +-0,-13.92, +0.82,-13.95, +2.43,-13.78, +2.43,-13.78, +2.43,-13.81, +2.47,-14, +3.19,-14.51, +5.04,-14.09, +5.12,-14.07, +5.1,-14.02, +5.04,-13.86, +4.95,-13.6, +4.81,-13.22, +4.86,-13.12, +6.84,-12.2, +7,-12.12, +7,-12.12, +6.8,-11.78, +6.57,-11.38, +6.51,-11.28, +6.49,-11.24, +6.27,-10.6, +7.58,-9.47, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.8,-10.49, +8.6,-10.25, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.34,-9.94, +7.82,-9.33, +8.38,-8.61, +9.23,-7.75, +9.23,-7.75, +9.21,-7.73, +8.57,-7.19, +8.49,-7.12, +8.47,-7.11, +7.87,-6.6, +8.25,-5.45, +8.59,-4.96, +8.35,-4.82, +7.81,-4.51, +8.26,-3.38, +8.4,-3.06, +8.46,-2.87, +8.81,-1.55, +8.81,-1.55, +8.56,-1.21, +8.16,0, +8.03,0, +8.06,-3.06, +8.91,-4.26, +9.44,-4.31, +9.69,-5.35, +10.79,-2.18, +10.95,0.46, +9.47,5.47, +8.44,4.87, +7.53,4.35, +8.86,4.83, +4.68,6.23, +-0.59,2, +-0.19,-6.77, +3.32,-8.93, +5.49,-4.79, +5,-4.23, +5.39,-2.66, +6.07,-1.47, +6.88,-1.06, +6.61,-1.12, +6,-1.64, +5.65,-1.65, +5.24,-1.89, +5.85,-0.91, +5.22,0, +4.31,-0.77, +-1.55,-3.75, +-1.3,-2.27, +-4.4,-3.3, +-4.7,-3.09, +-4.62,-3.87, +-4.14,-3.81, +-2.93,-2.86, +-2.08,-3.11, +-2.05,-3.04, +-1.58,-2.9, +-1.23,-2.63, +-1.1,-3.91, +-0.62,-5.43, +-0.3,-6, +0.35,-6.21, +0.38,-5.44, +0.15,-5.87, +-0,-5.78, +-0.57,-5.84, +-1.05,-5.93, +-1.72,-5.75, +-1.99,-5.46, +-1.73,-4.75, +-1.73,-4.75, +-1.54,-4.23, +-1.52,-3.91, +-1.96,-3.57, +-2.32,-4.02, +-2.71,-4.69, +-2.61,-4.52, +-2.38,-3.59, +-2.94,-3.24, +-3.66,-2.41, +-4,-0.5, +-4.38,0.33, +-5.14,0.46, +-4.48,0.2, +-3.85,0.16, +-1.7,0.47, +-3.06,2.69, +-3.94,3.15, +-2.81,3.2, +-1.23,3.14, +-0.23,2.4, +-0.4,0.68, +-1.81,-0.94, +-2.74,-0.93, +-2.01,-0.61, +-1.67,-0.01, +0,3.11, +0.66,4.36, +0.79,2.51, +1.03,2.27, +0.93,-1.41, +0.46,0.15, +-1.37,1.98, +2.03,2.98, +-0.54,3.71, +2.08,3.18, +4.13,1.8, +3.78,0.51, +4.24,-1.33, +4.57,-2.89, +1.94,-5.39, +0.84,-6.48, +2.96,-6.04, +2.34,-6.44, +0.9,-5.95, +1.72,-5.61, +2.17,-4.68, +0.76,-4.95, +-0.87,-5.59, +0.3,-5.73, +1.27,2.14, +0,5.81, +0.45,5.99, +1.17,6.65, +2.42,7.24, +2.73,7.49, +3.19,8.21, +3.79,7.45, +4.26,7.38, +4.26,7.38, +3.78,8.06, +3.41,9.38, +3.15,9.28, +1.76,9.95, +0.92,9.31, +0,8.21, +0,6.69, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.65,-11.19, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.38,-11.35, +-4.8,-12.1, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-3.23,-12.6, +-2.27,-12.89, +-2.27,-12.85, +-2.33,-13.23, +-2.36,-13.4, +-1.09,-13.86, +-0,-13.48, +-0,-13.1, +-0,-13.39, +0.66,-13.77, +2.27,-13.81, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.66,-14.27, +4.8,-14.17, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.11,-14.04, +5.05,-13.88, +5.13,-13.37, +6.98,-12.43, +7,-12.12, +7,-12.12, +6.99,-12.1, +6.91,-11.97, +6.63,-11.48, +6.51,-11.28, +7.09,-10.83, +8.11,-9.72, +7.98,-9.51, +7.81,-9.31, +7.75,-9.23, +8.84,-10.54, +9,-10.72, +8.79,-10.48, +8.47,-10.09, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.12,-9.68, +7.76,-9.25, +8.73,-8.26, +9.23,-7.75, +9.23,-7.75, +8.86,-7.43, +8.49,-7.12, +8.49,-7.12, +8.36,-7.01, +8.52,-5.3, +8.59,-4.96, +8.59,-4.96, +8.5,-4.75, +8.68,-3.18, +8.4,-3.06, +8.49,-2.74, +8.81,-1.55, +8.81,-1.55, +8.81,-1.53, +8.9,-0.57, +8.91,0, +8.94,-2.75, +9.21,-3.61, +9.38,-3.87, +10.18,-4.29, +10.86,0.41, +10.54,4.09, +9.6,5.54, +7.14,5.26, +6.31,4.1, +6.8,5.5, +3.83,6.89, +1.93,4.31, +3.87,-5.06, +5.05,-8.08, +7.13,-3.48, +6.49,-3.24, +6.47,-1.57, +6.94,-0.72, +7.02,-0.87, +6.45,-2.34, +6.67,-0.08, +5.75,-1.8, +4.46,-1.56, +5.42,-2.13, +1.37,-3.14, +1.73,-3.69, +-2.39,-3.86, +-2.5,-4.5, +-4.6,-3.73, +-4.99,-4.63, +-3.83,-3.72, +1.07,-2.66, +-1.56,-3.36, +-1.83,-3.47, +-1.23,-3.69, +-1.17,-3.74, +-0.59,-3.52, +-0.27,-5.25, +0.52,-5.98, +0.99,-6.26, +1.41,-6, +1.59,-5.68, +1.05,-5.93, +0.65,-5.97, +-0.13,-6.01, +-0.78,-5.96, +-1.55,-5.8, +-1.95,-5.36, +-1.73,-4.75, +-1.73,-4.75, +-1.74,-4.5, +-1.69,-3.88, +-1.88,-4.01, +-1.79,-4.59, +-2.33,-4.74, +-2.65,-4.59, +-2.65,-4.15, +-2.95,-3.62, +-3.04,-2.71, +-3.65,-1.57, +-4.06,-0.24, +-4.14,0.73, +-5.38,0.05, +-2.58,0.97, +-0.38,0.75, +-2.89,2.33, +-3.21,1.71, +-2.29,2.6, +-0.99,2.72, +-0.48,1.87, +-0.55,1.46, +-1.75,-1.09, +-2.32,-1.54, +-1.79,-0.75, +-1.67,-0.96, +0.43,1.97, +0.23,2.65, +0.17,1.93, +0.88,2.45, +-0.05,0.32, +-2.15,-0.37, +-0.66,2.93, +1.6,2.93, +0.25,3.86, +2.91,3, +2.62,2.96, +3.25,1.24, +3.67,-1.1, +4.21,-1.83, +0.95,-5.26, +0.88,-6.67, +3.5,-6.06, +2.2,-6.63, +1.49,-5.79, +2.1,-5.03, +1.78,-4.88, +1.26,-5.56, +0.13,-5.89, +3.12,-4.56, +0,5.05, +0,6.16, +0.3,6.14, +1.22,6.89, +2.63,7.23, +2.77,7.62, +3.5,7.74, +4.23,7.32, +4.05,7.01, +4.49,7.77, +4.48,7.77, +3.84,8.04, +3.18,8.74, +2.19,8.79, +0.57,8.23, +-0.15,7.11, +-0.41,4.79, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.49,-11.24, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.47,-11.3, +-5.2,-11.92, +-4.48,-12.23, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.08,-12.36, +-2.46,-12.78, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-1.99,-13.12, +-0.17,-13.12, +-0,-13.02, +-0,-13.02, +0.03,-13.37, +1.82,-13.86, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.44,-13.86, +4.33,-14.13, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.21,-14.03, +7.21,-13.01, +7.3,-12.65, +7.05,-12.21, +7,-12.12, +7,-12.12, +6.94,-12.02, +6.74,-11.67, +7.8,-10.4, +8.37,-9.98, +8.37,-9.98, +8.28,-9.87, +8.01,-9.55, +8.68,-10.35, +9,-10.72, +9,-10.72, +8.93,-10.65, +8.5,-10.13, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +7.98,-9.51, +8.12,-8.88, +9.2,-7.78, +9.23,-7.75, +9.22,-7.73, +8.57,-7.19, +8.49,-7.12, +8.49,-7.12, +9.14,-5.44, +8.59,-4.96, +8.59,-4.96, +8.71,-4.71, +9.32,-3.39, +8.86,-3.22, +8.51,-2.91, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +8.84,-1.27, +8.6,-0.15, +8.51,-1.32, +9.32,-3.39, +9.47,-3.45, +10.11,-4.45, +10.87,1.5, +11.33,4.12, +10.58,4.73, +7.55,5.15, +3.58,6.65, +3.27,7.55, +3.57,9.18, +7.09,8.79, +13.38,-4.26, +7.98,-5.47, +3.14,-8.82, +2.28,-8.62, +8.04,-1.26, +3.06,-5.89, +3.33,-4.7, +1.84,-5.82, +0.7,-5.29, +0.03,-4.99, +1.12,-5.02, +-1.55,-4.12, +-5.58,-2.83, +-2.03,-3.63, +-3.82,-2.91, +-4.2,-4.06, +-5.47,-3.46, +-3.84,-3.66, +3.53,-0.96, +0.92,-2.69, +-1.16,-3.2, +-1.26,-3.72, +-0.84,-4.24, +-0.55,-5.13, +-0,-5.18, +1.05,-5.93, +1.5,-6.15, +2.34,-6.42, +2.17,-5.97, +2.12,-5.81, +1.24,-6.13, +1.1,-6.25, +0.26,-6, +-0.45,-5.99, +-1.05,-5.93, +-1.77,-5.62, +-1.82,-5.01, +-1.73,-4.75, +-1.49,-4.1, +-1.4,-3.84, +-1.75,-4.38, +-2.03,-4.61, +-2.22,-5.59, +-2.83,-4.9, +-2.65,-4.29, +-3.53,-4.05, +-3.28,-3.15, +-3.45,-2.89, +-3.91,-0.95, +-4.3,0.58, +-3.42,1.73, +-0.08,2.08, +2.52,-1.54, +-2.72,0.62, +-2.88,-0.19, +-2,0.99, +-1.34,1.74, +0.31,2.4, +-0.35,2.47, +-1.82,-0.62, +-2.28,-1.57, +-2.21,-1.28, +-1.65,-0.82, +-1.16,-0.28, +-0.14,1.91, +0,1.94, +0.29,1.91, +0.5,1.79, +-2.59,-0.9, +-1.45,2.8, +-0.01,3.08, +-1.35,3.7, +1.72,3.96, +2.71,2.56, +3.11,1.51, +2.95,-0.59, +3.77,-0.76, +3.24,-3.2, +-0.42,-6.05, +1.7,-6.64, +1.46,-6.25, +1.96,-5.47, +2.62,-4.31, +2.35,-4.86, +1.63,-5.78, +0.52,-5.98, +0.9,-5.95, +0.32,2.36, +-0.12,6.34, +0,6.58, +1.24,7.05, +2.18,7.38, +2.87,7.88, +3.62,7.34, +4.15,7.18, +3.99,6.9, +3.99,6.9, +4.39,7.6, +4.99,8.65, +4.6,11.18, +2.66,10.2, +0.2,7.47, +-0.96,6.31, +-0.76,4.31, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.41,-11.11, +-6.21,-10.76, +-6.5,-11.26, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.29,-11.4, +-5.47,-11.79, +-4.61,-12.18, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.41,-12.26, +-3.12,-12.62, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.23,-12.83, +-1.08,-12.95, +-0,-13.02, +-0,-13.02, +-0,-13.05, +1.51,-13.73, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +4.72,-14.03, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.44,-13.93, +7.45,-12.98, +7.47,-12.94, +7.44,-12.88, +7.17,-12.42, +7.01,-12.14, +7,-12.12, +7.19,-11.97, +8.26,-10.61, +8.48,-10.11, +8.37,-9.98, +8.37,-9.98, +8.36,-9.96, +9,-10.72, +9,-10.72, +9,-10.72, +9,-10.72, +8.86,-10.56, +8.48,-10.1, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.26,-9.85, +7.9,-9.42, +9.09,-7.9, +9.23,-7.75, +9.23,-7.75, +8.99,-7.54, +8.49,-7.12, +8.51,-7.1, +9.45,-5.68, +8.8,-5.08, +8.59,-4.96, +8.88,-4.35, +9.32,-3.39, +9.32,-3.39, +8.92,-2.91, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +8.58,-0.87, +7.91,-0.69, +7.89,-0.94, +9.19,-3.34, +9.35,-3.66, +11.55,-4.21, +11.33,-2.06, +11.44,4.16, +11.9,4.33, +6.99,6.32, +-0.74,5.93, +1.17,8.34, +5.75,9.81, +12.88,11.13, +16.71,2.43, +9.12,-0.12, +0.43,-8.31, +4.35,-7.75, +7.15,-2.43, +3.64,-5.52, +5.6,-2.26, +6.68,-0.38, +2.09,-1.82, +3.03,-2.85, +1.64,-3.42, +-0.94,-2.32, +-5.48,-2.42, +-5.63,-2.37, +-5.52,-2.45, +-5.22,-4.41, +-3.13,-4.58, +0.87,-2.76, +0.74,-4.13, +1.23,-3.68, +-0.56,-3.91, +-0.63,-4.3, +-0.31,-5.03, +0.3,-5.86, +0.89,-5.7, +1.41,-6.12, +2.23,-6.62, +2.47,-6.78, +2.54,-6.97, +2.36,-6.47, +1.99,-6.4, +1.2,-6.79, +0,-6.67, +-0.26,-6.25, +-1.07,-6.07, +-1.43,-5.83, +-1.82,-5.01, +-1.73,-4.75, +-1.49,-3.79, +-1.4,-3.84, +-1.7,-4.36, +-2.05,-4.9, +-2.17,-5.61, +-2.93,-5.08, +-2.86,-4.44, +-3.79,-3.56, +-3.58,-3.55, +-3.66,-3.86, +-3.92,-3.32, +-3.17,0.27, +-1.53,2.95, +1.57,2.68, +2.61,-2.44, +-2.57,-1.34, +-2.76,-0.92, +-2.35,-0.23, +-1.67,0.91, +-1.56,1.53, +-1.19,1.95, +-2.65,0.86, +-3.46,-1.31, +-2.28,-1.32, +-1.69,-0.84, +-0.8,-1.19, +-0.78,0.6, +0.05,1.91, +0.38,2.15, +0.11,2.67, +-2.86,-1.66, +1.65,2.43, +-1.35,2.69, +-0.83,3.66, +0.83,2.85, +2.12,2.67, +2.6,1.55, +0.65,-0.93, +1.83,-2.18, +2.7,0.42, +3.29,-3.55, +0.15,-5.89, +1.25,-6.43, +3.36,-4.08, +3.15,-3.92, +2.65,-4.29, +2.16,-5.38, +1.48,-5.82, +1.05,-5.93, +-0.4,2.27, +-0.75,6.28, +0,6.67, +1.14,7.59, +1.49,7.45, +2.82,7.75, +3.08,7.46, +4.05,7.02, +3.74,6.48, +4.3,7.45, +4.3,7.45, +5.22,9.04, +5.48,10.83, +4.01,12.73, +-0.63,11.83, +-2.2,6.8, +-1.84,5.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.41,-11.11, +-6.14,-10.64, +-6.06,-10.49, +-6.4,-11.09, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.46,-11.31, +-6.42,-11.33, +-6.36,-11.36, +-6.38,-11.35, +-5.13,-11.95, +-4.76,-12.11, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-3.4,-12.55, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-1.26,-12.94, +-0.03,-13.02, +-0,-13.02, +-0,-13.02, +0.34,-13.33, +2.25,-13.44, +2.43,-13.76, +2.43,-13.78, +2.46,-13.8, +3.29,-13.9, +5.09,-14.08, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.21,-14.03, +7.18,-13.13, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.39,-12.79, +7.17,-12.42, +7.31,-11.92, +9,-10.72, +8.93,-10.64, +8.48,-10.11, +8.37,-9.98, +7.99,-10.27, +8.71,-10.63, +9,-10.72, +9,-10.72, +9,-10.72, +9,-10.72, +8.87,-10.57, +8.46,-10.08, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.62,-9.72, +9.44,-7.92, +9.23,-7.75, +9.23,-7.75, +9.2,-7.72, +8.49,-7.12, +8.49,-7.12, +9.41,-5.83, +8.82,-5.09, +8.59,-4.96, +8.61,-4.91, +9.31,-3.41, +8.95,-3.26, +8.54,-3.08, +8.74,-1.86, +8.81,-1.55, +8.59,-1.52, +7.83,0, +6.37,-1.51, +6.95,-0.52, +8.59,-1.65, +9.63,-3.5, +12.58,-4.58, +11.55,-4.21, +10.94,3.76, +11.45,4.7, +7.89,5.07, +0.35,6.3, +1.66,5.8, +8.41,7.06, +15.51,10.93, +17.48,8.22, +8.02,3.22, +4.22,-4.27, +3.42,-7.42, +4.46,-5.29, +-2.21,-7.23, +-1,-4.84, +0.74,2.6, +-4.44,-3.68, +-6.62,-3.62, +-7.38,-2.55, +-7.33,-3.63, +-6.06,-3.5, +-3.5,-2.52, +-2.71,-2.73, +-2.94,-4.01, +-1.95,-4.78, +-1.34,-5.18, +0.07,-5.16, +2.73,-4.36, +0.61,-4.4, +0.46,-4.55, +1.23,-4.94, +0,-6.88, +1.65,-6.49, +2.25,-6.62, +2.81,-7.14, +3.27,-7.24, +3,-7.23, +2.49,-6.84, +2.39,-6.58, +1.7,-6.62, +0.68,-6.72, +-0.15,-6.85, +-1.36,-6.32, +-1.92,-5.7, +-1.77,-4.85, +-1.59,-4.36, +-1.4,-3.84, +-1.76,-3.66, +-1.78,-4.06, +-1.73,-4.75, +-2.01,-5.53, +-2.09,-5.11, +-2.72,-4.41, +-3.38,-4.02, +-3.06,-3.84, +-2.62,-3.7, +-2.08,-4.07, +-2.7,-1.64, +-1.62,2.97, +-0.41,3.43, +1.55,-1.65, +-1.89,-1.61, +-1.21,-2.77, +-1.59,-2.65, +-2.14,-0.32, +-2.19,1.62, +-1.95,1.97, +-2.54,1.45, +-3.34,0.26, +-2.17,-1.25, +-1.6,-1.09, +-0.9,-1.52, +0.17,-1.38, +0.42,2.01, +0.46,2.6, +-0.86,2.49, +-2.55,-1.71, +0.47,1.35, +1.16,1.9, +-0.46,2.07, +-0.1,2.48, +0.5,2.23, +0.3,1.27, +-1.83,-1.15, +-1.29,-1.57, +1.8,-0.91, +2.87,-2.53, +0.85,-4.66, +1.29,-6.53, +3.91,-3.8, +4.12,-2.89, +2.89,-4.12, +2.85,-4.94, +2.48,-5.34, +1.63,-5.78, +-0.76,4.3, +-0.93,6.43, +0,7.28, +0.54,7.37, +1.3,7.37, +2.73,7.49, +2.73,7.49, +3.77,6.53, +3.57,6.18, +3.64,6.3, +4.16,7.21, +4.42,7.66, +4.43,7.67, +5.19,9.51, +-1.31,8.2, +-4.33,6.81, +-2.48,4.79, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.51,-11.28, +-6.51,-11.28, +-6.48,-11.22, +-6.15,-10.65, +-6.03,-10.44, +-6.03,-10.44, +-6.18,-10.7, +-6.5,-11.26, +-6.51,-11.28, +-6.31,-11.39, +-6.09,-11.5, +-4.8,-12.1, +-4.72,-12.13, +-4.67,-12.15, +-4.56,-12.2, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.29,-12.29, +-3.07,-12.63, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.07,-12.86, +-0.25,-13.02, +-0,-13.02, +-0,-13.02, +0.03,-13.02, +1.23,-12.99, +2.39,-13.54, +2.43,-13.78, +2.43,-13.78, +2.82,-13.82, +5.08,-13.96, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.36,-13.97, +7.41,-13.01, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.45,-12.9, +7.84,-11.8, +9,-10.72, +9,-10.72, +8.96,-10.68, +8.58,-10.22, +8.25,-10.07, +9.4,-11.2, +9.16,-10.91, +9.07,-10.8, +9,-10.72, +9,-10.72, +9,-10.72, +8.94,-10.66, +8.5,-10.13, +8.37,-9.98, +8.37,-9.98, +8.82,-9.54, +9.81,-8.23, +9.23,-7.75, +9.23,-7.75, +9.21,-7.73, +8.55,-7.18, +8.49,-7.12, +9.19,-5.9, +8.61,-4.97, +8.59,-4.96, +8.59,-4.96, +8.69,-3.51, +8.4,-3.06, +8.4,-3.06, +8.41,-1.96, +8.17,-1.44, +7.92,-0.59, +7.59,0.17, +6,-0.3, +6.98,0.15, +8.57,-0.69, +9.45,-3.44, +11.78,-4.29, +10.83,-0.05, +6.67,6.81, +6.81,5.79, +5.91,5.1, +3.34,5.13, +4.81,3.73, +13.26,7.88, +18.41,10.63, +16.86,9.73, +7.82,3.94, +6.84,2.49, +3.74,-2.96, +3.97,-5.09, +-0.02,-5.24, +-4.77,-0.26, +-5.49,1.41, +-6.73,-3.05, +-5.96,-2.44, +-5.22,-3.01, +-5.64,-3.26, +-4.4,-3.3, +-3.5,-3.79, +-3.01,-4.88, +-1.25,-5.24, +0.73,-5.64, +1.55,-5.52, +3.67,-3.33, +4.3,-2.43, +2.18,-3.95, +1.62,-5.03, +-1.26,-6.65, +-1.61,-6.79, +1.42,-6.6, +2.87,-6.36, +3.81,-6.87, +4.4,-7.62, +3.82,-7.65, +2.92,-7, +2.9,-6.8, +1.55,-6.93, +0.69,-6.94, +-0,-7, +-0.69,-6.94, +-1.72,-6, +-1.92,-4.84, +-2.13,-4.56, +-1.44,-3.97, +-1.47,-3.8, +-2.07,-3.83, +-2.54,-4.59, +-2.76,-4.83, +-1.89,-5.21, +-2.67,-4.62, +-2.67,-4.62, +-2.23,-4.52, +-0.83,-4.3, +0.49,-3.79, +1.88,-1.17, +1.08,2.33, +-0.92,3.06, +-0.38,-1.98, +-2.43,-0.68, +-1.41,-2.78, +-1.5,-3.61, +-2.16,-2.68, +-2.9,0.63, +-2.6,1.88, +-2.37,1.66, +-2.86,1.17, +-1.73,1.44, +-1.8,-1.37, +-1.6,-1.1, +-0,-0.67, +0.64,2.43, +0.2,3.29, +-0.06,3.75, +-1.2,1.19, +-0.04,2.67, +0.17,1.92, +0.2,1.76, +-0.04,1.89, +0.51,2, +0.98,2.43, +-1.07,1.07, +-0.67,-1.1, +0.61,-0.4, +1.36,0.91, +2.49,-1.46, +2.79,-4.29, +3.6,-4.69, +3.96,-3.13, +3.78,-3.34, +3.18,-4.64, +3.28,-4.53, +-0.1,0.29, +-1.16,3.56, +-1.16,6.57, +-0.52,7.23, +0.3,7.34, +1.33,7.53, +2.39,7.58, +2.91,7.41, +3.85,6.66, +3.68,6.38, +3.92,6.8, +3.68,6.38, +3.09,5.36, +3.54,5.53, +4.56,5.56, +-2.74,3.43, +-4.58,4.37, +-2.49,3.14, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.51,-11.28, +-6.45,-11.17, +-6.13,-10.62, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.05,-10.47, +-6.45,-11.17, +-6.42,-11.33, +-5.16,-11.93, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4,-12.38, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.2,-12.84, +-1.03,-12.96, +-0,-13.02, +-0,-13, +-0,-13.02, +1.03,-12.96, +2.28,-12.95, +2.38,-13.49, +2.43,-13.78, +2.83,-13.7, +4.78,-13.45, +5.07,-13.94, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.26,-14.01, +7.25,-13.09, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.94,-12.57, +9.3,-11.09, +9.03,-10.76, +9,-10.72, +9,-10.72, +8.72,-10.39, +9.37,-11.17, +9.62,-11.47, +9.54,-11.37, +9.32,-11.11, +9,-10.72, +9,-10.72, +9,-10.72, +9,-10.72, +8.67,-10.33, +8.37,-9.98, +9.06,-9.3, +9.98,-8.37, +9.63,-8.08, +9.23,-7.75, +9.23,-7.75, +8.89,-7.46, +8.49,-7.12, +8.8,-6.7, +8.64,-4.99, +8.59,-4.96, +8.49,-4.9, +8.09,-3.83, +8.4,-3.06, +8.4,-3.06, +8.45,-2.89, +7.9,-0.79, +7.08,-0.29, +6.98,0.15, +6.58,0.61, +6.94,0.61, +7.87,1.19, +9.21,-2.1, +10.25,-3.73, +8.42,4.7, +6.07,5.07, +5.19,5.18, +5.88,4.48, +4.74,4.81, +8.65,4.9, +20.89,11.56, +18.52,10.69, +15.54,8.97, +6.42,2.34, +5.97,2.17, +4.65,1.94, +3.97,1.71, +-0.97,0.07, +-3.49,-1.93, +-5.19,-2.07, +-5.5,-2.63, +-5.94,-2.72, +-5.66,-2.52, +-5.53,-2.84, +-3.91,-4.09, +-2.52,-4.76, +-0.44,-4.51, +1.06,-3.66, +0.37,-3.75, +0.93,-5.86, +0.66,-4.99, +-1.14,-4.19, +0.7,-5.93, +0.9,-7.73, +-0,-7.81, +0.19,-7.26, +1.54,-7.27, +3.83,-6.98, +4.31,-7.46, +4.37,-7.91, +4,-7.96, +4.33,-7.11, +4.13,-6.42, +2.69,-6.58, +0.86,-7.65, +-0.15,-7.47, +-1.22,-6.89, +-1.43,-6.4, +-1.94,-5.32, +-1.86,-4.38, +-1.94,-3.55, +-1.4,-3.84, +-1.8,-3.65, +-1.73,-4.75, +-1.83,-4.7, +-1.73,-4.75, +-2.64,-4.56, +-2.58,-4.96, +-1.3,-4.84, +0.64,-4.39, +2.54,-3.34, +2.94,-2.23, +2.38,-1.85, +-2.78,-2.64, +-0.96,-3.38, +-1.59,-2.39, +-0.66,-3.23, +-1.4,-3.84, +-1.72,-3.69, +-3.11,-2.38, +-3.46,0.43, +-3.49,0.7, +-3.07,1.46, +-2.39,1.63, +-2.55,-0.22, +-1.91,-1.32, +-0.03,-1.4, +-0.43,2.19, +0.02,4.36, +-0.71,4.16, +-0.79,3.97, +-0.19,2.74, +-0.08,2.47, +0.03,2.32, +-0.48,2.14, +0.1,2.62, +-1.35,0.82, +0.86,-1.57, +-0.36,1.22, +-1.38,1.17, +0.28,-1.26, +3.2,1.14, +4.09,-1.18, +4.65,-1.92, +3.68,-3.41, +4,-3.03, +3.57,-3.48, +3.03,-4.15, +-1.35,3.69, +-1.95,5.36, +-1.29,6.44, +-0.69,7.21, +0.27,6.97, +1.38,7.85, +2.15,7.64, +2.68,7.38, +3.28,6.29, +3.57,6.18, +5.09,8.81, +5.11,8.86, +4.79,7.86, +5.57,6.64, +5.58,3.89, +-2.71,1.66, +-5.38,3.63, +-3.53,3.27, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.51,-11.28, +-6.43,-11.13, +-6.06,-10.49, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.04,-10.46, +-6.41,-11.11, +-6.15,-11.47, +-4.64,-12.16, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.17,-12.33, +-2.35,-12.81, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.25,-12.74, +-2.14,-12.48, +-0.64,-12.21, +-0,-12.77, +-0,-12.98, +0.26,-13.02, +2.01,-12.86, +2.29,-12.99, +2.41,-13.66, +2.49,-13.77, +3.98,-13.39, +4.91,-13.49, +5.11,-14.04, +5.12,-14.07, +5.12,-14.07, +5.39,-13.96, +7.31,-13.06, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +8.06,-12.59, +9.61,-11.45, +9.31,-11.09, +9.01,-10.74, +9,-10.72, +9,-10.72, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.44,-11.25, +9.02,-10.75, +9,-10.72, +9,-10.72, +9,-10.72, +8.55,-10.19, +8.82,-9.55, +9.98,-8.37, +9.59,-8.05, +9.23,-7.75, +9.23,-7.75, +8.72,-7.32, +8.49,-7.12, +8.22,-6.77, +8.47,-5.15, +8.59,-4.96, +8.03,-4.64, +8.06,-3.81, +8.4,-3.06, +8.4,-3.06, +8.53,-2.61, +7.92,-0.52, +7.28,0, +7.34,0.3, +6.93,0.76, +7.33,0.35, +7.41,1.62, +8.86,-0.65, +9.6,-2.86, +7.2,3.91, +5.36,4.5, +5.36,4.5, +6.59,3.8, +5.25,4.6, +6.66,1.26, +13.96,-1.36, +17.48,7.7, +15.4,8.41, +7.66,2.56, +5.3,1.8, +4.22,1.67, +3.62,1.75, +0.34,1.23, +-3,0.32, +-3.74,-1.55, +-3.83,1.61, +-4.16,0.62, +-2.71,-3.57, +-1.02,-5.71, +-0.4,-5.52, +0.39,-4.65, +0.04,-3.87, +-2.62,-3.16, +-3.2,-3.23, +-1.7,-5.88, +-0.67,-5.78, +-0.15,-6.79, +1.63,-7.25, +2.37,-7.59, +2.35,-8.37, +3.33,-7.36, +3.96,-6.86, +5.14,-6.49, +3.89,-7.39, +4.68,-8.1, +4.93,-8.11, +6.05,-7.66, +6.11,-5.58, +3.29,-6.7, +0.84,-7.53, +-0,-7.65, +-1.1,-7.12, +-2.66,-5.39, +-2.02,-5.55, +-1.52,-4.18, +-2,-3.82, +-1.4,-3.67, +-1.67,-3.71, +-1.65,-4.52, +-2.41,-4.43, +-2.03,-4.61, +-2.37,-4.1, +-1.89,-5.21, +-0.7,-4.55, +1.51,-3.82, +3.4,-2.24, +3.71,-1.63, +3.39,-2.06, +1.97,-2.77, +0.78,-2.78, +-2.29,-1.43, +-1.31,-3.45, +-1.44,-4.25, +-1.77,-4.6, +-2.27,-3.52, +-3.46,-2.1, +-3.69,-1.72, +-3.77,0.31, +-3.06,1.34, +-2.75,0.8, +-2.77,-0.83, +-3.3,-0.86, +-2.45,2.89, +-1.52,4.05, +-1.6,3.8, +-1.76,3.65, +-1.32,2.91, +-0.11,3.08, +-0.92,1.68, +-1.49,1.37, +-2.35,1.84, +-1.7,2.45, +-2.68,-3.5, +-1.42,2.78, +-1.23,2.95, +-1.46,-0.07, +0.31,-2.1, +3.61,0.55, +3.88,0.92, +4.21,-1.36, +4.47,-2.2, +3.08,-3.85, +4.38,-1.75, +-0.08,2.96, +-2.1,5.78, +-1.41,6.46, +-0.35,7.11, +0.2,7.63, +1.24,7.01, +2.32,7.46, +2.63,7.23, +2.58,6.49, +3.2,6.08, +3.54,6.44, +4.69,8.13, +4.96,7.73, +4.06,4.43, +6.16,2.5, +-1.93,1.14, +-5.53,3.2, +-4.67,3.27, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.13,-10.62, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.19,-10.72, +-6.48,-11.22, +-5.15,-11.94, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-3.75,-12.45, +-2.26,-12.83, +-2.26,-12.8, +-2.26,-12.83, +-2.25,-12.78, +-2.2,-12.49, +-2.12,-12.01, +-2.1,-11.89, +-1.18,-11.97, +-0.11,-12.07, +-0,-12.44, +0.06,-12.56, +1.33,-12.84, +2.26,-12.83, +2.31,-13.08, +2.4,-13.62, +3.93,-13.4, +4.79,-13.15, +5.03,-13.81, +5.12,-14.07, +5.12,-14.07, +5.27,-14, +7.37,-13.02, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.49,-12.98, +7.48,-12.96, +8.07,-12.58, +9.62,-11.47, +9.6,-11.44, +9.37,-11.17, +9.01,-10.74, +9,-10.72, +9.43,-11.24, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.54,-11.37, +9.11,-10.86, +9,-10.72, +9,-10.72, +8.95,-10.67, +8.48,-9.93, +9.14,-9.24, +9.84,-8.26, +9.23,-7.75, +9.23,-7.75, +8.66,-7.26, +8.49,-7.12, +7.87,-6.61, +7.8,-6.1, +8.46,-4.88, +7.77,-4.48, +7.89,-4.17, +8.4,-3.06, +8.4,-3.06, +8.51,-2.71, +7.81,-0.22, +7.65,0, +6.98,0.17, +6.9,1.06, +6.93,0.76, +6.8,1.55, +7.88,0.61, +8.02,1.46, +6.21,4.73, +5.64,4.73, +6.63,4.58, +6.75,3.67, +4.99,3.91, +4.17,-0.69, +10.85,-6.26, +15.03,-1.45, +14.77,6.4, +6.51,1.15, +4.76,0.84, +4.29,1.56, +3.13,0.59, +0.36,0.73, +-2.95,-0.11, +-3.29,-0.2, +-3.08,1.91, +-2.71,2.1, +-1.66,-1.53, +0.35,-5.8, +1.5,-5.81, +1.03,-5.84, +0.43,-5.52, +0.89,-2.72, +0.02,-3.58, +0.29,-6.09, +3.16,-4.23, +2.47,-7.41, +0.79,-7.9, +-0.32,-7.09, +2.81,-8.1, +4.65,-7.02, +5.54,-5.68, +4.11,-6.78, +3.75,-7.78, +4.73,-8.19, +6.37,-8.78, +7.49,-7.93, +6.36,-6.82, +4.55,-6.61, +1.16,-7.04, +-0.39,-6.75, +-1.3,-6.37, +-1.62,-6.66, +-2.78,-5.51, +-1.85,-5.09, +-1.4,-3.84, +-1.54,-3.45, +-2.03,-3.65, +-2.26,-4.1, +-2.14,-4.44, +-1.84,-4.7, +-2.19,-4.8, +-1.53,-5.38, +-1.08,-4.76, +0.25,-4.19, +3.46,-2.21, +3.86,-1.36, +3.78,-2.08, +2.98,-2.77, +2.39,-2.53, +1.54,-1.99, +-0.48,-3.73, +-0.64,-4.49, +-0.7,-4.73, +-1.18,-4.52, +-1.53,-3.69, +-2.06,-3.05, +-2.23,-2.15, +-3.76,0.31, +-3.99,0.81, +-4.03,-0.25, +-4.6,-0.48, +-4.62,0.69, +-3.4,3.38, +-2.81,2.94, +-2.03,3.52, +-1.95,3.53, +-1.76,3, +-2.78,-0.22, +-3.42,0.12, +-3.84,2.24, +0,3.41, +1.61,-3.03, +-1.81,0.2, +1.02,-0.9, +-0.56,-2.32, +-0.02,-2.59, +2.77,1.67, +3.23,2.26, +3.01,2.54, +2,2.1, +-0.91,-4.1, +-0.95,0.99, +-2.77,4.8, +-2.27,6.23, +-1.5,6.67, +-0.14,6.99, +0.46,7.61, +1.24,7.01, +2.15,6.93, +2.54,6.98, +2.44,6.71, +2.5,6.88, +2.39,6.58, +2.82,5.92, +3.5,6.06, +4.52,5.74, +5.93,2.74, +-7.31,4.04, +-5.48,3.36, +-4.76,2.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.04,-10.46, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.23,-10.79, +-6.2,-11.45, +-4.7,-12.14, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.27,-12.3, +-2.3,-12.75, +-2.16,-12.23, +-2.12,-12.04, +-2.13,-12.09, +-2.1,-11.91, +-2.09,-11.87, +-2.09,-11.87, +-2,-11.88, +-0.19,-12.05, +-0,-12.05, +-0,-12.05, +0.63,-12.28, +2.25,-12.76, +2.26,-12.83, +2.38,-13.49, +3.47,-13.53, +4.76,-13.16, +4.86,-13.36, +5.12,-14.07, +5.12,-14.07, +5.28,-14, +7.28,-13.07, +7.48,-12.96, +7.48,-12.96, +7.52,-13.02, +7.78,-13.47, +7.53,-13.04, +8.23,-12.47, +9.62,-11.47, +9.62,-11.47, +9.58,-11.42, +9.21,-10.98, +9,-10.72, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.53,-11.36, +9.14,-10.89, +9,-10.72, +9,-10.72, +8.51,-10.15, +8.45,-9.91, +9.13,-9, +9.17,-7.82, +9.21,-7.73, +8.49,-7.12, +8.47,-7.1, +7.65,-6.42, +7.59,-6.37, +7.92,-5, +7.74,-4.47, +7.87,-4.23, +8.4,-3.06, +8.4,-3.06, +8.68,-2.04, +7.8,-0.58, +6.96,0.46, +6.94,0.61, +6.89,1.22, +6.53,1.15, +6.58,2.39, +6.77,2.14, +7.23,3.78, +6.01,4.2, +6.8,4.35, +8.28,3.65, +6.88,3.17, +4.75,3.63, +0.53,-4.16, +3.49,-6.55, +7.12,-3.44, +4.88,-2.98, +4.95,-0.05, +4.28,0.55, +3.42,0.73, +1.65,-1.6, +0.78,-1.72, +-2.45,-1.11, +-2.57,0.34, +-0.4,3.6, +-1.4,1.04, +-1.83,-2.95, +-1.99,-5.46, +-0.69,-5.74, +1.22,-7.7, +3.67,-4.81, +3.92,-3.29, +2.47,-5.47, +3.09,-4.97, +3.45,-2.92, +2.15,-5.02, +1.71,-8.73, +1.63,-8.13, +3.13,-7.9, +4.65,-7.02, +3.56,-5.51, +3.97,-6.88, +5.67,-6.76, +6.33,-6.89, +7.61,-6.39, +7.93,-6.98, +6.06,-7.22, +4.56,-7.01, +2.61,-6.74, +1.55,-6.8, +-0.49,-7.03, +-1.67,-6.63, +-2.38,-5.87, +-1.95,-5.36, +-1.47,-4.04, +-1.4,-3.84, +-1.76,-3.93, +-1.4,-3.84, +-1.48,-3.8, +-1.68,-4.62, +-2.2,-5.46, +-1.04,-5.46, +-0.11,-5.04, +0.8,-3.98, +2.23,-3.12, +3.3,-2.49, +3.56,-3.34, +3.45,-3.3, +3.24,-3.63, +1.78,-2.41, +0.89,-3.9, +0.26,-4.48, +0.44,-5.02, +0.46,-4.84, +1.02,-3.81, +1.28,-3.73, +-0,-3.96, +-2.08,-3.28, +-3.92,-1.56, +-4.84,-0.4, +-5.39,-0.95, +-5.22,-0.83, +-3.69,1.34, +-3.13,2.62, +-2.09,3.43, +-2.81,3.17, +-0.92,2.52, +-2.8,-1.29, +-4.45,-1.46, +-0.65,3.91, +1.04,3.4, +4.02,-2.18, +3.9,-0.98, +2.54,-3.06, +1.85,-0.64, +1.78,-0.88, +2.41,1.47, +2.48,2.81, +-0.42,4.02, +-3.38,4.02, +-4.29,3.09, +-3.41,3.91, +-3.04,5.05, +-2.11,5.79, +-1.19,6.73, +-0.35,6.97, +0.65,7.18, +1.22,6.89, +2.27,6.74, +2.39,6.58, +2.36,6.47, +2.44,6.71, +2.39,6.58, +2.48,6.38, +3.31,6.62, +3.55,5.85, +2.3,4.84, +-5.92,4.41, +-5.58,4.42, +-3.89,2.88, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.26,-10.9, +-5.34,-11.85, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-3.95,-12.4, +-2.14,-12.16, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-1.82,-11.91, +-0.03,-12.05, +-0,-12.05, +-0,-12.05, +0.35,-12.04, +2.08,-12.35, +2.25,-12.78, +2.31,-13.1, +2.58,-13.73, +4.53,-13.24, +4.82,-13.24, +5.1,-14.02, +5.12,-14.07, +5.42,-13.94, +7.34,-13.04, +7.48,-12.96, +7.48,-12.96, +7.79,-13.49, +7.97,-13.8, +7.82,-13.54, +8.05,-12.72, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.58,-11.42, +9.16,-10.91, +9.48,-11.3, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.55,-11.38, +9,-10.72, +9,-10.72, +8.83,-10.52, +8.37,-9.98, +8.32,-9.91, +7.86,-9.13, +8.48,-8.46, +8.53,-7.16, +8.36,-7.01, +7.61,-6.39, +7.59,-6.37, +7.47,-4.95, +7.74,-4.47, +7.83,-4.32, +8.39,-3.08, +8.4,-3.06, +8.66,-2.14, +7.51,-0.44, +7,0, +6.89,1.22, +6.89,1.22, +6.49,1.3, +6.88,2.32, +7.1,3.55, +7.22,4.2, +5.92,2.92, +3.27,-1.65, +7.47,-0.58, +6.87,3.49, +2.18,5.18, +-2.78,1.92, +0.97,-3.88, +1.37,-3.48, +1.52,-3.46, +3.45,-1.98, +4.07,-0.18, +3.5,-0.93, +1.58,-2.71, +-0.3,-3.72, +-1.54,-1.98, +-1.82,1.5, +-1,3.01, +-2.52,-1.31, +-2.56,-4.52, +-3.22,-5.58, +-2.69,-5.56, +0.96,-6.31, +3.26,-5.21, +3.17,-5.24, +1.64,-6.53, +3.37,-4.94, +2.4,-4.93, +0.35,-6.83, +2.63,-9.69, +4.2,-8.66, +4.72,-7.09, +5.11,-7.13, +4.23,-7.32, +4.33,-6.32, +5.01,-5.53, +5.99,-3.81, +6.48,-4.59, +5.68,-7.36, +4.08,-8.15, +3.7,-7.44, +2.67,-7.33, +1.66,-6.77, +0.99,-6.63, +-1.18,-6.72, +-1.84,-6.58, +-1.72,-5.86, +-1.13,-5.41, +-1.16,-4.55, +-1.58,-4.05, +-2.17,-4.16, +-1.94,-4.12, +-1.95,-5.36, +-1.07,-6.09, +-0.53,-5.22, +0.44,-5.18, +0.32,-4.62, +1.23,-3.09, +2.33,-1.65, +2.53,-3.99, +2.55,-4.63, +2.29,-4.46, +1.93,-3.34, +0.79,-4, +0.73,-4.16, +1.33,-4.61, +2.41,-4.43, +2.62,-3.13, +2.88,-2.88, +2.25,-3.37, +0.44,-4.15, +-2.76,-3.7, +-5.02,-2.67, +-5.57,-1.87, +-5.33,-1.41, +-4.24,-0.59, +-3.31,2.15, +-3.15,3.44, +-3.08,3.5, +-3.63,-0.43, +-4,-1.75, +-4.7,-1.84, +0.24,3.86, +0.7,2.89, +3.85,0.76, +3.26,-1.94, +2.64,-0.14, +0.22,-2.47, +1.93,-1.71, +2.64,0.46, +1.25,2.52, +-5.02,5.98, +-7.37,7.6, +-5.43,5.46, +-3.93,4.91, +-3.08,5.34, +-2.06,5.66, +-1.25,6.44, +0,7, +0.93,7.04, +1.22,6.89, +1.95,6.69, +2.39,6.58, +2.16,5.92, +2.3,6.32, +2.1,5.78, +2.49,6.84, +3.14,8.62, +3.01,9.34, +-0.62,6.77, +-4.36,5.66, +-5.8,6.06, +-5.28,4.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-5.52,-11.26, +-4.55,-12.2, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.19,-12.32, +-4.05,-12.37, +-2.9,-12.68, +-2.89,-12.68, +-2.71,-12.73, +-2.25,-12.43, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2,-11.86, +-0.26,-11.97, +-0,-11.9, +-0,-11.95, +-0,-11.92, +1.07,-11.98, +2.16,-12.27, +2.26,-12.83, +2.41,-13.35, +4.44,-13.26, +4.79,-13.17, +4.99,-13.7, +5.11,-14.04, +5.21,-14.03, +7.31,-13.06, +7.48,-12.96, +7.51,-13, +7.9,-13.68, +7.97,-13.8, +7.97,-13.8, +8.45,-13.09, +9.68,-11.54, +9.64,-11.48, +9.62,-11.47, +9.62,-11.47, +9.57,-11.41, +9.56,-11.39, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.61,-11.45, +9.03,-10.76, +9,-10.72, +8.97,-10.68, +8.4,-10.01, +8.37,-9.98, +7.97,-9.49, +8.23,-8.75, +8.8,-7.39, +8.49,-7.12, +7.91,-6.64, +7.59,-6.37, +7.6,-5.42, +7.74,-4.47, +7.93,-4.1, +8.4,-3.06, +8.45,-2.91, +8.8,-1.57, +8.59,-1.21, +6.32,0.85, +6.97,1.38, +6.77,1.19, +6.97,1.87, +7.14,3.19, +7.71,4.45, +6.45,4.1, +5.88,1.52, +4.19,-3.39, +6.63,-2.49, +5.71,1.54, +3.72,4.15, +2.13,2.37, +1.67,-1.97, +1.66,-4.04, +0.82,-3.66, +3.72,-2.15, +4.02,-0.53, +3.62,-1.47, +2.45,-4.18, +1.06,-4.57, +0.23,-3.57, +-1.61,-0.89, +-3.09,-1.36, +-3.56,-4.07, +-2.51,-4.88, +-1.02,-4.95, +-0.29,-5.16, +-2.3,-5.65, +-2.11,-4.52, +0.43,-4.78, +3,-5.65, +3.59,-4.76, +1.88,-6.68, +1.36,-8.47, +3.79,-8.98, +5.02,-8.7, +5.02,-7.36, +4.49,-7.43, +5.72,-7.54, +5.88,-5.86, +5.99,-4.09, +5.74,-3.32, +5.43,-4.06, +3.27,-7.93, +2.2,-9.17, +2.31,-7.85, +2.73,-7.49, +2.48,-6.8, +1.64,-6.03, +1.22,-6.03, +0.3,-6.28, +-0,-6.03, +-0,-5.33, +-0,-6.16, +-1.41,-5.69, +-1.49,-4.96, +-1.72,-4.22, +-1.54,-5.43, +-0.3,-5.48, +0.59,-5, +0.83,-5.69, +0.76,-5.15, +0.27,-4.41, +-0.38,-3.82, +0.61,-4.04, +0.75,-4.87, +1.09,-4.92, +0.12,-4.07, +0.62,-4.03, +1.05,-3.93, +1.8,-3.65, +2.43,-3.6, +2.75,-3, +3,-2.75, +2.76,-2.97, +1.86,-4.23, +-1.42,-4.61, +-3.99,-3.68, +-4.53,-3.15, +-4.21,-2.34, +-3.77,-1.5, +-4.32,1.25, +-3.23,3.85, +-1.45,4.44, +-0.69,1.06, +-2.71,-3.21, +1.74,-1.22, +0.24,4.26, +0.75,3.77, +2.73,1.58, +3.09,0.16, +1.82,-1.25, +1.78,-2.83, +3.44,0.23, +2.12,1.49, +-0.07,3.2, +-5.14,6.12, +-5.86,4.91, +-5.17,4.51, +-4.11,5.34, +-2.92,5.66, +-2.12,5.81, +-1.09,6.17, +0,6.88, +0.76,6.93, +1.22,6.89, +1.38,6.85, +2.17,5.97, +2.11,5.79, +2.06,5.66, +2.06,5.66, +2.25,6.17, +1.78,5.91, +1.26,6.25, +-0.89,5.92, +-2.7,5.96, +-3.54,5.99, +-3.54,6.13, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-5.96,-10.47, +-4.85,-11.62, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-3.91,-12.4, +-3.51,-12.52, +-2.35,-12.81, +-2.41,-12.8, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.16,-12.25, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-1.69,-11.56, +-0.07,-11.17, +-0,-11.08, +-0,-11.17, +-0,-11.27, +1.6,-11.84, +2.1,-11.9, +2.24,-12.7, +2.47,-12.88, +4.44,-12.96, +4.79,-13.15, +4.82,-13.24, +5.09,-13.97, +5.41,-13.95, +7.45,-12.98, +7.48,-12.96, +7.48,-12.96, +7.72,-13.37, +7.97,-13.8, +7.97,-13.8, +8.67,-13.34, +10.19,-12.14, +10.03,-11.95, +9.7,-11.57, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.09,-10.83, +9,-10.72, +9,-10.72, +8.5,-10.13, +8.37,-9.98, +8.22,-9.79, +8.29,-8.9, +9.05,-7.59, +8.53,-7.15, +8.22,-6.9, +7.59,-6.37, +8.36,-4.91, +7.74,-4.47, +8.28,-3.33, +8.4,-3.06, +8.71,-1.94, +9.3,-1.64, +8.3,-0.11, +7.32,1.11, +7.01,1.08, +8.01,1.19, +7.48,1.94, +7.31,3.76, +7.25,5.18, +4.79,6.67, +3.4,3.23, +4.02,-0.9, +6.73,-1.8, +5.73,-0.42, +3.89,2.98, +3.64,-1.09, +2.87,-1.71, +2.31,-3.26, +1.79,-4.16, +4.71,-2.69, +5.06,-1.49, +3.85,-1.68, +2.09,-4.54, +1.72,-5.75, +1.59,-4.07, +1.11,-3.35, +-0.15,-3.74, +0.69,-3.9, +1.18,-4.04, +2.14,-4.71, +1.12,-7.24, +0.76,-5.14, +2.27,-1.91, +4,-1.7, +4.22,-5.11, +1.03,-6.87, +0.67,-8.02, +2.05,-8.67, +3.1,-8.5, +3.65,-9.19, +5.06,-8.38, +6.27,-7.47, +6.98,-6.98, +8.06,-5.22, +7.96,-4.88, +5.99,-3.83, +3.93,-5.8, +0.92,-8.21, +0.46,-7.82, +1.49,-6.98, +2.7,-7.77, +2.9,-6.7, +3.29,-5.92, +2.1,-5.77, +1.22,-6.22, +0.9,-5.81, +0.49,-5.23, +0.15,-6.01, +-0.15,-6.5, +-0.6,-5.97, +-0.7,-6.45, +-0,-6.11, +0.93,-5.54, +2.03,-4.61, +2.31,-4.74, +1.08,-5.57, +0.25,-4.89, +-0.81,-4.6, +-0.81,-3.99, +-0.07,-4.4, +0.45,-4.72, +-0.47,-4.04, +0.53,-4.02, +1.64,-3.72, +2.43,-3.26, +2.78,-3.31, +3.42,-3.02, +3.16,-2.76, +2.37,-3.3, +1.82,-4.1, +-0.47,-5.02, +-2.76,-5.29, +-3.53,-4.48, +-3.12,-2.97, +-3.3,-2.37, +-3.07,0.12, +-1.88,4.31, +-0.47,4.57, +0.21,2.71, +0.46,-2.66, +1.24,-2.26, +0.5,2.91, +0.73,2.69, +2.82,1.72, +2.96,-0.71, +2.37,-3.29, +2.92,-0.57, +3.01,0.12, +2.29,0.4, +0.1,2.95, +-3.98,4.51, +-4.62,3.87, +-3.59,3.34, +-3.77,5.08, +-3.02,5.48, +-1.68,5.76, +-1.05,5.93, +0,6.3, +0.82,6.51, +1.15,6.53, +1.22,5.89, +2.11,5.79, +2.16,5.92, +2.23,6.12, +1.95,5.36, +2.01,5.53, +1.05,5.93, +0.6,5.97, +-0.77,6.1, +-1.86,7.43, +-1.83,8, +-0.72,5.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-5.59,-10.66, +-4.35,-11.79, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.43,-12.25, +-3.58,-12.5, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.16,-12.25, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.06,-11.7, +-1.94,-11.01, +-0.94,-11.02, +-0,-11.08, +-0,-11.08, +-0,-11.08, +0.45,-11.18, +1.96,-11.78, +2.13,-12.08, +2.26,-12.8, +4.24,-12.38, +4.71,-12.95, +4.79,-13.15, +4.93,-13.56, +5.26,-14.01, +7.25,-13.09, +7.48,-12.96, +7.48,-12.96, +7.65,-13.25, +7.97,-13.8, +7.97,-13.8, +8.44,-13.5, +10.25,-12.21, +10.25,-12.21, +9.76,-11.64, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.16,-10.92, +9,-10.72, +9,-10.72, +8.89,-10.59, +8.37,-9.98, +8.37,-9.98, +8.5,-9.35, +9.23,-7.75, +8.87,-7.44, +8.49,-7.12, +7.92,-6.62, +8.31,-5.37, +8,-4.6, +8.24,-3.42, +8.42,-2.99, +8.77,-1.82, +7.51,-1.32, +2.62,0.46, +7.28,1.85, +6.98,1.2, +8.57,0.5, +8.29,1.86, +7.31,2.48, +7.4,3, +6.78,5.24, +5.13,4.55, +5.55,1.52, +7.19,-0.63, +6.58,-0.59, +4.9,-0.24, +4.23,-4.95, +4.45,-3.35, +2.75,-4.42, +2.66,-4.36, +5.2,-2.49, +4.54,-2.74, +2.5,-4.16, +1.22,-5.46, +1.11,-6.29, +-0.13,-5.44, +-2.04,-4.32, +-0.89,-3.21, +0.08,-3.74, +0.53,-5.43, +-0.19,-6.64, +-0.37,-7.35, +3.38,-5.05, +5.61,-0.46, +6.23,-0.51, +2.51,-6.8, +1.87,-8.61, +2.39,-8.44, +2.96,-8.65, +3.39,-9.32, +4.43,-8.83, +5.9,-7.92, +7.26,-6.37, +7.65,-5.27, +8.39,-3.95, +7.84,-4.27, +7,-4.69, +4.83,-6.44, +2.22,-7.85, +1.5,-7.39, +1.23,-6.22, +2.3,-6.75, +3.63,-6.79, +3.5,-6.06, +2.88,-6.35, +1.58,-6.63, +1.01,-5.72, +0.93,-5.3, +1.29,-5.87, +0.99,-6.49, +0.87,-6.27, +0.97,-6.5, +1.88,-6.17, +2.92,-5.05, +3.38,-4.37, +3.05,-4.4, +1.93,-5.68, +0.42,-5.57, +-0,-5.18, +-0.5,-5.01, +-0.09,-4.92, +0.4,-4.43, +-0.6,-4.01, +0.7,-3.99, +1.87,-3.6, +2.69,-3.06, +3.29,-3.07, +3.72,-3.4, +3.32,-3.77, +1.72,-3.69, +1.18,-4.26, +-0.67,-5.68, +-0.92,-5.07, +-1.95,-3.69, +-2.24,-3.4, +-2.17,-2.92, +-1.99,-0.58, +-1.04,3.87, +0.09,4.07, +1.19,3.06, +1.76,-1.35, +1.73,-0.94, +1.04,1.75, +0.02,2.13, +2.08,0.93, +2.99,-1.51, +3.12,-1.86, +2.56,0.49, +2.69,-0.91, +1.92,2.24, +1.9,1.94, +-3.01,4.16, +-4.02,2.98, +-3.08,2.19, +-3.75,5.15, +-2.94,5.79, +-1.81,5.73, +-0.78,5.96, +0,6.03, +0.52,5.98, +1.07,6.05, +1.05,5.93, +2.11,6.8, +2.11,5.79, +2.82,7.75, +2.06,5.66, +0.96,5.46, +1.02,5.8, +0.62,6.25, +-0.43,6.72, +-1.27,7.21, +-1.07,8.07, +-0.19,7.35, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-5.69,-10.61, +-4.3,-11.74, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-3.58,-12.5, +-2.46,-12.79, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.25,-12.75, +-2.13,-12.06, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.03,-11.51, +-1.88,-10.92, +-0.87,-11.02, +-0,-11.08, +-0,-11.05, +-0,-10.84, +0.19,-11.07, +1.92,-11.24, +2.09,-11.84, +2.32,-12.29, +4.2,-12.3, +4.65,-12.77, +4.79,-13.15, +4.79,-13.17, +5.23,-13.71, +7.41,-13.01, +7.48,-12.96, +7.48,-12.96, +7.67,-13.28, +7.97,-13.8, +7.97,-13.8, +8.93,-13.17, +10.25,-12.21, +10.25,-12.21, +10.03,-11.95, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.46,-11.27, +9.01,-10.74, +9,-10.72, +9,-10.72, +8.52,-9.95, +8.82,-9.54, +9.74,-8.43, +9.23,-7.75, +8.85,-7.43, +8.51,-7.06, +8.53,-5.69, +8.59,-4.96, +8.34,-3.91, +8.41,-3.03, +8.76,-1.89, +5.28,-0.93, +0,0, +2.83,0.5, +8.26,1.46, +7.17,0.56, +8.28,1.26, +8.74,1.04, +7.71,1.36, +6.92,0.71, +8.17,0.35, +7.57,1.28, +6.06,1.52, +8.75,2.43, +7.77,0.67, +4.06,-2.28, +3.63,-6.43, +4.52,-3.33, +3.22,-4.84, +2.14,-5.28, +3.4,-3.91, +2.57,-3.18, +1.42,-4.94, +1.1,-6.25, +0.52,-6.95, +-0.37,-6.43, +-2.3,-6, +-3.5,-5.37, +-3.84,-6.17, +-2.68,-6.6, +-1.28,-6.42, +1.6,-5.69, +4.33,-3.12, +7.28,0.67, +5.16,-1.97, +2.84,-7.8, +3.65,-8.08, +4.47,-7.74, +3.77,-8.99, +3.74,-9.15, +5.16,-8.44, +6.34,-6.95, +7.19,-5.27, +7.69,-2.66, +8.03,-2.29, +7.34,-3.04, +6.29,-4.8, +5.11,-8.07, +4.61,-9.45, +3.88,-8.71, +3.29,-7.23, +2.67,-6.45, +2.76,-5.6, +3.39,-5.61, +2.71,-6.43, +2.27,-7.11, +1.3,-6.2, +1.09,-4.92, +1.3,-5.53, +1.32,-6.61, +1.64,-6.78, +2.09,-5.74, +3.14,-5.13, +4.03,-4.62, +4.51,-3.98, +3.46,-4.64, +1.35,-6.65, +0.48,-6.52, +0.32,-5.84, +0.33,-5.03, +0.63,-5, +1.09,-4.27, +-0.13,-3.72, +1.1,-2.83, +2.49,-2.96, +3.06,-2.69, +3.52,-3.14, +3.38,-3.37, +2.52,-4.36, +0.99,-4.07, +0.6,-4.84, +0.19,-5.08, +-0.26,-4.84, +-0.88,-3.97, +-1.3,-3.84, +-0.96,-3.19, +0.46,-1.66, +1.02,1.82, +0.27,2.75, +0.41,2.17, +1.68,0.99, +1.94,1.07, +1.32,1.38, +0.22,1.9, +2.32,-0.82, +3.4,-1.51, +3.04,-0.42, +1.95,0.55, +1.81,0.98, +1.28,3.2, +1.21,2.39, +-1.7,2.83, +-3.07,1.16, +-2.61,1.22, +-3.58,4.11, +-2.6,5.94, +-1.58,6.36, +-0.72,5.55, +0,5.9, +0.33,5.03, +0.88,4.98, +0.9,5.11, +1.74,6.46, +2.1,5.78, +2.34,6.42, +1.7,5.62, +0.96,5.46, +1,5.7, +0.52,5.98, +0,6.03, +-0.12,6.99, +-0.77,6.51, +-0.3,6.14, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-5.55,-10.69, +-4.41,-11.82, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.36,-12.27, +-3.64,-12.48, +-3.56,-12.5, +-2.82,-12.7, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.22,-12.56, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.04,-11.57, +-1.76,-10.93, +-0.17,-11.07, +-0,-10.94, +-0,-10.45, +-0,-10.18, +0.15,-10.73, +1.76,-10.94, +1.99,-11.29, +2.19,-11.82, +4.07,-11.86, +4.47,-12.27, +4.75,-13.06, +4.79,-13.15, +5.04,-13.39, +7.17,-13.02, +7.48,-12.96, +7.48,-12.96, +7.57,-13.11, +7.87,-13.64, +7.97,-13.8, +8.31,-13.59, +10.25,-12.21, +10.25,-12.21, +10.21,-12.17, +9.81,-11.69, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.5,-11.32, +9.09,-10.84, +9.25,-10.48, +9.7,-9.55, +9.98,-8.37, +9.86,-8.27, +9.33,-7.62, +9.21,-6.83, +9.39,-5.71, +8.65,-4.99, +8.84,-4.45, +9.01,-3.28, +7.34,-2.13, +0,0, +0,0, +0,0, +2.98,0, +8.67,-0.33, +8.6,-0.26, +7.97,1.12, +8.71,1.11, +8.19,1.86, +6.49,-3.18, +8.79,-1.55, +8.35,-1.21, +6.69,2.17, +11.39,4.1, +4.37,2.25, +0.91,-6.34, +4.51,-7.58, +5.66,-3.78, +2.57,-6.04, +1.6,-7.07, +2.46,-4.26, +1.27,-3.8, +0.42,-4.9, +0.15,-6.57, +-0,-7.36, +-0.92,-7.33, +-0.15,-6.93, +-0,-6.84, +1.72,-6.94, +2.78,-6.91, +3.5,-5.9, +5.14,-4.82, +6.86,-1.95, +7.51,-0.11, +5.2,-4.22, +4.95,-6.9, +5.75,-6.85, +5.11,-7.97, +4.57,-8.77, +4.94,-8.55, +6.1,-7.28, +6.1,-6.67, +6.43,-4.17, +6.26,-2.07, +5.73,-0.17, +6.52,-1.04, +5.62,-3.62, +4.94,-6.53, +4.97,-8.61, +5.25,-9.09, +5.78,-8.57, +4.26,-7.38, +3.15,-5.46, +3.08,-5.34, +2.64,-6.31, +2.48,-6.8, +2.31,-6.35, +2.02,-5.55, +1.34,-5.35, +1.17,-5.9, +2.2,-6.5, +3.25,-5.02, +4.09,-4.4, +4.69,-4.39, +4.96,-3.38, +3.98,-4.51, +2.22,-5.94, +1.17,-6.62, +0.92,-5.95, +1.51,-5.38, +2.47,-4.89, +2.12,-4.42, +0.4,-3.45, +1.33,-2.55, +2.81,-2.53, +3.24,-2.46, +3.1,-3.49, +2.11,-4.69, +1.02,-5.29, +1.3,-4.51, +1,-4.66, +0.85,-4.8, +-0.16,-4.72, +0.29,-4.7, +0.27,-4.04, +0.55,-3.17, +2.33,-1.88, +2.36,0.91, +0.89,1.85, +1.04,2.19, +1.15,1.56, +1.64,1.28, +2.16,1.23, +0.64,1.76, +1.54,-0.95, +3.67,-1.97, +2.79,-1.96, +1.15,-2.44, +1.49,-1.18, +1.91,1.58, +-1.51,4.55, +-3.9,7.41, +-6.25,2.7, +-3.15,1.37, +-3.17,3.68, +-2.25,5.04, +-1.03,5.81, +-0.27,5.31, +0,5.05, +0.48,5.25, +0.88,4.98, +0.98,4.95, +1.44,4.83, +2.06,5.66, +1.91,5.26, +1.49,4.81, +0.91,5.14, +0.94,5.34, +0.32,6.36, +0,6.51, +-0.57,5.84, +-0.98,6.79, +-0.96,4.61, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6,-10.45, +-4.99,-10.99, +-4.4,-12.08, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.37,-12.27, +-4.14,-12.34, +-4.43,-12.25, +-4.24,-12.31, +-2.71,-12.73, +-2.41,-12.8, +-2.37,-12.8, +-2.36,-12.81, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.8, +-2.16,-12.23, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.84, +-1.96,-11.1, +-1.8,-10.93, +-0.13,-11.08, +-0,-10.81, +-0,-9.94, +-0,-9.91, +0.48,-10.11, +1.78,-10.76, +1.93,-10.94, +2.09,-11.42, +4.02,-11.59, +4.45,-12.22, +4.6,-12.65, +4.78,-13.13, +4.82,-13.14, +6.17,-13.06, +7.47,-12.94, +7.48,-12.96, +7.48,-12.96, +7.82,-13.54, +7.97,-13.8, +8.67,-13.35, +10.25,-12.21, +10.25,-12.21, +10.25,-12.21, +9.86,-11.75, +9.62,-11.47, +9.61,-11.45, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.83,-11.71, +9.73,-11.6, +9.62,-11.47, +9.62,-11.47, +9.78,-11.05, +10.3,-9.48, +10.67,-8.95, +9.98,-8.37, +10.39,-7.76, +10.12,-6.48, +9.72,-5.61, +9.32,-5.33, +6.44,-2.92, +6.21,-2.26, +8.22,-2.1, +1.06,-0.19, +0,0, +0,0, +0,0, +3.3,0, +9.36,-0.8, +9.16,-1.26, +8.9,0, +9.93,1.75, +9.8,2.98, +7.6,-0.45, +9.29,-2.11, +7.56,-1.84, +2.4,0.96, +12.79,-0.79, +7.05,5.06, +-1.93,-3.56, +5.06,-7.65, +5,-4.87, +2.53,-6.31, +1.16,-7.47, +2.83,-4.9, +1.86,-3.75, +0.79,-4.73, +0.78,-6.93, +0.59,-7.92, +-0,-7.09, +1.4,-6.24, +4.67,-5.77, +6,-5.62, +4.99,-6.49, +5.74,-5.49, +6.8,-3.47, +7.06,-2.36, +7.21,0.72, +6.48,-2.56, +6.07,-5.35, +5.87,-6.39, +4.5,-7.83, +4.31,-8.75, +4.83,-7.94, +5.46,-7.31, +6.06,-5.95, +5.7,-4.22, +3.91,-1.92, +3.2,-0.14, +2.99,-0.38, +3.4,-2.43, +5.17,-5.28, +5.81,-7.87, +5.04,-8.73, +4.96,-8.59, +4.4,-7.62, +3.61,-6.25, +2.67,-5.53, +3.18,-6.34, +2.62,-6.62, +2.31,-6.35, +2.11,-5.79, +1.83,-5.85, +1.67,-5.1, +2.34,-5.67, +3.75,-5.15, +4.98,-3.73, +5.13,-3.14, +5.33,-2.78, +4.58,-3.84, +3.44,-4.92, +1.75,-6.58, +1.62,-6.03, +2.54,-5.44, +3.26,-4.9, +2.66,-4.11, +1.15,-3.54, +1.76,-2.32, +3.02,-2.53, +2.64,-3.35, +1.76,-4.95, +0.47,-6.96, +-0.02,-6.31, +1.31,-4.54, +0.5,-4.43, +0.13,-4.35, +0.81,-5.23, +1.45,-5.33, +1.76,-4.31, +2.18,-2.58, +2.77,-0.85, +2.22,-0.07, +2.03,0.12, +1.45,1.26, +0.98,0.82, +1.91,1.2, +1.87,1.24, +1.24,1.64, +1.23,2.41, +1.88,0.17, +2.43,-0.46, +2.47,0.65, +2.65,0.48, +2.18,-0.4, +0.58,0.55, +-3.18,7.15, +-7.24,6.15, +-4.64,2.2, +-2.45,3.23, +-1.27,4.59, +-0.73,4.99, +0,5.16, +0.46,5.01, +0.81,4.57, +0.83,4.13, +1.37,4.34, +1.73,4.75, +2.21,5.06, +2.3,4.79, +1.85,4.83, +0.84,4.74, +0.93,5.3, +0,6.27, +0,8.12, +-0.99,10.22, +-1.38,7.85, +-1.02,4.67, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-5.82,-10.55, +-4.45,-11.24, +-4.42,-12.16, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.43,-12.25, +-3.36,-12.56, +-2.34,-12.81, +-3.28,-12.58, +-2.66,-12.74, +-2.29,-12.82, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.24,-12.69, +-2.1,-11.91, +-2.09,-11.87, +-2.08,-11.8, +-1.89,-11.01, +-1.26,-10.99, +-0.03,-11.02, +-0,-10.19, +-0,-9.91, +-0,-9.91, +1.03,-9.84, +1.78,-10.07, +1.89,-10.72, +2.11,-11.13, +3.87,-11.32, +4.37,-11.99, +4.46,-12.26, +4.63,-12.73, +4.79,-13.15, +5.67,-12.8, +7.34,-12.72, +7.48,-12.96, +7.48,-12.96, +7.57,-13.11, +7.95,-13.77, +8.7,-13.33, +10.25,-12.21, +10.25,-12.21, +10.25,-12.21, +9.84,-11.72, +9.62,-11.47, +9.39,-11.19, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +10.13,-12.08, +10.25,-12.21, +10.18,-12.13, +9.76,-11.64, +9.69,-11.41, +11,-10.11, +10.99,-9.22, +10.72,-9, +10.56,-7.78, +11.18,-6.57, +10.5,-6, +9.97,-5.11, +7.5,-2.88, +0,0, +0,0, +1.08,-0.19, +0,0, +0,0, +0,0, +0,0, +3.69,0, +10.66,0.02, +8.7,-1.13, +9.55,0.94, +12.14,0.6, +10.55,-0.9, +7.19,-1.18, +9.03,-3.46, +-1.64,-3.41, +-1.85,-2.38, +12,-2.27, +5.85,3.91, +0.53,-2.47, +5.54,-7.16, +3.74,-5.95, +1.48,-7.16, +2.34,-7.77, +3.38,-5.85, +2.03,-4.76, +0.77,-4.99, +0.87,-5.46, +1.53,-7.68, +2.41,-7.26, +2.95,-4.66, +5.36,-3.27, +4.31,-3.69, +2.3,-6.7, +4.17,-5.01, +4.84,-3.68, +6.2,-1.34, +6.79,1.51, +6.69,0.17, +6.84,-3.77, +5.58,-6.04, +4.06,-7.8, +4,-7.81, +4.51,-7.42, +5.75,-6.85, +6.03,-6.11, +4.77,-5.68, +2.33,-4.51, +-0.38,-1.84, +-1.28,-1.36, +0.95,-2.27, +2.82,-4.72, +5.43,-7.55, +6.01,-8.66, +5.25,-9.09, +4.85,-8.4, +4.39,-7.6, +2.94,-6.68, +2.27,-5.72, +3.22,-6.31, +2.81,-7.14, +2.31,-6.35, +2.34,-6.42, +2.43,-5.61, +3.14,-4.74, +4.37,-4.12, +5.19,-3, +5.67,-1.94, +5.96,-2.01, +5.44,-3.61, +3.91,-5.34, +3.01,-6.29, +2.89,-6.17, +3.11,-5.39, +3.56,-4.24, +3.08,-3.84, +1.76,-3.8, +2.34,-2.79, +2.53,-3.2, +1.39,-3.99, +-0,-6.19, +-0.63,-7.81, +-0.4,-6.77, +-0.33,-4.54, +0.1,-4.07, +0.63,-3.59, +0.96,-2.98, +1.98,-3.91, +2.53,-3.36, +2.41,-1.83, +2.86,-0.5, +2.79,0, +2.41,0.28, +1.9,0.76, +1.45,0.81, +1.22,0.88, +1.39,1.35, +1.19,1.87, +2.37,2.83, +2.48,3.29, +1.36,1.75, +2.11,-1.24, +2.57,0.21, +1.8,-0.57, +0.96,0.44, +-1.63,5.4, +-5.97,6.77, +-5.9,5.47, +-2.46,4.65, +-0.81,4.6, +0,5.05, +0.35,6, +1.26,6.45, +1.12,4.29, +1.4,3.84, +1.67,4.6, +1.85,5.09, +2.84,6.1, +3.45,5.98, +1.91,3.78, +1.62,5.03, +0.93,5.25, +0.27,5.72, +0,7.58, +-1.43,8.13, +-1.58,8.25, +-1.24,4.88, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-5.8,-10.56, +-4.2,-11.3, +-4.34,-11.93, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-3,-12.65, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.28,-12.95, +-2.29,-12.98, +-2.28,-12.91, +-2.29,-12.98, +-2.27,-12.87, +-2.26,-12.83, +-2.26,-12.83, +-2.18,-12.36, +-2.09,-11.87, +-1.91,-11.89, +-0.74,-11.26, +-0.14,-11.08, +-0,-10.8, +-0,-9.99, +-0,-9.91, +-0,-9.91, +1.07,-9.84, +1.72,-9.76, +1.76,-9.98, +1.94,-10.51, +3.67,-10.84, +4.19,-11.51, +4.44,-12.2, +4.46,-12.26, +4.65,-12.79, +5.52,-12.83, +7.07,-12.25, +7.26,-12.58, +7.47,-12.94, +7.5,-12.98, +7.58,-13.13, +8.49,-13.26, +10.25,-12.21, +10.25,-12.21, +10.25,-12.21, +10.17,-12.12, +9.7,-11.56, +9.37,-11.17, +9.62,-11.47, +9.66,-11.52, +10.12,-12.06, +10.2,-12.16, +10.25,-12.21, +10.25,-12.21, +10.25,-12.21, +10.46,-11.71, +11.03,-10.08, +11.33,-9.51, +10.83,-9.09, +11.21,-7.75, +11.24,-6.68, +11.09,-5.62, +10.98,-4.78, +6.58,-2.39, +2.6,-0.95, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +11.87,2.09, +7.6,0.72, +6.59,-2.93, +12.13,-1.73, +5.6,0.01, +1.48,0.23, +3.33,-0.63, +-3.28,-3.34, +1.65,-3.31, +11.09,-1.87, +6.41,-3.14, +2.7,-5.77, +2.91,-7.41, +2.96,-7.74, +3,-8.09, +4.15,-7.18, +3.39,-6.57, +2.68,-5.38, +1.84,-4.68, +0.66,-5, +2.35,-6.84, +3.76,-6.07, +5.24,-2.59, +5.17,-1.52, +3.5,-3.85, +2.38,-6.55, +3.46,-5.47, +4.11,-3.12, +5.35,-1, +6.23,1.45, +5.56,2.48, +4.81,-1.13, +4.17,-4.97, +3.39,-7.64, +3.89,-7.78, +4.55,-6.5, +5.05,-6.02, +4.12,-6.22, +1.47,-7.03, +-0.95,-6.05, +-2.28,-3, +-2.54,-1.37, +-1.94,-1.99, +1.4,-4, +3.03,-6.64, +3.06,-8.4, +4.31,-9.46, +3.49,-8.74, +3.64,-8.37, +2.77,-6.93, +2.06,-5.66, +2.28,-6.27, +2.77,-7.6, +2.58,-7.1, +2.65,-6.22, +3.44,-5.96, +3.89,-4.43, +4.82,-3.59, +5.5,-2.42, +5.93,-1.05, +6.37,-1.28, +6.28,-3.03, +4.98,-4.88, +4.08,-6.33, +4.05,-5.53, +4.05,-4.83, +3.4,-4.06, +2.94,-4.09, +2.27,-4.36, +2.19,-3.6, +1.54,-3.72, +-0.15,-4.9, +-0.7,-6.21, +-0.43,-7.12, +-0.55,-5.01, +-0,-4.06, +0.71,-4, +0.74,-2.24, +1.12,-1.83, +2.77,-3.17, +1.91,-1.66, +2.44,-1.25, +2.65,-0.39, +3.2,0.06, +3.17,0.43, +2.02,0.58, +1.79,0.74, +1.4,0.81, +1.59,1.45, +1.25,1.49, +2.06,2.1, +2.41,1.66, +2.32,0.09, +2.44,-1.32, +2.24,-1.03, +1.79,-1.07, +1.09,0.58, +1.39,0.86, +-2.39,6.64, +-3.08,6.43, +0.1,2, +1.46,2.37, +0.2,4.92, +0.81,4.57, +1.68,5.01, +1.8,4.18, +1.94,3.94, +2.93,5.26, +2.12,4.42, +3.06,5.65, +3.26,5.64, +2.22,5.13, +1.68,4.62, +0.98,4.95, +0.44,5.14, +-0.38,5.02, +-0.88,4.98, +-1.09,6.17, +-0.77,4.38, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-5.8,-10.56, +-4.4,-11.21, +-4.22,-11.6, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.43,-12.25, +-2.66,-12.74, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.33,-13.21, +-2.08,-13.73, +-1.21,-13.8, +-1.53,-13.45, +-1.32,-13.46, +-1.46,-13.11, +-1.19,-12.94, +-1.53,-12.91, +-2.2,-12.5, +-2.1,-11.93, +-0.72,-12.01, +-0,-11.62, +-0,-11.08, +-0,-10.52, +-0,-9.91, +-0,-9.91, +0.2,-9.91, +1.49,-9.75, +1.68,-9.5, +1.71,-9.72, +1.97,-10.21, +3.78,-10.6, +4.12,-11.33, +4.41,-12.11, +4.45,-12.24, +4.48,-12.32, +5.71,-12.44, +7,-12.12, +7.04,-12.19, +7.37,-12.77, +7.48,-12.96, +7.67,-12.85, +9.44,-11.88, +10.15,-12.1, +10.25,-12.21, +10.25,-12.21, +10.25,-12.21, +10.09,-12.02, +9.62,-11.47, +9.65,-11.5, +10.14,-12.08, +10.25,-12.21, +10.26,-12.23, +10.34,-12.33, +10.42,-12.35, +11.06,-11.73, +11.93,-10.07, +11.44,-9.6, +10.8,-9.06, +11.25,-7.82, +11.3,-6.48, +10.24,-4.78, +1.56,-0.79, +2.5,-0.91, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +12.61,3.06, +2.01,-2.35, +5.04,-4.33, +9.53,-1.5, +1.79,1.67, +0.14,2.99, +-3.63,-0.33, +-1.64,0.53, +4.6,-1.16, +9,-1.47, +6.28,-4.87, +3.43,-5.94, +3.51,-7.12, +3.46,-8.21, +3.54,-8.58, +3.17,-8.05, +2.65,-7.29, +2.64,-5.39, +2.33,-4.93, +0.99,-6.23, +0.8,-7.02, +3.64,-5.48, +5.76,-0.31, +4.77,0.16, +3.79,-4.4, +4,-5.94, +5.06,-5.19, +5,-3.54, +4.8,-0.14, +4.75,1.31, +3.56,2.45, +1.93,-0.92, +2.21,-3.54, +2.76,-6.74, +3.13,-7.84, +3.67,-6.57, +2.69,-6.37, +0.82,-7.53, +-0.4,-7.94, +-1.57,-6.8, +-2.45,-4.36, +-2.96,-2.74, +-1.74,-2.33, +-0.4,-4.19, +1.63,-6.45, +2.2,-8.88, +2.21,-9.52, +3.3,-9.63, +3.01,-8.27, +2.64,-7.26, +2.23,-6.12, +2.39,-6.58, +2.87,-7.88, +2.66,-6.85, +3.34,-6.13, +3.58,-5.5, +4.12,-5.18, +4.68,-3.95, +5.31,-2.47, +6.12,-0.47, +6.46,-0.61, +6.34,-3.23, +5.56,-4.21, +4.83,-5.22, +4.9,-5.13, +4.37,-4.51, +3.97,-4.52, +3.39,-4.21, +2.01,-5.53, +1.55,-4.33, +0.33,-4.49, +-1.02,-5.77, +-0.94,-6.19, +-0.15,-6.11, +0.42,-4.77, +0.93,-4.1, +1.39,-3.82, +1.24,-2.31, +1.42,-1.83, +2.76,-2.23, +2.78,-1.65, +2.95,-1.39, +2.89,-0.46, +3.08,0.37, +3.23,0.57, +2.13,0.46, +2.28,0.83, +2.09,1.09, +1.86,1.44, +1.68,1.85, +3.2,0.93, +3.68,-0.44, +3.06,-1.43, +3.34,-1.22, +2.68,-1.13, +1.84,-0.69, +0.91,0.55, +0.96,0.12, +-0.22,4.51, +-0.41,5.42, +1.19,0.36, +1.9,1.35, +2.73,2.78, +2.86,3.06, +2,4.35, +2.84,5.3, +2.04,3.54, +2.75,4.56, +2.22,3.85, +2.59,4.48, +2.53,4.38, +3.13,6.5, +1.82,5.01, +1.55,6.8, +0.63,5, +-0.29,6.22, +-0.88,4.98, +-0.75,7.24, +-0.54,5.69, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-6.14,-10.63, +-6.03,-10.44, +-6.03,-10.44, +-5.04,-10.92, +-4.15,-11.39, +-4.34,-11.91, +-4.45,-12.24, +-4.45,-12.24, +-4.43,-12.25, +-2.85,-12.69, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.24,-12.83, +-1.19,-13.47, +-0.49,-13.97, +-0,-14, +-0,-13.7, +-0,-13.75, +-0,-13.7, +-0,-13.61, +-0,-13.05, +-0.73,-12.98, +-0.46,-12.27, +-0.44,-12.03, +-0,-11.83, +-0,-11.08, +-0,-10.66, +-0,-9.91, +-0,-9.91, +0.31,-9.9, +1.72,-9.74, +1.57,-8.91, +1.6,-9.08, +1.74,-9.76, +3.19,-9.86, +3.95,-10.86, +4.21,-11.58, +4.45,-12.24, +4.45,-12.24, +5.16,-12.07, +6.84,-11.96, +7,-12.12, +7.03,-12.18, +7.15,-12.39, +7.44,-12.89, +9.29,-11.71, +9.77,-11.65, +10.21,-12.17, +10.25,-12.21, +10.25,-12.21, +10.25,-12.21, +9.8,-11.68, +9.79,-11.66, +10.25,-12.21, +10.57,-12.6, +10.79,-12.86, +11.03,-12.8, +11.85,-12.01, +12.81,-10.75, +12.11,-10.16, +11.47,-9.62, +11.34,-8.14, +11.42,-6.6, +11.61,-5.48, +9.44,-3.44, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +9.86,2.75, +4.25,-0.58, +2.01,-1.79, +3.12,-2.36, +-0.88,1.06, +-0.47,0.38, +-4.42,1.9, +0.94,3.73, +7.19,1.06, +6.69,-2.67, +5.28,-5.26, +3.93,-6.81, +4.12,-7.14, +3.37,-8.26, +4,-8.27, +4.18,-8.01, +4.13,-7.6, +4,-5.41, +3.51,-5.46, +4.19,-6.79, +4.1,-6.84, +5.72,-5.02, +6.68,-1.79, +5.97,-0.32, +5.14,-2.48, +4.87,-4.8, +6.26,-4.85, +5.61,-4.71, +4.06,-2.41, +3.02,-0.18, +1.39,-0.76, +-1.03,-2.34, +-0.59,-3.46, +1.2,-6.01, +2.73,-7.49, +2.67,-7.34, +0.84,-7.77, +-1.06,-8.43, +-1.03,-9.46, +-1.67,-8.2, +-2.16,-5.8, +-2.93,-3.92, +-2.53,-3.15, +-2.01,-4.99, +-0.15,-8.08, +0.49,-9.32, +0.98,-9.69, +1.58,-8.94, +3.06,-8.4, +3.41,-8.24, +2.88,-7.26, +2.44,-6.69, +2.63,-7.23, +3.18,-7.4, +3.57,-6.18, +3.98,-4.98, +3.87,-4.62, +3.79,-4.01, +4.49,-2.09, +5.56,0.04, +5.96,-0.15, +5.69,-2.46, +5.74,-3.72, +5.62,-4.12, +5.36,-4.5, +4.96,-4.89, +3.77,-5.48, +3.35,-4.83, +1.41,-4.98, +0.46,-5.5, +-0,-5.75, +-0.72,-6.4, +-0.17,-6.33, +0.33,-5.51, +1.16,-4.9, +1.99,-4.63, +2.18,-3.78, +1.96,-2.67, +2.69,-2.21, +2.9,-1.68, +2.81,-1.72, +2.6,-1.07, +1.91,-0.24, +2.52,0.38, +2.47,0.75, +2.53,0.85, +2.22,0.81, +1.82,0.96, +2.47,1.88, +2.28,2.28, +3.69,2.52, +4.64,-1.69, +3.93,-1.43, +3.8,-1.48, +3.28,-1.66, +2.36,-1.36, +1.19,0.58, +0.51,-0.09, +0.16,2.72, +0.65,1.21, +1.14,0.44, +1.78,1.2, +2.84,2.55, +3.02,2.95, +2.19,5.25, +2.26,3.91, +2.72,4.24, +3.4,4.36, +2.44,3.42, +2.26,4.1, +1.88,4.32, +2.16,5.48, +2.16,5.92, +1.65,7.67, +0.35,6.16, +0,5.75, +-0.33,5.51, +-0.13,8.23, +0,5.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.14,-10.63, +-6.03,-10.44, +-6.03,-10.44, +-4.96,-10.96, +-4.12,-11.33, +-4.25,-11.69, +-4.45,-12.22, +-4.45,-12.24, +-4.45,-12.24, +-2.59,-12.75, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.25,-12.75, +-2.19,-12.44, +-2.24,-12.71, +-1.21,-12.95, +-0.14,-13.56, +-0,-14, +-0,-14, +-0,-14, +-0,-13.74, +-0,-13.61, +-0,-13.84, +-0,-13.22, +-0,-13.02, +-0,-12.42, +-0,-12.05, +-0,-11.83, +-0,-11.17, +-0,-10.67, +-0,-9.91, +0.02,-9.91, +0.95,-9.85, +1.71,-9.69, +1.57,-8.88, +1.62,-9.19, +1.79,-9.67, +2.86,-9.93, +3.86,-10.59, +4.13,-11.35, +4.36,-11.99, +4.45,-12.24, +4.8,-12.4, +6.49,-11.66, +7,-12.12, +7,-12.12, +7,-12.12, +7.33,-12.38, +9.38,-11.63, +9.7,-11.56, +10.18,-12.13, +10.25,-12.21, +10.25,-12.21, +10.25,-12.21, +9.62,-11.47, +10.16,-12.1, +10.4,-12.39, +10.87,-12.95, +11.69,-12.16, +12.4,-11.45, +12.95,-10.87, +12.86,-10.79, +12.17,-9.57, +12.17,-8.26, +1.52,-0.87, +2.82,-1.63, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.78,0.65, +7.1,0.07, +4.79,-1.51, +4.8,-2.6, +4.54,-1.92, +3.17,-0.48, +3.3,1.56, +2.77,5.53, +6.62,0.15, +5.49,-2.74, +4.42,-5.51, +3.99,-6.9, +4.05,-7.41, +4.58,-7.5, +5.29,-6.87, +4.56,-7.51, +4.87,-7.32, +6.02,-5.73, +7.32,-5.07, +8.34,-5.36, +7.22,-6.06, +7.13,-4.39, +6.95,-2.68, +6.44,-1.88, +5.46,-2.41, +6.33,-2.24, +7.2,-2.87, +4.89,-5.14, +2.35,-4.86, +2.89,-2.39, +2.53,-1.49, +1.89,-3.01, +1.35,-4.5, +0.62,-6.08, +1.31,-7.44, +1.72,-8.76, +2.03,-8.82, +1.9,-9.39, +1.85,-10.16, +0.65,-9.01, +-0.86,-7.45, +-2.55,-5.41, +-3.16,-4.38, +-2.4,-5.93, +-1.29,-7.59, +-0,-9.36, +0.98,-9.96, +1.53,-8.67, +2.3,-7.72, +3.33,-8.43, +2.73,-7.49, +2.82,-7.09, +3,-7.01, +3.5,-6.06, +4.11,-5.77, +4.53,-5.67, +3.5,-6.06, +3.5,-4.71, +3.86,-2.46, +4.8,0.82, +5.48,-0.68, +5.45,-3.14, +5.68,-3.63, +5.63,-4.53, +5.7,-5.38, +4.68,-5.57, +3.11,-5.12, +1.56,-5.13, +0.55,-4.99, +0.13,-6.29, +-0,-6.39, +-0,-7, +0.48,-6.42, +1.14,-5.17, +2.03,-4.76, +3.14,-5.13, +3.29,-4.35, +3.03,-3.06, +3.26,-2.19, +3.01,-1.44, +2.35,-0.85, +1.84,-0.62, +1.76,-0.26, +1.9,0.34, +2.06,0.75, +1.96,0.71, +2.35,0.85, +1.95,1.05, +2.58,2.01, +2.98,3.19, +3.64,3.05, +4.76,-1.17, +4.35,-1.51, +3.95,-1.44, +4.07,-2.27, +2.4,-1.44, +1.56,-0.39, +0.48,0.1, +0.48,0.39, +1.31,0.46, +1.57,0.5, +2.38,1.04, +2.45,2.45, +2.23,2.9, +3.49,6.83, +2.85,4.93, +3.06,4.74, +3.28,4.73, +2.44,3.77, +1.86,3.62, +1.71,3.5, +0.66,0.79, +-0.09,0.92, +-0.59,1.4, +-1.3,2.95, +-1.34,3.43, +-1.35,3.49, +-1.2,3.74, +-0.3,2.37, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.09,-10.54, +-6.03,-10.44, +-5.78,-10.57, +-4.32,-11.25, +-4.12,-11.33, +-4.13,-11.35, +-4.29,-11.79, +-4.44,-12.21, +-4.43,-12.25, +-2.89,-12.68, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.83, +-2.17,-12.32, +-2.12,-12.04, +-2.07,-11.87, +-1.4,-12.22, +-0.03,-12.86, +-0,-13.55, +-0,-14, +-0,-14, +-0,-14, +-0,-13.82, +-0,-13.72, +-0,-13.85, +-0,-13.59, +-0,-13.12, +-0,-12.94, +-0,-12.3, +-0,-12.05, +-0,-11.36, +0.02,-10.84, +0.15,-10.19, +1.11,-9.84, +1.59,-9.78, +1.71,-9.72, +1.67,-9.48, +1.63,-9.26, +1.64,-9.33, +3.23,-9.55, +3.68,-10.11, +4.08,-11.21, +4.16,-11.44, +4.45,-12.22, +5.19,-11.92, +6.57,-11.38, +6.83,-11.82, +6.98,-12.09, +7,-12.12, +7,-12.12, +9.16,-11.57, +9.62,-11.47, +9.88,-11.78, +10.25,-12.21, +10.25,-12.21, +10.25,-12.21, +10.25,-12.21, +10.3,-12.17, +10.55,-11.43, +10.97,-10.15, +11.4,-9.61, +10.2,-9.14, +9.82,-8.57, +4.03,-5.92, +1.1,-1.3, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.43,-0.63, +6.84,-0.39, +4.79,-1.51, +4.27,-2.47, +3.89,-0.99, +3.59,1.08, +4.58,1.77, +5.67,1.22, +5.79,-1.02, +4.68,-3.78, +3.87,-6, +4.05,-6.47, +4.15,-6.79, +4.86,-5.08, +5.27,-4.09, +4.91,-6.52, +4.96,-6.18, +8.11,-4.39, +9.04,-3.98, +9.15,-3.54, +5.79,-5.78, +5.55,-4.86, +5.43,-2.61, +5.64,-2.97, +6.62,-1.76, +7.52,-0.37, +7.1,-2.66, +2.25,-7.1, +1.41,-4.39, +2.18,-2.68, +3.44,-2.37, +3.11,-3.75, +2.02,-5.55, +2.06,-6.09, +2.66,-7.19, +2.91,-8.58, +3.14,-8.63, +4.25,-8.3, +4.29,-9.27, +3.01,-10.32, +1.15,-8.66, +-0.58,-7, +-1.52,-5.92, +-1.18,-6.94, +0.29,-9.03, +1.15,-9.6, +2.59,-9.73, +2.72,-9.05, +2.5,-8.22, +2.81,-7.72, +4.23,-7.72, +4.09,-7.09, +4.62,-6.85, +4.65,-6.44, +4.5,-5.36, +4.5,-5.36, +4.37,-5.79, +4.19,-4.99, +3.11,-3.3, +3.79,-0.87, +4.2,-2.2, +4.97,-3.15, +5.22,-3.01, +5.84,-3.57, +5.66,-4.07, +4.91,-5.54, +3.33,-5.73, +1.43,-6.12, +0.11,-6.87, +-0.17,-7.4, +-0.3,-6.97, +0.2,-6.98, +1.11,-6.39, +1.96,-5.38, +3.11,-4.56, +4.5,-5.36, +4.43,-4.23, +3.38,-2.83, +2.52,-1.56, +2.64,-1.35, +1.68,-0.7, +1.89,-0.43, +2.04,-0.11, +2.06,0.42, +2.17,0.79, +2.24,0.82, +3.45,1.26, +3.23,1.77, +3.03,2.26, +3.44,2.73, +3.62,3.04, +3.66,0.34, +4.05,-1.58, +3.94,-1.56, +3.75,-1.86, +2.54,-1.89, +1.86,-1.39, +0.93,-0.05, +1.45,0, +2.03,0.36, +2.03,0.72, +2.4,0.93, +2.5,1.92, +2.43,3.2, +3.11,5.39, +1.83,3.17, +2.31,4, +2.68,4.44, +2.31,4.48, +1.64,3.28, +0.75,0.51, +-0.26,0.85, +-0.92,0.51, +-1.23,1.25, +-1.65,2.62, +-1.53,3.12, +-1.52,3.46, +-1.72,3.69, +-1.14,3.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,-10.44, +-5.71,-10.59, +-4.51,-11.17, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.13,-11.35, +-4.34,-11.93, +-4.45,-12.24, +-2.85,-12.69, +-2.26,-12.83, +-2.26,-12.83, +-2.26,-12.8, +-2.18,-12.38, +-2.1,-11.92, +-2.09,-11.87, +-1.68,-11.92, +-0.12,-12.05, +-0,-12.81, +-0,-13.12, +-0,-13.91, +-0,-14, +-0,-14.25, +-0,-14.66, +-0,-14.75, +-0,-14.26, +-0,-14, +-0,-13.66, +-0,-13.09, +-0,-12.66, +-0,-12.07, +0.03,-11.91, +1.04,-11.06, +1.52,-10.1, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.71,-9.7, +1.68,-9.52, +2,-9.57, +3.23,-9.46, +3.7,-10.17, +3.95,-10.85, +4.13,-11.35, +4.33,-11.9, +5.44,-11.8, +6.51,-11.28, +6.52,-11.3, +6.68,-11.58, +6.9,-11.95, +7.02,-12.11, +8.84,-11.15, +9.57,-11.4, +9.64,-11.49, +10.16,-12.11, +10.25,-12.21, +10.36,-12.34, +10.37,-12.2, +11.64,-10.86, +11.86,-10.31, +11.48,-9.64, +11.12,-9.33, +3.66,-6.17, +-0.5,-1.73, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.95,-0.7, +6.53,-1.15, +5.4,-1.17, +4.66,-1.58, +4.38,-0.57, +4.49,1.22, +5.16,1.88, +5.84,1.38, +5,-1.09, +4.51,-3.78, +4.7,-5, +3.93,-5.76, +3.76,-5.33, +4.69,-1.92, +4.69,0.73, +4.4,-2.6, +6.39,-3.99, +8.63,-1.52, +8.15,-2.95, +6.17,-4.46, +2.52,-6.34, +2.72,-5.41, +2.54,-4.1, +4.46,-2.24, +5.98,0.17, +6.55,1.66, +5.5,-2.46, +1.59,-7.75, +1.55,-6.23, +1.11,-3.46, +1.09,-2.99, +2.11,-4.71, +2.1,-5.78, +2.54,-5.44, +2.49,-6.46, +3.22,-8.86, +3.73,-8.49, +3.92,-6.78, +3.42,-7.3, +2.82,-9.34, +3.04,-9.97, +2.3,-8.44, +1.24,-7.59, +0.72,-7.07, +2.31,-8.27, +3.11,-8.53, +3.53,-9.13, +3.39,-9.32, +3.18,-8.75, +2.77,-7.62, +3.65,-6.66, +3.85,-6.66, +4.35,-6.5, +4.77,-5.68, +4.58,-5.45, +4.79,-5.24, +4.5,-5.36, +4.41,-5.25, +3.43,-4.08, +2.96,-3.11, +3.56,-2.52, +4.37,-3.52, +5.2,-3, +5.61,-3.39, +5.36,-4.5, +4.56,-5.43, +2.97,-6.31, +1.38,-5.84, +0.2,-7.63, +0.29,-7.51, +0.69,-6.94, +1.61,-6.79, +2.44,-6.2, +3.22,-5.05, +4.12,-4.37, +5.43,-4.21, +5.3,-3.74, +3.02,-1.97, +1.92,-1.25, +1.55,-0.94, +1.63,-0.75, +1.29,-0.25, +1.73,0.21, +2.32,0.71, +2.9,0.73, +3.84,1.27, +3.5,1.27, +3.19,1.25, +3.13,1.6, +2.7,1.88, +3.01,2.46, +3.39,-0.19, +3.3,-1.48, +3.47,-1.83, +3.5,-2.02, +2.65,-2.26, +2.79,-1.88, +1.68,-0.39, +2.01,-0.04, +2.85,0.59, +2.84,0.57, +2.81,0.8, +3.23,1.62, +2.73,2.54, +2.28,3.81, +2.07,3.58, +1.84,3.84, +2.12,2.94, +1.42,1.26, +0.93,0.16, +-0.04,0.59, +-0.78,0.43, +-1.12,0.43, +-1.26,0.88, +-2.05,2.51, +-1.59,2.7, +-1.81,3.36, +-1.95,3.58, +-1.37,2.57, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.89,-10.99, +-4.61,-11.12, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.25,-11.67, +-3.9,-12.41, +-2.37,-12.8, +-2.26,-12.83, +-2.26,-12.83, +-2.24,-12.73, +-2.1,-11.89, +-2.09,-11.87, +-1.28,-11.96, +-0.16,-12.05, +-0,-12.08, +-0,-12.85, +-0,-13.11, +-0,-13.9, +-0,-14.08, +-0,-14.23, +-0,-14.02, +0.09,-14.29, +0.05,-14.3, +0.1,-13.99, +0.07,-13.99, +0.4,-13.48, +1.25,-12.92, +1.19,-12.27, +1.29,-11.85, +1.88,-10.94, +1.77,-10.06, +1.73,-9.79, +1.72,-9.76, +1.72,-9.76, +1.65,-9.33, +1.57,-8.82, +2.87,-9.24, +3.52,-9.66, +3.66,-10.05, +3.92,-10.78, +4.12,-11.33, +4.28,-11.61, +5.87,-11.52, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.54,-11.32, +6.98,-11.79, +8.69,-10.93, +9.45,-11.26, +9.65,-11.5, +10.14,-12.08, +10.25,-12.21, +11.18,-12.02, +12.03,-11.17, +12.21,-10.29, +12.17,-10.21, +11.65,-9.78, +8.9,-9.36, +0.84,-3.24, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.96,-0.7, +6.07,-1.07, +5.93,-1.05, +5.96,-0.78, +5.74,0.52, +5.44,1.86, +5.6,2.2, +5.89,1.04, +4.91,-1.01, +4.26,-2.82, +5.09,-4.04, +4.39,-5.26, +4.07,-3.92, +4.89,-0.1, +3.77,3.16, +2.01,1.37, +4.44,-1.69, +6.98,-0.15, +5,-1.36, +0.36,-5.09, +-0.31,-5.69, +-0.82,-5.37, +0.72,-4.16, +3.3,-1.04, +3.81,0.54, +2.79,1.04, +-0.42,-4.63, +0.72,-7.71, +2.81,-6.38, +2.51,-5.15, +0.79,-4.59, +-0.07,-4.44, +1.11,-6.01, +0.91,-5.9, +1.15,-6.53, +2.78,-7.57, +4,-8.27, +3.16,-7.73, +1.41,-7.99, +1.72,-8.65, +3.66,-10.05, +3.88,-9.4, +3.5,-8.02, +3.47,-7.01, +3.81,-6.99, +4.53,-7.84, +4.05,-8.4, +3.44,-9.45, +3.41,-9.36, +3.89,-7.48, +3.78,-6.54, +4.47,-6.9, +4.64,-6, +5.03,-6, +4.89,-5.83, +4.95,-5.59, +4.97,-5.16, +4.77,-5.68, +4.15,-5.21, +3.25,-3.87, +3.28,-2.91, +4.54,-2.11, +4.98,-2.9, +5.22,-3.01, +5.44,-4.39, +4.54,-5.41, +3.38,-6.38, +2.92,-6.68, +1.3,-7.37, +1.43,-7.12, +1.89,-6.71, +2.45,-6.66, +3.75,-5.89, +4.25,-4.89, +4.87,-3.5, +6.39,-3.12, +4.87,-2.61, +3.03,-1.75, +1.89,-1.09, +2.3,-1.88, +1.45,-0.65, +1.18,0.25, +1.07,0.39, +2.61,0.74, +3.43,0.78, +3.41,0.73, +3.5,1.15, +3.42,1.25, +3.48,1.37, +3.32,2.05, +3.16,1.88, +3.54,-1.4, +2.76,-1.06, +3.03,-1.75, +3.22,-2.47, +2.25,-2.48, +2.47,-2.18, +1.58,-0.34, +2.21,0.02, +2.89,-0.06, +2.9,0.17, +3.25,0.65, +3.61,1.25, +3.27,1.71, +2.24,3.47, +1.49,3.43, +1.27,2.15, +1.72,0.37, +0.8,-0.78, +-0.4,0.15, +-0.78,0.41, +-1.19,0.02, +-2.2,0.98, +-2.62,1.63, +-2.47,3.22, +-2.11,3.31, +-2.43,3.26, +-2.33,3.33, +-1.46,2.53, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-3.7,-11.46, +-2.57,-12.09, +-2.25,-12.78, +-2.26,-12.83, +-2.25,-12.75, +-2.17,-12.29, +-2.1,-11.92, +-1.77,-11.91, +-0.55,-12.02, +-0,-12.05, +-0,-12.05, +-0,-12.85, +-0,-13.02, +-0,-13.9, +-0,-14, +-0,-14.05, +0.05,-14, +1.27,-14.05, +2.11,-13.85, +1.92,-13.85, +2.11,-13.83, +2.03,-13.76, +2.28,-12.95, +2.24,-12.68, +2.1,-11.92, +2.02,-11.46, +1.91,-10.83, +1.76,-10, +1.75,-9.93, +1.93,-9.72, +1.97,-9.48, +2.66,-8.59, +3,-8.88, +3.42,-9.38, +3.59,-9.86, +3.78,-10.39, +4.02,-11.04, +4.51,-11.16, +6.22,-11.17, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.66,-11.25, +8.61,-10.71, +9.07,-10.81, +9.53,-11.35, +9.8,-11.68, +10.54,-11.71, +11.96,-10.65, +12.29,-10.31, +12.05,-10.11, +11.62,-9.61, +11.77,-8.72, +4.93,-6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.97,-0.52, +5.93,-1.05, +6.46,-0.54, +6.61,0.3, +6.69,1.49, +6.19,3.22, +5.59,2.17, +5.23,-0.7, +4.69,-1.48, +4.94,-1.53, +5.2,-3.31, +3.63,-4.36, +4.69,-1.31, +4.86,1.24, +2.41,2.5, +-0.46,2.51, +0.3,2.22, +3.13,1.22, +2.47,-2.99, +-0.65,-4.84, +-1.31,-5.69, +-1.69,-5.65, +-1.87,-4.04, +-1.5,-2.43, +-3.24,-2.01, +-3.47,-1.89, +-2.02,-3.97, +1.54,-5.38, +3.78,-5.68, +2.52,-6.49, +0.82,-6.37, +-0.22,-5.4, +0.15,-5.87, +0.81,-6.2, +0.98,-6.3, +1.29,-7.13, +1.48,-8.4, +0.78,-8.87, +1.19,-8.73, +1.5,-8.53, +2.44,-8.68, +3.52,-8.95, +5.22,-9.03, +4.47,-7.74, +3.99,-6.9, +4.54,-8.35, +3.93,-9.98, +3.64,-10, +4.12,-9.89, +4.77,-8.27, +3.92,-6.78, +3.66,-6.34, +4.02,-5.87, +3.61,-5.98, +3.16,-5.12, +3.36,-4.85, +3.41,-4.74, +4.27,-6.7, +4.51,-6.54, +3.73,-4.45, +3.64,-3.82, +4.12,-2.89, +4.91,-2.69, +5.82,-3.36, +5.66,-4.07, +4.77,-5.68, +4.5,-7.14, +3.67,-7.05, +2.39,-6.55, +2.39,-6.58, +2.87,-6.49, +3.99,-6.9, +4.76,-6.21, +4.92,-4.35, +5.44,-2.54, +5.91,-1.67, +4.86,-2.38, +4.11,-2.57, +2.79,-1.57, +2.94,-2.93, +2.25,-1.21, +1.64,0.09, +1.88,0.21, +2.56,0.56, +3.53,0.62, +3.02,0.53, +3.83,0.92, +3.13,1.05, +3.02,1.18, +2.8,1.38, +3.81,2.07, +3.46,-0.73, +3.05,-1.17, +2.95,-1.7, +3.11,-2.3, +2.48,-2.18, +2.67,-2.01, +2.46,-0.66, +2.68,-0.36, +2.88,-0.07, +3.26,0.09, +3.36,0.39, +3.76,0.78, +3.26,1.3, +1.44,2.82, +0.53,3.02, +2.2,0.98, +1.24,0.05, +0.63,-0.23, +-0.24,0, +-1.43,0.14, +-2.28,0.27, +-2.71,0.91, +-2.37,1.66, +-2.28,2.9, +-2.02,3.08, +-2.33,3.09, +-1.74,2.83, +-1.29,2.38, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.05,-11.35, +-3.77,-11.44, +-2.48,-11.78, +-2.09,-11.87, +-2.16,-12.25, +-2.21,-12.53, +-2.16,-12.28, +-1.93,-11.89, +-1.18,-11.97, +-0.29,-12.04, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.61, +-0,-13.11, +-0,-13.88, +-0,-14, +-0,-14, +1.43,-14.07, +2.49,-14.49, +2.49,-14.14, +2.43,-13.78, +2.44,-13.83, +2.43,-13.78, +2.38,-13.52, +2.27,-12.87, +2.19,-12.42, +2.1,-11.89, +2.07,-11.75, +2.05,-10.78, +2.68,-10.43, +3.25,-9.59, +3.34,-9.34, +3.22,-8.85, +3.29,-9.03, +3.39,-9.32, +3.63,-9.45, +4.62,-10.02, +4.86,-10.21, +5.63,-10.64, +6.14,-10.63, +6.43,-11.13, +6.23,-10.79, +6.27,-10.87, +6.23,-10.8, +2.5,-3.62, +1.19,-1.47, +1,-1.19, +7.87,-9.38, +9.78,-10.82, +11.16,-10.09, +11.84,-9.93, +11.8,-9.9, +11.27,-9.14, +11.53,-7.54, +7.61,-6.39, +0.42,-1.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.98,-0.35, +6.37,-0.8, +7,0, +6.92,0.87, +6.73,1.8, +6.38,2.81, +5.06,1.92, +3.96,-1.06, +4.45,-0.97, +5.12,-0.68, +5.32,-2.55, +4.34,-2.31, +4.96,0.73, +4.65,1.69, +2.86,0.49, +-1.15,1.86, +-2.84,2.87, +-2.85,1.59, +-1.73,-2.16, +0.13,-5.02, +0.57,-5.25, +-0.17,-6.12, +-2.09,-5.34, +-3.44,-3.87, +-3.64,-3.58, +-3.84,-3.03, +-2.23,-1.26, +1.76,-0.56, +4.25,-3.82, +3.16,-7.16, +1.32,-7.46, +0.46,-6.59, +1.22,-6.03, +1.21,-7.06, +0.2,-7.95, +0.04,-7.7, +0.29,-9.08, +1.15,-9.6, +1.64,-9.28, +1.55,-8.81, +1.98,-7.83, +3.02,-8.29, +3.9,-8.62, +4.41,-7.64, +3.99,-6.9, +2.96,-8.14, +3.29,-9.03, +3.46,-9.5, +4.09,-8.35, +4.39,-7.6, +4.04,-7, +4.01,-6.49, +3.44,-5.96, +3.36,-5.82, +3.09,-6.13, +1.97,-5.4, +1.65,-4.08, +1.67,-4.74, +3.6,-6.13, +4.09,-5.14, +3.87,-4.62, +3.56,-3.56, +4.67,-2.84, +4.75,-3.17, +4.33,-4.9, +4.22,-6, +4.47,-7.74, +4.31,-7.46, +2.45,-6.73, +2.67,-6.45, +3.99,-6.9, +4.81,-6.45, +5.61,-5.61, +6.05,-3.89, +5.72,-1.76, +5.61,-0.63, +5.92,-1.26, +6.42,-1.87, +2.88,-0.32, +2.46,-0.08, +2.59,-0.73, +2.71,-0.21, +2.37,-0.44, +2.54,0.15, +3.28,0.51, +3.45,0.52, +2.26,0.36, +2.44,0.61, +2.89,1.23, +3.3,1.94, +3.38,1.6, +3.96,-1.23, +3.37,-1.49, +2.65,-0.94, +2.79,-1.83, +2.89,-2.58, +3.05,-1.97, +3.03,-0.88, +4.29,-0.91, +2.89,-0.08, +3.7,-0.2, +4.64,0.15, +4.14,0.33, +3.45,1.6, +1.13,3.04, +0.87,2.89, +1.12,0.73, +1.09,-0.14, +0.37,-0.08, +-0.66,0.24, +-1.57,0.44, +-2.09,0.71, +-2.27,1.56, +-1.88,2.76, +-2.21,3.42, +-2.02,3.15, +-1.97,3.41, +-1.28,3.51, +-0.57,2.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-3.86,-11.41, +-3.24,-11.59, +-2.28,-11.83, +-2.09,-11.87, +-2.09,-11.87, +-2.1,-11.89, +-1.89,-11.92, +-1.61,-11.94, +-0.39,-12.04, +-0.02,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.15, +-0,-13.05, +0.05,-13.89, +0.52,-14, +1.83,-14.19, +2.48,-14.68, +2.6,-14.74, +2.59,-14.67, +2.57,-14.57, +2.58,-14.6, +2.55,-14.46, +2.45,-13.9, +2.38,-13.47, +2.28,-12.93, +2.35,-12.38, +2.61,-11.71, +3.49,-10.65, +3.72,-10.44, +3.73,-10.26, +3.46,-9.52, +3.39,-9.24, +3.6,-8.95, +3.63,-9.22, +4.33,-8.93, +5.23,-9.12, +5.54,-9.6, +5.79,-10.03, +6.01,-10.41, +6.07,-10.51, +6.03,-10.44, +3.7,-8.98, +4.88,-9.98, +3.77,-6.52, +3.36,-4.41, +4.21,-5.01, +8.12,-9.42, +10.22,-9.59, +10.9,-9.15, +11.3,-9.48, +11.64,-8.93, +11.49,-7.34, +10.94,-6.35, +1.51,-4.59, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.54,-0.45, +6.88,-0.12, +6.95,0.52, +6.82,1.48, +6.49,2.18, +5.11,2.67, +4.56,1.38, +4.02,0.5, +4.17,-0.18, +5.43,-0.42, +5.74,-1.37, +4.95,-0.87, +4.27,1.89, +2.5,1.92, +0.82,0.01, +-1.24,0.62, +-3.48,1.43, +-4.79,1.51, +-4.52,-0.72, +-2.7,-3.11, +-0.54,-4.75, +-1.35,-6.12, +-2.48,-5.93, +-1.81,-5.38, +-2.07,-4.41, +-1.96,-2.65, +-1.76,-0.07, +-0.66,2.45, +-1.88,-1.76, +-0.62,-7.18, +1.21,-7.83, +2.87,-6.81, +2.42,-6.66, +2.11,-7.82, +2.99,-8.1, +2.63,-8.52, +2.69,-8.95, +3.39,-9.32, +3.34,-9.73, +3.21,-9.54, +3.41,-8.24, +4.07,-7.93, +4.4,-7.62, +4.29,-7.43, +4.07,-7.04, +3.18,-8.23, +3.11,-8.55, +3.48,-8.56, +4.34,-7.96, +4.23,-7.32, +3.9,-6.76, +3.5,-6.06, +3.5,-6.06, +2.56,-5.83, +2.25,-6.18, +1.77,-6.17, +0.7,-5.24, +0.33,-4.47, +1.1,-4.7, +3.09,-5.64, +3.62,-6.27, +3.5,-4.26, +3.79,-3.46, +3.87,-4.16, +3.33,-5.55, +3.18,-6.21, +3.99,-6.9, +4.39,-7.6, +2.68,-7.38, +3.58,-6.94, +4.72,-6.89, +5.39,-6.42, +6.08,-5.1, +7.08,-2.97, +5.97,-0.3, +5.85,0.63, +6.62,0.58, +6.13,1.72, +4.56,1.58, +2.68,1.19, +3.99,-0.09, +2.69,-0.72, +3.15,-1.09, +3.2,-0.02, +4.75,0.75, +3.62,0.11, +2.38,0.03, +1.71,0.54, +2.39,0.79, +2.6,1.75, +2.63,1.62, +3.56,-1.59, +2.9,-1.44, +2.84,-0.93, +3.24,-1.55, +3.24,-2.46, +3.32,-2, +3.7,-0.79, +4.4,-0.78, +4.33,-0.41, +4.04,-0.53, +4.37,-0.38, +3.93,0.18, +2.62,0.85, +2.17,4.04, +1.96,1.33, +1.45,0.44, +-0.35,-0.14, +-1.14,0.52, +-2.22,1.22, +-1.96,0.87, +-2.41,1.34, +-2.05,2.05, +-1.99,3.16, +-1.92,3.72, +-1.6,3.71, +-1.16,3.26, +-0.71,4.13, +-0.09,2.91, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.12,-11.33, +-4.12,-11.33, +-3.72,-11.43, +-2.16,-11.35, +-2.07,-11.48, +-2,-11.34, +-2.07,-11.72, +-2.09,-11.87, +-1.98,-11.89, +-0.71,-12.01, +-0.05,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.22, +0.12,-13.07, +1.43,-13.81, +2.29,-13.88, +2.6,-14.72, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.55,-14.49, +2.44,-13.83, +3.2,-13.51, +3.57,-12.57, +4.17,-11.46, +3.93,-10.8, +3.79,-10.41, +3.78,-10.39, +3.62,-9.88, +4.15,-8.98, +4.88,-8.6, +4.94,-8.6, +4.9,-8.62, +5.13,-8.88, +5.51,-9.54, +5.55,-9.62, +5.86,-10.16, +5.87,-10.16, +2.52,-7.91, +-0.44,-1.37, +2.55,-7.74, +6.21,-10.03, +7.71,-9.39, +8.21,-9.63, +9.7,-9.08, +10.39,-8.75, +10.53,-8.57, +11.36,-8.16, +11.56,-7.16, +10.98,-6.2, +7.93,-5.54, +0.01,-1.12, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.06,-0.06, +0,0, +0,0, +0,0, +0,0, +-0.14,-0.78, +0.05,-1.15, +0,0, +0,0, +0,0, +1.32,-0.23, +6.98,-0.17, +6.93,0.81, +6.71,1.89, +5.71,2.2, +4.76,2.03, +4.95,0.98, +4.34,1.58, +4.94,0.74, +6,-0.12, +5.96,-0.75, +5.3,0.5, +4.07,1.98, +1.24,1.89, +-0.93,1.6, +-2.72,0.69, +-3.99,1.35, +-4.79,1.05, +-5.03,-0.22, +-4.85,-1.36, +-3.94,-3.71, +-2.6,-6.12, +-1.58,-6.06, +-1.8,-4.94, +-0.39,-5.4, +-0.05,-2.92, +-1.2,-0.62, +-3.72,-0.02, +-5.12,-3.28, +-3.64,-5.8, +-0.88,-6.7, +2.05,-6.52, +4.1,-6.58, +4.63,-7.37, +4,-7.96, +3.32,-9.11, +3.2,-8.79, +3.39,-9.32, +3.39,-9.32, +3.82,-9.3, +4.65,-8.06, +4.75,-8.23, +4.63,-8.02, +4.75,-8.23, +4.47,-7.74, +4.24,-7.85, +4,-7.96, +4.8,-8.31, +4.59,-7.95, +4.33,-7.5, +3.62,-6.27, +3.36,-6.12, +2.34,-6.44, +1.32,-5.99, +0.92,-6.22, +1.13,-6.41, +0.61,-5.16, +-0,-5.16, +0.12,-4.4, +1.35,-4.7, +2.12,-5.33, +2.53,-5, +2.53,-5.09, +2.69,-6.17, +2.84,-7.29, +2.58,-7.1, +2.68,-7.38, +4.04,-7.74, +3.23,-7.13, +3.99,-6.56, +4.46,-6.12, +5.51,-5.49, +6.7,-4.22, +7.51,-2.49, +6.62,0.37, +5.77,2.08, +6.71,2.61, +5.28,3.48, +4.23,2.79, +4.68,2.06, +4.93,-0.36, +4.13,-1.21, +4.08,-0.74, +4.77,-0.1, +4.98,0.88, +4.05,-0.18, +2.81,-0.82, +1.91,-0.04, +2.32,0.69, +2.5,1.2, +2.39,2.2, +3.51,-1.03, +2.7,-1.16, +2.93,-1.02, +3.65,-1.6, +3.14,-2.15, +4.22,-1.88, +3.25,-0.56, +4.31,-0.76, +5.05,-1.09, +4.75,-0.57, +4.18,-0.43, +3.34,-1, +2.45,0.97, +2.36,-0.46, +1.74,0.2, +0.75,-0.03, +-0.99,-0.12, +-2.03,0.17, +-2.07,0.84, +-2.31,1.2, +-2.38,1.94, +-2.45,2.68, +-2.12,3.68, +-2,4.09, +-1.48,4.06, +-0.78,4.43, +-0.15,5.04, +0.09,3.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.1,-11.26, +-3.98,-10.94, +-2.71,-10.94, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.97,-11.15, +-2.01,-11.77, +-0.58,-12.03, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +0.07,-12.07, +0.03,-12.78, +0.28,-13.12, +2.36,-13.77, +2.46,-13.94, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +2.61,-14.8, +2.62,-14.88, +2.62,-14.85, +2.89,-14.68, +4,-14, +4.72,-13.17, +4.61,-12.66, +4.33,-11.9, +4.04,-11.1, +3.82,-10.49, +4.13,-10.28, +4.8,-9.48, +4.92,-8.61, +4.96,-8.59, +4.96,-8.59, +4.96,-8.59, +4.94,-8.56, +5.08,-8.8, +5.35,-9.26, +5.57,-9.64, +5.42,-9.61, +0.17,-3.51, +-0.49,-2.05, +5.06,-9.46, +6.62,-9.06, +7.43,-8.77, +8.66,-8.22, +9.41,-7.9, +9.79,-7.38, +10.42,-6.64, +11.45,-6.61, +11.02,-5.61, +7.97,-5.18, +-0.1,-1.35, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.16,-0.19, +-1.17,-3.94, +-1.14,-4.14, +-0.85,-4.6, +-0.45,-2.32, +-0.51,-0.92, +0.18,-4.21, +0.14,-1.53, +0,0, +0,0, +0,0, +0.74,-0.13, +2.97,-0.35, +7,0, +6.91,1.01, +6.41,2.19, +5.53,2.01, +4.75,1.73, +4.78,1.62, +4.64,1.96, +4.86,1.3, +5.45,0.27, +5.88,-0.47, +6.31,1.52, +4.96,1.97, +2.51,1.65, +0.29,2.57, +-1.75,1.3, +-3.7,1.35, +-3.97,2.17, +-3.26,0.34, +-3.83,-1.56, +-4.11,-2.66, +-2.06,-4.71, +0.35,-5.83, +0.66,-5.75, +0.87,-5.09, +1.05,-3.32, +-0.26,-2.85, +-2.69,-3.52, +-3.01,-3.52, +-0.93,-4.86, +1.05,-4.78, +3.96,-4.49, +4.52,-4.79, +4.66,-6.94, +3.59,-8.16, +3.36,-8.57, +3.06,-8.4, +3.25,-8.92, +3.55,-9.09, +4.58,-8.36, +4.48,-8.2, +4.63,-8.02, +4.39,-7.6, +4.08,-7.69, +4.4,-7.62, +4.44,-8, +4.47,-7.74, +4.65,-8.06, +3.88,-7.56, +3.4,-7.48, +2.61,-6.21, +2.61,-6.45, +1.17,-5.88, +0.41,-6.64, +-0,-7, +0.46,-6.47, +0.21,-5.79, +0.41,-6.15, +0.11,-4.92, +0.35,-4.47, +1.68,-5.34, +1.89,-6.14, +1.6,-5.91, +1.32,-6.61, +1.4,-7.09, +2.18,-7.51, +3.15,-7.82, +3.07,-7.92, +3.43,-6.81, +3.75,-6.18, +4.5,-5.36, +5.59,-4.17, +6.61,-3.08, +7.13,-0.95, +7.29,1.06, +6.16,2.96, +5.99,3.79, +5.24,4.62, +4.33,5.63, +4.07,3.95, +4.5,1.79, +5.03,0.29, +4.98,-0.88, +4.62,-0.23, +4.29,0.1, +4.13,-0.76, +3.27,-1.58, +2.3,-0.31, +2.33,0.41, +3.2,1.63, +3.09,1.7, +4.09,-0.18, +3.09,-1.26, +3.63,-1.29, +3.61,-1.82, +3.92,-1.86, +3.56,-1.43, +3.7,-0.53, +4.15,-0.56, +5.08,-1.43, +5.16,-0.61, +4.85,-0.95, +3.26,-1.45, +2.73,-0.81, +2.14,-0.97, +1.85,-0.02, +1.45,0.05, +-1.03,-0.27, +-2.24,0.14, +-2.44,0.7, +-2.15,1.37, +-3.32,2, +-3.22,2.9, +-2.54,3.48, +-2.01,3.73, +-1.09,4.54, +-0.66,5, +0,4.61, +0.9,2.77, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.88,-10.66, +-3.8,-10.43, +-2.88,-10.67, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.83,-10.93, +-1,-11.26, +-0.08,-11.95, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +0.15,-12.05, +1.19,-11.97, +1.11,-12.69, +1.58,-13.39, +2.37,-13.77, +2.45,-13.87, +2.58,-14.66, +2.6,-14.74, +2.6,-14.74, +2.66,-15.07, +2.72,-15.42, +2.76,-15.64, +2.74,-15.55, +3.56,-14.56, +5.12,-14.07, +4.98,-13.68, +4.73,-12.99, +4.48,-12.3, +4.27,-11.74, +4.2,-11.06, +4.96,-9.98, +5.42,-9.39, +5.09,-8.68, +5.16,-8.46, +4.96,-8.59, +4.92,-8.51, +4.56,-7.89, +4.57,-7.91, +4.83,-8.36, +5.11,-8.86, +1.17,-6.07, +-0.44,-0.81, +-0.29,-3.1, +5.61,-9.37, +7.06,-8.54, +7.38,-8.26, +8.48,-7.26, +9.05,-7.17, +9.88,-6.31, +10.32,-5.34, +10.68,-5, +8.97,-4.32, +1.49,-2.9, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.01,-0.44, +0,0, +-0.25,-0.42, +-0.2,-2.13, +-1.11,-2.46, +-1.99,-4.44, +-1.9,-7.93, +-1.3,-7.39, +-0.8,-7.11, +-0.14,-6.31, +0.32,-5.84, +1.6,-5.83, +0.09,-1.71, +0,0, +0,0, +0.71,-0.26, +6.27,-1.41, +6.06,-0.66, +7,0, +6.83,0.83, +5.71,1.89, +5.22,2.25, +4.5,2.6, +4.38,2.53, +4.38,2.53, +5.14,1.59, +5.48,0.74, +5.61,1.01, +5.22,2.72, +3.32,2.29, +1.8,2.24, +0.71,2.65, +-1.41,1.79, +-2.82,1.45, +-2.93,2.99, +-2.6,2.49, +-1.64,-1.14, +-1.37,-2.56, +-0.65,-3.18, +0.76,-4.64, +1.34,-5.28, +1.23,-5.3, +0.61,-2.84, +0.26,-4.01, +-0.76,-4.95, +-0.83,-3.78, +0.3,-2.97, +2.29,-2.14, +2.35,-2.77, +3.21,-3.18, +3.78,-5.28, +4.54,-7.86, +3.84,-9.11, +3.98,-9.04, +3.76,-8.07, +4.16,-7.89, +4.47,-7.74, +3.33,-7.68, +2.73,-7.49, +2.15,-7.09, +2.26,-6.04, +2.18,-7.38, +2.39,-7.72, +3.08,-6.72, +3.12,-7.57, +2.96,-8.14, +2.28,-7.61, +1.43,-6.26, +0.93,-5.94, +-0.17,-6.01, +-1.06,-6.02, +-1.55,-5.8, +-1.28,-5.75, +-1.04,-5.34, +0.02,-6.37, +-0.33,-5.27, +0.11,-4.92, +0.59,-4.98, +0.55,-6.14, +0.46,-6.84, +0.41,-6.96, +1.21,-7.76, +2.13,-8.37, +2.84,-7.79, +4.18,-7.68, +3.57,-6.56, +4.09,-5.99, +5.38,-5.26, +6.21,-4.56, +6.43,-2.71, +6.82,-0.25, +6.68,2, +6.04,3.69, +5.61,4.71, +4.53,5.16, +4.47,5.44, +4.17,5.55, +3.97,2.58, +5.04,-0.11, +4.32,-1.17, +3.41,-0.55, +3.49,-0.06, +4.07,-0.77, +3.38,-1.03, +3.61,-1.23, +4.35,-1.35, +3.03,-0.29, +3.42,-0.04, +3.42,-1.1, +2.72,-1.58, +3.6,-1.27, +3.65,-1.75, +3.29,-1.2, +3.72,-1.28, +4.32,-0.76, +5.2,-0.84, +5.46,-1.22, +4.91,-1.12, +4.59,-1.64, +3.78,-1.8, +2.59,-1.82, +2.09,-1.71, +1.94,-1.18, +0.88,-0.34, +-2.15,-1.15, +-2.41,0.03, +-2.87,0.44, +-2.81,0.49, +-4.13,0.71, +-3.38,2.22, +-3,2.72, +-2.26,3.53, +-0.98,4.81, +-0.06,4.52, +0.51,3.74, +0.52,2.45, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.79,-10.41, +-3.79,-10.41, +-2.61,-10.75, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.59,-10.96, +-0.02,-11.08, +-0,-11.44, +-0,-11.99, +-0,-12.05, +0.16,-12.05, +1.21,-11.97, +1.39,-11.95, +1.37,-11.95, +1.28,-11.96, +2.16,-12.23, +2.25,-12.75, +2.38,-13.5, +2.43,-13.78, +2.46,-13.97, +2.6,-14.74, +2.6,-14.74, +2.68,-15.19, +2.76,-15.67, +2.77,-15.7, +2.77,-15.7, +3.47,-15.51, +4.84,-14.49, +5.12,-14.07, +5.11,-14.04, +4.92,-13.52, +4.82,-12.7, +5.13,-11.73, +5.72,-10.65, +5.65,-9.78, +5.54,-9.53, +5.93,-8.14, +5.95,-7.54, +5.23,-7.65, +4.69,-8.09, +4.43,-7.68, +4.32,-7.48, +3.8,-7.49, +3.63,-7.97, +-0.4,-1.24, +0,0, +0.37,-3.2, +6.05,-8.21, +6.72,-7.87, +7.58,-6.89, +8.08,-6.53, +8.83,-5.17, +9.38,-4.27, +9.62,-3.71, +5.17,-3.34, +1.94,-1.98, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.15,-0.26, +4.26,-1.77, +5.15,-2.63, +1.67,-3.66, +0.65,-4.74, +0.39,-7.03, +-0.95,-8.33, +-1.82,-9.37, +-1.74,-9.34, +-1.39,-8.44, +-0.24,-7.91, +0.21,-7.13, +0.34,-5.72, +-0.06,-2.01, +0,0, +0,0, +0,0, +1.42,-0.52, +6.31,-1.51, +6.95,-0.52, +6.97,0.27, +6.57,1.16, +5.78,1.99, +5.97,2.17, +5.56,2.02, +4.88,2.01, +4.68,2.45, +4.97,2.07, +5.11,1.7, +5.3,2.16, +4.75,3.2, +3.5,3.35, +2.14,3.23, +1.11,3.51, +0.12,2.32, +0.33,2.99, +0.91,3.97, +1.85,3.03, +2.68,0.67, +4.54,-0.83, +5.1,-2.33, +4.71,-3.69, +3.55,-4.84, +2.7,-4.82, +1.83,-4.13, +0.51,-3.82, +0.24,-5.15, +0.78,-4.08, +2.07,-1.92, +2.97,-1.6, +2.74,-2.41, +2.46,-2.38, +2.89,-4.29, +2.8,-7.19, +3.18,-8.75, +4.25,-8.3, +4.41,-7.64, +4.47,-7.74, +3.87,-7.31, +2.25,-7.45, +0.71,-6.99, +-0.47,-6.4, +-1.27,-5.54, +-0.47,-5.27, +0.93,-6.74, +1.05,-6.49, +1.5,-7.21, +1.78,-8.19, +1.63,-7.78, +0.81,-6.6, +0.84,-6.34, +-0.77,-6.56, +-2.1,-6.8, +-2.5,-5.99, +-2.21,-5.71, +-2.37,-5.21, +-1.92,-5.65, +-0.61,-5.81, +-0.25,-5.19, +1.04,-6.46, +0.44,-7.18, +-0,-7.14, +-0,-7.24, +0.98,-8.21, +2.51,-8.84, +3.06,-8.4, +4.77,-7.37, +3.38,-5.85, +4.45,-5.07, +5.89,-5.17, +7,-4.07, +5.88,-1.65, +6.64,0.41, +6.22,2.37, +4.79,3.63, +4.26,4.57, +3.47,5.07, +2.82,4.79, +3.15,4.45, +3.77,2.12, +4.29,0.17, +3.25,-1.27, +2.18,-0.94, +2.08,-0.17, +2.28,-0.42, +3,-1.11, +4.32,-1.48, +4.08,-1.58, +3.94,-1.43, +4.37,-1.22, +2.95,-1.56, +3.27,-1.52, +4.03,-1.52, +3.82,-1.68, +3.8,-1.36, +4.25,-1.06, +4.41,-1.14, +5.1,-0.9, +4.99,-1.36, +4.5,-1.7, +3.74,-2.26, +2.93,-2.55, +3.14,-2.28, +2.4,-2.16, +2.94,0.51, +-0.26,-1.88, +-1.85,-1.77, +-2.81,-0.56, +-2.89,0.25, +-3.73,0.17, +-3.83,0.66, +-4.05,1.8, +-3.3,2.37, +-2.37,3.95, +-1.46,5.16, +-0.24,4.32, +0.35,3.84, +0.35,3.03, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.79,-10.41, +-3.79,-10.41, +-2.86,-10.68, +-1.92,-10.91, +-1.92,-10.88, +-1.88,-10.64, +-1.88,-10.92, +-0.49,-11.06, +-0,-11.1, +-0,-11.37, +-0,-11.94, +-0,-12.05, +1.32,-11.96, +2.09,-11.87, +2.09,-11.87, +2.1,-11.89, +2.19,-12.44, +2.27,-12.85, +2.37,-13.45, +2.43,-13.78, +2.43,-13.81, +2.59,-14.68, +2.6,-14.74, +2.7,-15.29, +2.77,-15.7, +2.77,-15.7, +2.8,-15.69, +4.39,-15.3, +5.43,-14.92, +5.19,-14.25, +5.12,-14.07, +5.01,-13.76, +5.84,-12.52, +5.99,-11.55, +6.4,-11.09, +5.7,-9.88, +5.64,-9.53, +6.26,-8.04, +5.97,-7.11, +5.7,-6.89, +4.86,-7.49, +4.29,-7.43, +4.02,-6.97, +0.81,-4.18, +-0.47,-0.7, +-0.11,-0.13, +0,0, +0.33,-2.7, +6.37,-7.59, +6.89,-7.11, +7.61,-6.36, +8.22,-5.22, +8.92,-4.25, +9.29,-2.99, +7.15,-3.02, +2.01,-2.56, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.28,-1.09, +3.77,-1.64, +6.53,-1.34, +5.17,-3.04, +3.84,-4.64, +2.1,-6.4, +0.55,-7.73, +-1.03,-8.97, +-1.68,-9.91, +-1.71,-9.7, +-0.98,-8.79, +-0,-8.19, +0.57,-7.28, +-0.55,-1.97, +0,0, +0,0, +0,0, +0,0, +3.24,-1.18, +6.85,-1.36, +6.96,-0.46, +6.8,0.89, +5.75,1.72, +5.96,2.72, +6.37,2.32, +5.86,1.34, +6.22,1.22, +5.7,1.92, +5.01,2.59, +4.68,2.45, +4.08,3.43, +4.75,3.68, +4.21,4.74, +2.64,3.42, +1.58,4.26, +2.23,3.93, +3.14,4.39, +4.77,4.74, +6.15,3.53, +7.25,2.03, +7.82,0.28, +7.35,-2.92, +5.95,-4.41, +4.37,-5.41, +1.79,-5.41, +0.38,-4.28, +0.45,-4.27, +0.72,-4.82, +1.58,-4.08, +1.67,-1.43, +2.05,-0.3, +2.38,-2.65, +2.81,-3.01, +3.21,-3.85, +3.77,-6.02, +3.01,-8.27, +4.04,-7, +3.56,-6.57, +2.96,-6.79, +2.56,-7.03, +1.68,-7.52, +-0.54,-7.51, +-2.78,-5.94, +-2.71,-4.91, +-1.73,-4.7, +-2.04,-5.61, +-1.67,-5.91, +-1.55,-6.34, +-1.01,-7.04, +-0.66,-6.83, +-0.52,-5.98, +-1.17,-5.9, +-2.01,-6.97, +-2.91,-7.41, +-3.54,-6.44, +-4.02,-5.7, +-4.1,-5.25, +-3.4,-5.14, +-2.86,-4.91, +-1.99,-4.63, +-1.3,-5.72, +-0.87,-6.78, +-0,-7.61, +-0,-7.97, +0.73,-8.27, +1.55,-8.81, +3.17,-8.71, +4.75,-7.55, +2.53,-4.38, +3.9,-4.42, +5.91,-4.67, +6.8,-3.19, +5.81,-0.64, +7.2,0.89, +5.37,2.36, +3.87,3.25, +3.32,3.95, +2.71,4.08, +2.53,3.49, +2.59,3.32, +3.08,2.39, +3.21,0.15, +1.56,-1.6, +0.63,-1.33, +0.99,-0.39, +0.92,-0.31, +1.23,-0.12, +3.87,-1.18, +3.95,-1.72, +3.19,-1.61, +3.71,-1.5, +3.02,-1.63, +2.54,-1.7, +3.9,-1.5, +4.6,-1.67, +4.64,-1.02, +4.69,-0.8, +4.23,-0.32, +4.66,0.52, +4.28,-0.53, +4.32,-1.52, +3.09,-2.83, +2.94,-3.16, +3.04,-2.71, +2.78,-2.9, +0.1,-1.46, +-0.8,-2.92, +-1.53,-2.04, +-2.37,-1.17, +-2.86,-0.5, +-3.58,0.07, +-4.04,0.47, +-3.99,1.64, +-3.84,3.22, +-2.6,4.9, +-1.04,4.56, +0.35,3.66, +-0.7,4.83, +0,5.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.79,-10.41, +-3.79,-10.41, +-3.42,-10.53, +-2.18,-10.86, +-1.9,-10.78, +-1.76,-10, +-1.88,-10.64, +-1.2,-10.99, +-0.02,-11.08, +-0,-11.08, +-0,-11.4, +-0,-12.05, +0.21,-12.05, +1.07,-11.99, +2.05,-11.9, +2.18,-12.36, +2.26,-12.8, +2.3,-13.04, +2.43,-13.78, +2.43,-13.78, +2.45,-13.89, +2.59,-14.69, +2.6,-14.74, +2.68,-15.22, +2.77,-15.7, +2.77,-15.7, +3.75,-15.46, +5.19,-15.06, +5.45,-14.98, +5.17,-14.21, +5.12,-14.07, +5.7,-13.4, +6.66,-12.13, +6.51,-11.28, +6.37,-11.04, +5.7,-9.87, +6.19,-9.11, +6.5,-7.74, +5.75,-6.85, +5.54,-7.01, +4.35,-7.22, +4,-6.92, +2.07,-5.69, +-0.23,-1.14, +0,0, +0,0, +0,0, +0.28,-1.9, +2.24,-5.2, +6.89,-6.95, +7.53,-5.71, +8.28,-4.02, +8.89,-2.71, +7.05,-2.51, +-0.03,-1.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.31,-0.4, +6.26,-0.02, +7.35,0.47, +6.48,-1.4, +5.3,-2.82, +4.26,-4.24, +2.9,-5.35, +1.42,-6.74, +0.25,-7.95, +-1.33,-8.61, +-1.27,-8.5, +-0.39,-7.97, +-0,-7.15, +0.04,-5.93, +-0.34,-0.84, +0,0, +0,0, +0,0, +0,0, +5.37,-1.81, +6.77,-1.19, +6.53,-0.37, +6.94,0.69, +6.5,2.23, +6.58,2.39, +6.12,2.23, +5.93,1.05, +6.25,1.1, +5.87,1.27, +5.54,2.33, +4.99,3.34, +4.43,4.06, +4.57,4.9, +4.03,5.43, +3.01,4.63, +3.63,5.44, +4.35,4.88, +4.81,5.73, +5.49,5.74, +6.45,4.64, +7.58,3.23, +8.09,0.49, +7.93,-2.1, +7.7,-4.16, +5.89,-5.85, +4.59,-6.18, +5.05,-3.77, +4.73,-4.56, +4.45,-4, +3.94,-4.09, +2.22,-2.69, +0.97,-1.48, +0.24,-2, +2.32,-2.27, +3.28,-2.72, +3.01,-5.95, +3.04,-7.46, +3.99,-6.9, +2.88,-6.45, +2.35,-6.45, +1.05,-6.47, +-0.37,-7.37, +-1.9,-7.68, +-3.99,-6.52, +-5.03,-3.96, +-5,-4.68, +-4.45,-6.28, +-3.91,-6.68, +-4.76,-6.75, +-3.69,-7.12, +-1.43,-6.95, +-1.04,-6.03, +-1.59,-6.54, +-3.05,-6.73, +-3.95,-6.85, +-4.93,-6.24, +-5.33,-6.35, +-5.45,-5.78, +-4.86,-6.03, +-3.81,-5.6, +-2.49,-5.82, +-1.84,-6.58, +-1.36,-7.71, +-0.3,-8.06, +1.04,-8.69, +0,-9.16, +1.48,-9.67, +2.22,-8.63, +4.48,-7.36, +3.26,-4.9, +4.37,-4.71, +5.46,-3.87, +6.57,-3.27, +5.79,-0.87, +6.83,0.82, +4.81,2.37, +3.29,3.07, +2.53,3.2, +2.55,3.18, +2.48,3.23, +2.71,3.29, +3.16,2.17, +2.33,-0.56, +1.31,-1.28, +0.88,-1.05, +0.7,-0.72, +0.43,-0.48, +0.29,-0.04, +1.26,0.25, +3.46,-1.06, +2.54,-1.86, +2.6,-1.94, +1.49,-1.68, +2,-1.6, +3.87,-1.6, +2.99,-1.8, +3.37,-1.19, +4.49,-0.15, +4.24,0.43, +4.49,0.75, +4.34,0.81, +4.44,1.15, +3.5,0.27, +3.12,-1.58, +3.67,-2.98, +2.4,-3.45, +-0.14,-2.51, +-1,-2.14, +-2.72,0.44, +-1.94,-0.22, +-2.6,-1.09, +-2.88,0.17, +-3.25,0.57, +-1.84,2.39, +-3.45,2.99, +-1.98,4.36, +-0.79,4.12, +-0.22,4.67, +-0.86,4.86, +0.06,2.55, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.79,-10.41, +-3.79,-10.41, +-3.77,-10.42, +-3.13,-10.61, +-2.06,-10.24, +-1.73,-9.79, +-1.77,-10.05, +-1.77,-10.76, +-0.44,-11.06, +-0.04,-11.08, +-0,-11.34, +-0,-11.88, +-0,-12.05, +1.08,-11.99, +1.72,-12.25, +2.22,-12.8, +2.26,-12.83, +2.33,-13.21, +2.43,-13.78, +2.43,-13.78, +2.49,-14.09, +2.6,-14.72, +2.61,-14.8, +2.73,-15.47, +2.77,-15.7, +3.31,-15.58, +5.12,-15.09, +5.45,-14.98, +5.44,-14.94, +5.14,-14.12, +5.63,-13.86, +7.13,-12.6, +6.91,-11.97, +6.54,-11.33, +6.33,-10.96, +5.67,-9.82, +6.04,-8.49, +6.25,-7.45, +5.75,-6.85, +5.07,-6.94, +4.01,-6.95, +3.96,-6.89, +0.96,-4.62, +0,0, +0,0, +0,0, +0,0, +0,0, +1.7,-3.62, +5.57,-6.42, +3.89,-4.42, +5.14,-3.55, +5.54,-3.42, +-0.2,-1.27, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.11,-0.16, +2.21,-1.03, +6.96,0.61, +7.06,-0.01, +6.34,-1.68, +5.55,-2.47, +4.89,-3.76, +3.67,-4.89, +2.15,-5.81, +1.03,-6.86, +-0.03,-7.81, +-0.11,-7, +0.02,-6.74, +0.18,-5.92, +-0.1,-3.57, +0,0, +0,0, +0,0, +0,0, +1.32,-0.23, +5.93,-1.05, +5.75,-1.72, +5.96,-0.75, +5.83,1.43, +6.06,2.35, +6.05,2.2, +5.87,2.13, +6.26,1.69, +6.09,1.2, +5.66,2.06, +5.47,2.48, +5.5,3.88, +4.87,4.99, +4.57,5.44, +3.94,6.09, +3.84,6.65, +4.54,6.12, +5.75,5.02, +5.69,6.13, +5.76,5.99, +6.38,5.36, +7.52,3.63, +7.93,1.09, +8.35,-1.21, +7.47,-4.11, +6.75,-6.24, +6.83,-5.47, +7.15,-2.48, +6.48,-3.74, +7.07,-3.88, +6.9,-4.41, +3.58,-4.66, +1.93,-2.6, +1.82,-2.04, +2.35,-1.92, +1.49,-3.52, +1.24,-5.63, +1.2,-6.79, +-0,-6.61, +-0.51,-5.86, +-0.26,-5.93, +-1.86,-6.41, +-2.63,-7.8, +-3.06,-8.4, +-3.56,-7.98, +-5.38,-7, +-6.66,-6.52, +-7.3,-7.4, +-6.47,-7.27, +-6.35,-6.93, +-6.63,-7.14, +-4.42,-6.22, +-3.73,-5.19, +-1.67,-7.31, +-3.75,-7.84, +-5.02,-7.36, +-5.82,-6.93, +-6.06,-7.22, +-6.25,-6, +-5.88,-6.32, +-4.31,-6.33, +-2.79,-6.79, +-2.54,-6.97, +-1.66,-6.99, +-1.24,-6.17, +-0.77,-8.23, +-0,-9.75, +1.53,-9.93, +1.67,-9.49, +3.16,-8.18, +3.62,-5.97, +4.64,-5.53, +6.02,-4.4, +7.01,-2.98, +7.29,-0.8, +7.57,0.89, +4.86,1.96, +3.41,2.8, +2.63,3.29, +2.62,3.13, +3.05,2.9, +2.89,2.8, +3.25,1.71, +2.16,-0.48, +1.27,-1.35, +1.1,-1.57, +1.13,-1.27, +0.31,-1.11, +0.76,-0.35, +1.89,-0.18, +3.45,-1.39, +3.62,-1.82, +2.59,-2.3, +3.96,-1.48, +1.36,-1.62, +3.32,-1.41, +3.03,-1.75, +3.93,-1.4, +2.85,-0.39, +3.09,0.25, +3.22,0.32, +3.9,0.44, +4.25,0.44, +4.42,0.24, +3.97,-1.2, +4.1,-2.93, +3.42,-3.84, +2.61,-4.3, +1.8,-3.83, +0.75,-3.08, +-1.22,-1.25, +-1.58,-0.2, +0.79,0.95, +0.85,2.94, +1.16,3.39, +0.02,3.37, +-0.51,4.19, +-0.02,4.55, +0,4.78, +-0.26,3.85, +-0.02,2.68, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.79,-10.41, +-3.79,-10.41, +-3.77,-10.42, +-3.04,-9.91, +-1.82,-9.74, +-1.72,-9.76, +-1.76,-9.98, +-1.66,-10.51, +-0.13,-11.01, +-0,-11.08, +-0,-11.61, +-0,-12.05, +-0,-12.05, +-0,-12.33, +0.85,-12.98, +2.14,-12.85, +2.39,-13.55, +2.43,-13.78, +2.43,-13.78, +2.53,-14.36, +2.6,-14.74, +2.62,-14.84, +2.76,-15.68, +3.01,-15.65, +4.96,-15.13, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.38,-14.22, +7.05,-13.19, +7.35,-12.74, +7,-12.12, +6.78,-11.75, +6.43,-11.13, +5.77,-9.78, +5.83,-8.23, +6.29,-7.5, +5.78,-6.88, +5.08,-6.69, +3.99,-6.9, +2.21,-5.94, +-0.26,-0.71, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.16,-1.16, +-0.14,-0.17, +0,0, +-0.11,-0.13, +-0.12,-0.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.43,-0.69, +5.76,0.58, +6.48,0.4, +6.94,-0.36, +6.81,-1.46, +6.24,-2.5, +5.99,-3.49, +5.02,-4.35, +3.42,-4.86, +1.94,-5.76, +0.87,-6.29, +0.52,-5.96, +0.55,-5.29, +0.81,-4.7, +0.06,-2.62, +-0.05,-0.19, +0,0, +0,0, +0,0, +4.27,-0.45, +5.9,-1.16, +5.93,-1.05, +5.98,-0.12, +5.55,2.18, +4.99,3.34, +4.45,2.77, +4.95,3.4, +5.83,2.37, +5.97,2.17, +5.35,2.69, +5,3.81, +5.19,5.01, +5.07,5.81, +5.12,6.11, +5.25,5.98, +5.54,7, +4.42,7.65, +5.3,6.66, +6.41,6.7, +7.5,6.29, +7.87,5.14, +7.62,4.22, +7.05,0.77, +6.89,-1.63, +8.1,-2.97, +8.59,-5.46, +8.55,-4.71, +6.8,-1.47, +5.01,-1.45, +7.67,-2.05, +7.33,-3.51, +5.48,-6.53, +4.17,-3.58, +3.84,-2.14, +4.15,-1.77, +3.31,-2.7, +1.79,-4.99, +0.03,-6.7, +-4.05,-5.58, +-5.08,-1.82, +-4.11,-3.82, +-2.99,-7.06, +-2.93,-8.06, +-3.06,-8.4, +-2.82,-8.24, +-4.19,-8.23, +-4.93,-8.55, +-5.27,-9.13, +-5.78,-7.56, +-6.73,-6.72, +-6.08,-5.74, +-6.35,-3.89, +-5.89,-3.9, +-3.77,-6.57, +-3.89,-7.84, +-4.63,-6.89, +-5.41,-6.75, +-6.01,-6.44, +-6.66,-5.59, +-6.41,-4.69, +-5.33,-4.98, +-4.67,-5.22, +-3.98,-5.82, +-2.95,-6.53, +-2.28,-6.21, +-3.11,-6.18, +-2.52,-7.65, +-0.89,-9.18, +-0.24,-8.88, +0.09,-8.6, +3.5,-6.06, +4.25,-5.54, +5.31,-4.46, +7.25,-2.29, +7.9,-0.79, +7.2,0.87, +5,1.66, +4.04,3.01, +3.19,3.8, +3.16,3.77, +3.22,3.59, +3.26,2.92, +3.19,0.72, +2.65,-0.42, +2.11,-1.65, +2,-1.52, +1.8,-1.69, +1.56,-1.44, +1.36,-0.49, +2.6,-0.88, +3.04,-2.68, +2.83,-2.13, +3.48,-1.87, +3.9,-1.49, +2.05,-1.67, +3.3,-1.48, +4.26,-1.73, +4.02,-1.68, +3.62,-1.15, +4.18,-1.1, +4.15,-1.07, +4.16,-0.73, +4.18,-0.87, +4.49,0.95, +4.73,-1.72, +4.44,-2.39, +4.06,-3.75, +3.64,-4.31, +2.99,-3.72, +2.63,-3.29, +1.59,-2.66, +0.79,-1.21, +0.5,0.57, +0.29,1.92, +0.74,3.1, +1.3,3.15, +0.65,4.35, +0.4,4.31, +0.11,5.04, +0.37,3.85, +0.03,2.47, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.79,-10.41, +-3.79,-10.41, +-3.79,-10.41, +-2.69,-10.48, +-1.78,-9.97, +-1.73,-9.79, +-1.77,-10.02, +-1.77,-10.52, +-0.92,-11.03, +-0.04,-11.08, +-0,-11.61, +-0,-12.05, +-0,-12.05, +-0,-12.48, +0.15,-13.02, +1.67,-12.92, +2.41,-13.66, +2.43,-13.78, +2.43,-13.78, +2.55,-14.45, +2.6,-14.74, +2.61,-14.8, +2.96,-15.41, +4.89,-15.16, +5.45,-14.98, +5.45,-14.98, +5.42,-14.9, +5.3,-14.46, +6.06,-13.69, +7.48,-12.96, +7.27,-12.59, +7,-12.12, +6.68,-11.57, +6.43,-10.95, +6.76,-9.04, +6.7,-8.02, +6.28,-7.49, +5.75,-6.85, +5.19,-6.78, +3.51,-6.75, +0.44,-3.03, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.37,-0.76, +4.8,1.23, +5.91,1.16, +5.99,0.77, +6.22,0.07, +6.15,-0.56, +6.11,-1.21, +5.89,-1.72, +5.47,-2.57, +4.12,-3.16, +2.8,-3.42, +1.5,-3.93, +0.91,-3.69, +0.64,-3.11, +0.53,-2.87, +0.19,-2.13, +-0.23,-0.59, +0,0, +0,0, +0,0, +3.88,0.35, +5.84,-0.32, +5.17,-0.91, +5.16,-0.1, +4.84,2.29, +4.17,3.98, +3.56,4.24, +4.19,4.99, +4.38,3.87, +4.36,3.78, +3.87,4.39, +4.5,5.36, +5.03,6, +5.12,6.11, +4.97,5.92, +6.15,6.64, +6.09,7.26, +6.14,7.76, +6.37,7.59, +6.64,7.54, +7.39,5.88, +8.52,4.26, +8.71,3.17, +7.71,1.69, +7.97,0, +9.33,-0.54, +9.64,-3.28, +9.34,-5.39, +8.62,-3.61, +7.79,-1.62, +8.21,-0.57, +7.82,-1.64, +7.15,-6.25, +6.42,-4.67, +5.66,-2.06, +5.62,-1.28, +5.68,-2.41, +3.83,-5.87, +-1.21,-8.5, +-6.61,-4.86, +-7.21,-0.37, +-6.67,0.11, +-6.21,-4.75, +-3.88,-8.63, +-3.19,-8.77, +-3.06,-8.4, +-3.06,-8.4, +-3.1,-9.25, +-2.52,-9.89, +-3.58,-8.65, +-4.93,-8.54, +-5.36,-6.69, +-6.14,-4.48, +-6.76,-4.15, +-5.39,-6.71, +-2.65,-8.92, +-2.8,-8.45, +-4.07,-7.93, +-4.45,-6.75, +-5.49,-5.51, +-5.45,-5.17, +-5.77,-4.16, +-5.78,-3.9, +-5.66,-3.5, +-5.46,-3.15, +-5.08,-2.96, +-4.92,-2.5, +-5.04,-4.13, +-3.52,-6.93, +-2.43,-7.57, +-1.67,-8.34, +3.62,-5.97, +4.21,-5.56, +5.85,-5.13, +6.6,-2.91, +7.07,-0.72, +5.98,0.51, +4.63,1.99, +4,3.07, +3.46,3.66, +3.61,4.3, +3.8,4.52, +3.16,3.62, +2.85,1.03, +2.79,-0.75, +2.56,-1.33, +2.28,-1.81, +3.06,-1.57, +3.06,-1.45, +3.53,-1.24, +5.1,-1.1, +3.61,-1.64, +2.26,-1.87, +-1.54,-1.41, +0.04,-2.23, +1.89,-2.62, +3.04,-2.07, +4.14,-1.88, +3.63,-1.55, +4.33,-1.42, +5.1,-1.37, +5.35,-1.81, +4.9,-1.62, +4.3,-2.37, +4.21,-2.77, +4.81,-1.85, +4.84,-2.19, +4.97,-2.55, +4.49,-4, +4.65,-3.46, +3.99,-2.76, +2.56,-1.96, +1.73,-1.24, +1.4,0.36, +0.74,1.55, +1.32,1.78, +1.42,3.24, +1.74,3.71, +0.81,4.62, +0.95,3.77, +-0.24,3.97, +-0.76,1.96, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.79,-10.41, +-3.79,-10.41, +-3.14,-10.6, +-2.37,-10.81, +-1.88,-10.66, +-1.82,-10.29, +-1.91,-10.84, +-1.92,-10.89, +-1.88,-10.92, +-0.98,-11.01, +-0.04,-11.58, +-0,-12.05, +-0,-12.05, +-0,-12.36, +0.18,-13.02, +1.53,-12.94, +2.41,-13.66, +2.43,-13.78, +2.43,-13.78, +2.54,-14.42, +2.6,-14.74, +2.6,-14.74, +3.19,-15.12, +5.3,-14.91, +5.33,-14.63, +5.33,-14.64, +5.17,-14.21, +5.58,-13.9, +7.37,-13.03, +7.48,-12.96, +7.29,-12.63, +7,-12.12, +6.85,-11.86, +6.89,-10.96, +7.74,-9.3, +6.93,-8.25, +6.4,-7.62, +5.94,-7.07, +5.7,-6.86, +1.45,-5.09, +-0.07,-0.08, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.04,-0.56, +2.15,0.76, +5.02,2.09, +5.27,1.66, +5.85,1.29, +5.95,0.91, +6,0.42, +5.99,0.49, +6,0.25, +5.37,-0.06, +4.11,-0.62, +2.98,-0.57, +1.64,-0.99, +1.21,-0.86, +0.74,-0.33, +0.2,-0.32, +-0.28,-0.25, +-0.01,-0.02, +0,0, +0,0, +0.68,0.25, +4.83,1.41, +5.01,-0.1, +5.01,-0.5, +5.02,0.2, +4.74,1.59, +3.4,3.72, +3.2,4.79, +2.88,5.28, +3.12,4.98, +3.3,5.02, +3.18,5.5, +4.01,6.21, +5.12,6.11, +5.12,6.11, +5.52,5.54, +6.91,6.3, +6.37,7.59, +6.56,7.82, +6.65,8.64, +4.99,8.18, +4.44,7.69, +6.3,5.2, +9.19,2.92, +9.44,1.67, +9.68,1.71, +9.71,0.74, +8.82,-3.44, +9.89,-5.13, +11.17,-4.07, +10.91,-2.61, +10.36,-1.54, +9.14,-1.07, +7.98,-3.72, +6.02,-5.73, +5.36,-2.97, +5.74,-0.13, +6.75,-1.61, +4.1,-7.54, +-1.69,-9.58, +-6.07,-6.64, +-8.18,-1.47, +-7.77,-0.56, +-7.45,-4.66, +-5.11,-8.89, +-3.46,-9.5, +-3.12,-8.59, +-3.46,-8.21, +-1.66,-9.4, +-1.27,-9.56, +-1.72,-9.76, +-2.85,-9.56, +-3.7,-8.64, +-4.72,-7.52, +-5.83,-7.79, +-5.78,-7.56, +-4.96,-8.59, +-3.39,-9.32, +-3.56,-9.28, +-4.11,-8.27, +-4.76,-7.28, +-5.23,-6.23, +-5.51,-5.14, +-5.68,-4.77, +-5.34,-4.71, +-5.31,-4, +-4.95,-2.28, +-4.88,-1.26, +-4.33,-2.93, +-2.97,-6.22, +-1.79,-7.88, +-0.77,-7.63, +3.5,-6.06, +4.36,-5.46, +5.51,-4.82, +5.95,-2.6, +5.85,-0.65, +5.32,0.61, +4.72,1.72, +4.12,2.89, +3.78,4.12, +3.77,4.69, +3.16,5.12, +2.56,4.14, +2.08,2.25, +2.97,-0.41, +2.65,-1.62, +3.24,-0.81, +3.56,-1.59, +3.94,-1.43, +5.63,-1.7, +4.94,-1.02, +3.97,-1.59, +3.74,-1.61, +1.16,-2.76, +3.46,-2.34, +4.31,-1.57, +4.16,-1.25, +5.01,-0.5, +5.59,-0.87, +5.3,-1.53, +6.54,-1.59, +5.87,-1.92, +5.26,-2.63, +4.02,-3.04, +4.84,-2.57, +5.51,-2.38, +5.65,-1.64, +5.41,-1.92, +5.26,-3.24, +5.19,-3.47, +4.39,-3.35, +3.47,-2.77, +2.76,-1.79, +2.11,-0.18, +1.8,0.95, +1.67,2.05, +2.49,2.63, +1.82,3.99, +1.48,3.76, +-0.24,3.7, +-1.13,3.86, +-2.15,1.93, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.38,-10.88, +-2.59,-10.75, +-1.97,-10.9, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.84,-10.92, +-1.41,-11.48, +-0.21,-12.05, +-0,-12.05, +-0,-12.38, +-0,-13.02, +0.33,-13.01, +1.98,-13.65, +2.43,-13.78, +2.43,-13.78, +2.55,-14.48, +2.6,-14.74, +2.71,-14.72, +4.09,-14.55, +5.24,-14.38, +5.12,-14.07, +5.12,-14.07, +5.69,-13.83, +7.12,-13.15, +7.48,-12.96, +7.48,-12.96, +7.35,-12.73, +7,-12.12, +7.11,-12.05, +8.29,-10.74, +8,-9.53, +7.37,-8.79, +6.64,-7.91, +6.29,-7.5, +2.94,-6.11, +-0.43,-1.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.12,-0.15, +1.09,-0.08, +3.94,1.46, +2.5,1.22, +2.85,1.41, +4.42,2.12, +4.99,1.88, +5.67,2, +5.73,1.85, +6.2,2.16, +6.39,2.22, +6.2,2.17, +4.44,2.01, +3.32,2.06, +1.81,1.71, +1.08,1.67, +0.51,1.68, +-0.31,0.05, +-0.18,0.03, +0,0, +0,0, +0,0, +2.16,1.52, +4.3,2.61, +4.87,1.19, +5,0.66, +4.94,1, +4.65,1.9, +3.38,3.87, +2.48,5.34, +2.18,5.61, +2.2,5.6, +2.48,5.47, +2.81,6.03, +3.68,6.69, +4.64,6.45, +5.12,6.11, +5.49,5.74, +7.27,6.47, +6.37,7.59, +6.36,8.01, +4.96,8.59, +4.76,8.68, +4.67,8.43, +5.99,7.43, +8.86,4.13, +10.7,2.28, +10.26,1.81, +8.51,-1.03, +8.52,-2.6, +11.07,-2.23, +11.37,-2.71, +11.69,-2.77, +10.69,-2.75, +9.51,-2.68, +7.28,-4.38, +4.7,-6.4, +5.03,-4.83, +4.23,-0.06, +5.29,0.76, +4.11,-6.33, +-1.06,-10.4, +-5.14,-7.84, +-7.71,-4.49, +-8.73,-2.73, +-8.32,-5.54, +-5.94,-9.08, +-3.91,-10.75, +-3.55,-9.12, +-4.2,-8.66, +-2.95,-8.68, +-1.36,-8.96, +-0.19,-10.18, +-0.55,-10.53, +-1.52,-9.75, +-2.91,-9.44, +-3.88,-9.95, +-3.94,-10.13, +-3.72,-10.23, +-2.86,-9.59, +-2.97,-10.03, +-3.16,-9.25, +-3.77,-8.99, +-4.91,-7.61, +-5.25,-7.2, +-5.51,-5.89, +-5.3,-5.63, +-4.52,-5.11, +-5.11,-2.84, +-5.02,-1.27, +-4.7,-1.71, +-3.87,-3.7, +-2.14,-7.53, +-1.12,-8.6, +3.31,-6.15, +3.96,-5.58, +4.66,-5.01, +5.18,-2.79, +5.14,-0.65, +5,0.63, +4.78,1.62, +4.21,2.77, +3.66,3.61, +3.26,5.05, +2.76,5.31, +1.41,4.81, +1.5,3.35, +2.01,0.84, +2.51,-0.99, +2.48,-1.49, +3.9,-1.17, +4.32,-1.48, +5.73,-1.19, +5.11,-1.06, +4.43,-1.7, +3.56,-2.12, +4.45,-1.67, +4.92,-1.63, +4.29,-1.56, +4.01,-0.35, +4.36,0.86, +5.66,-0.2, +6.41,-2.17, +5.79,-2.45, +5.79,-2.77, +5.11,-3.12, +5,-3.23, +4.98,-3.28, +5.71,-1.89, +5.95,-0.92, +5.9,-1.19, +5.88,-2.3, +5.95,-2.97, +5.28,-2.88, +4.83,-2.08, +4.03,-1.33, +3.66,-0.62, +2.51,0.53, +2.83,1.43, +2.36,2.54, +2.11,3.25, +0.87,3.93, +-0.61,4.22, +-2.03,3.52, +-2.78,2.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.01,-11.41, +-1.93,-10.93, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.3,-11.62, +-0.07,-12.05, +-0,-12.05, +-0,-12.53, +-0,-13.02, +0.05,-13.02, +1.22,-13.64, +2.43,-13.78, +2.43,-13.78, +2.53,-14.36, +2.63,-14.74, +3.55,-14.52, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.55,-13.88, +7.09,-13.17, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.47,-12.94, +7.3,-12.36, +8.5,-11.1, +8.83,-10.57, +8.29,-9.88, +7.51,-8.95, +7.1,-8.02, +6.56,-7.43, +5.94,-7.34, +-0.13,-1.38, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.53,0.08, +2.32,0.07, +3.86,2.2, +1.37,0.73, +0,0, +1.13,0.75, +3.74,2.33, +4.55,2.58, +5.28,2.94, +5.71,3.28, +5.95,3.74, +6.44,4.22, +5.87,4.24, +4.57,4.16, +3.17,4.29, +2.06,3.98, +1.2,4.1, +-0.19,3.69, +-0.24,-0.18, +0,0, +0,0, +0,0, +0,0, +2.64,2.21, +4.49,3.63, +4.47,2.33, +4.75,1.73, +4.8,1.54, +4.64,1.96, +3.7,3.35, +2.71,4.96, +2.06,5.66, +2.06,5.66, +2.06,5.66, +2.36,6.48, +3.3,7.04, +4.11,6.81, +4.96,6.56, +5.12,6.11, +6.67,6.23, +7.18,7.61, +7.26,7.99, +6.81,8.5, +7.73,8.4, +8.43,7.7, +8.77,6.97, +9.93,5.24, +10.75,2.55, +10.62,1.87, +9.36,-0.39, +9.58,-0.6, +11.02,0.72, +10.49,-0.55, +10.31,-2.38, +9.26,-3.9, +8.21,-4.47, +6.33,-5.83, +4.61,-7.14, +5.57,-4.25, +4.36,-0.03, +4.55,1.22, +2.53,-7.07, +-1.42,-11.23, +-3.62,-9.33, +-6.52,-7.42, +-7.66,-6.2, +-7.97,-7.72, +-5.65,-10.26, +-4.03,-11.06, +-3.84,-10.54, +-4.32,-9.84, +-3.54,-8.89, +-2.35,-9.15, +0.03,-10.25, +0.48,-10.75, +-0,-10.25, +-0.8,-10.68, +-0.77,-11.4, +-1.02,-11.48, +-1.28,-11.08, +-1.47,-10.17, +-1.58,-10.21, +-1.72,-9.76, +-2.34,-10.28, +-3.45,-10.1, +-4.76,-8.73, +-5.08,-7.45, +-4.55,-6.5, +-4.31,-5.61, +-4.72,-3.96, +-5.12,-2.18, +-5.15,-1.64, +-4.82,-2.37, +-3.33,-6.51, +-1.01,-8.65, +2.67,-6.45, +3.5,-6.06, +4.21,-5.56, +4.71,-3.49, +4.94,-0.87, +4.74,0.84, +4.75,1.73, +4.19,2.8, +3.6,3.52, +2.89,4.57, +1.7,5.38, +0.93,5.25, +0.94,4.33, +1.69,1.27, +2.15,-1.33, +2.49,-1.74, +3.96,-0.84, +4.93,-1.05, +5.95,-0.87, +5.04,-1.58, +5.32,-1.19, +5.07,-0.2, +5.16,-0.61, +4.98,-0.88, +4.99,-0.73, +4.38,0.16, +4.27,0.24, +5.64,-0.67, +6.77,-2.12, +6.06,-3.5, +5.06,-3.61, +4.62,-4.1, +4.73,-3.08, +5.75,-1.04, +5.78,-1.61, +6.03,0, +6.16,-0.35, +6.05,-1.07, +6.58,-2.39, +6.35,-2.31, +5.75,-2.25, +4.82,-1.87, +3.85,-0.05, +3.65,0.82, +2.82,1.54, +1.89,2.81, +1.16,3.74, +-1.05,4.26, +-2.3,4.42, +-3.16,3.57, +-2.28,1.8, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.01,-11.39, +-1.93,-10.95, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.73,-10.94, +-1.63,-10.95, +-0.44,-11.49, +-0,-12.05, +-0,-12.05, +-0,-12.1, +0.1,-12.81, +0.84,-12.97, +2.09,-12.89, +2.37,-13.45, +2.43,-13.76, +2.43,-13.78, +3.23,-13.85, +4.8,-13.94, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +6,-13.58, +7.47,-12.95, +7.48,-12.96, +7.48,-12.96, +7.48,-12.96, +7.6,-12.89, +8.48,-12.21, +9.47,-11.34, +9.13,-10.89, +8.66,-10.32, +7.94,-9.17, +7.9,-7.85, +7.49,-6.57, +6.17,-7.24, +0.45,-3.44, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.12,-0.15, +3.9,0.82, +4.71,2.68, +0,0, +0,0, +1.17,0.98, +3.1,2.61, +3.78,2.94, +4.49,3.49, +4.97,3.68, +5.57,4.13, +5.94,4.93, +6.35,5.43, +5.67,5.4, +4.38,5.56, +3.35,5.68, +2.1,5.56, +1.09,5.57, +-1.13,1.06, +0,0, +0,0, +0,0, +0,0, +0.75,0.43, +4.82,3.59, +4.63,3.37, +4.98,2.5, +5.35,1.83, +5.27,1.92, +4.77,2.19, +4.12,2.89, +2.96,4.34, +2.42,5.5, +2.06,5.66, +1.53,6.03, +2,6.68, +2.62,7.18, +3.99,6.9, +4.01,7.25, +4.59,7.22, +6.17,6.9, +7.76,7.19, +7.71,7.86, +8.12,8.86, +8.3,8.68, +9.68,7.1, +9.99,6.67, +10.08,5.76, +10.47,3.08, +10.25,2.28, +10.2,0.7, +10.38,1.37, +9.72,2.01, +8,1.09, +8.39,-1.56, +8.44,-4.4, +7.4,-5.39, +5.89,-6.71, +5.85,-6.7, +6.44,-4.06, +6.23,-0.84, +6.21,-1.73, +3.8,-9.87, +0.26,-12.03, +-2.89,-10.41, +-4.41,-8.77, +-6.21,-8.89, +-6.13,-9.18, +-5.61,-10.27, +-4.4,-11.33, +-4.2,-11.53, +-3.93,-10.8, +-3.61,-9.92, +-2.51,-10.55, +-1.08,-9.72, +1.34,-10.77, +2.09,-11.87, +1.93,-12.16, +1.74,-11.9, +0.7,-11.88, +0.6,-11.44, +0.24,-10.48, +0.43,-10.02, +-0.52,-10.87, +-1.44,-11.08, +-2.29,-11.15, +-3.92,-10.14, +-3.57,-9.08, +-3.41,-7.84, +-3.82,-6.62, +-4.9,-4.76, +-4.9,-4.11, +-5.5,-3.18, +-5.25,-3.36, +-4.39,-5.38, +-0.39,-8.96, +2.83,-7.32, +3.74,-6.48, +4.31,-5.94, +4.29,-3.67, +4.82,-1.4, +4.32,0.41, +4.3,1.67, +4.15,2.65, +3.46,3.66, +2.66,3.99, +1.33,4.52, +0.78,4.99, +0.4,4.65, +1.84,1.33, +2.29,-1.14, +2.92,-1.88, +4.25,-1.17, +6.2,-0.81, +5.93,-1.05, +5.93,-1.05, +4.7,1.4, +1.98,3.43, +4.49,1.61, +5.12,0.22, +5.86,0, +6.26,-0.15, +5.6,-1.14, +5.91,-2.29, +6.63,-2.57, +6.24,-3.61, +5.36,-4.5, +4.65,-4.87, +5.19,0.68, +5.93,-0.7, +6.22,-1.85, +6.1,-0.52, +6.12,-0.26, +6.73,-1.19, +6.63,-1.67, +5.84,-1.85, +5.02,-1.36, +4.45,-0.99, +4.02,-0.13, +2.99,0.98, +0.89,2.26, +-0.1,3.37, +-1.14,4.04, +-3.03,4.19, +-3.41,4.26, +-3.64,4.17, +-3.14,3.57, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.99,-11.29, +-1.99,-11.29, +-1.93,-10.93, +-1.83,-10.93, +-1.31,-10.99, +-1.49,-10.97, +-1.28,-10.99, +-1.24,-10.99, +-0.17,-11.07, +-0.15,-11.12, +-0,-11.66, +-0,-12.05, +-0,-12.05, +0.37,-12.03, +1.7,-12.44, +2.06,-12.85, +2.26,-12.83, +2.28,-12.95, +2.42,-13.7, +3.14,-13.62, +4.47,-13.25, +4.84,-13.29, +5.05,-13.86, +5.02,-13.8, +5.59,-13.49, +6.77,-12.37, +7.27,-12.59, +7.48,-12.96, +7.48,-12.96, +7.53,-12.93, +8.45,-12.33, +9.6,-11.48, +9.62,-11.47, +9.42,-11.22, +9.1,-9.95, +9.42,-8.32, +8.57,-7.22, +7.81,-6.74, +1.44,-3.97, +-0.16,-0.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.44,0.14, +3.77,1.22, +4.86,2.43, +2.45,1.37, +0,0, +0,0, +2.94,2.82, +2.94,2.98, +3.72,3.49, +4.25,3.98, +4.83,4.38, +5.37,5.17, +6.02,5.53, +6.31,6.62, +5.93,6.86, +2.35,4.59, +1.36,3.85, +0.16,3.05, +0.17,5.72, +-1.08,0.44, +0,0, +0,0, +0,0, +0,0, +1.23,0.86, +5.22,2.69, +4.71,2.58, +5.04,2.42, +5.56,2.02, +5.66,2.06, +5.47,2.47, +4.49,2.93, +3.15,4.53, +2.17,5.61, +1.93,5.7, +1.26,6.31, +1.64,6.89, +2.73,7.49, +3.19,7.75, +3.79,7.45, +4.7,7.25, +6.03,7.18, +6.5,7.74, +6.96,8.29, +7.66,9.13, +7.75,9.23, +8.95,7.85, +9.13,7.66, +8.71,6.81, +9.6,5.54, +9.71,4.67, +10.41,3.79, +9.25,3.1, +8.5,2.71, +7.86,2.59, +7.8,0.72, +8.04,-3.42, +6.85,-5.75, +6.81,-5.96, +7.35,-6.16, +8.11,-4.88, +8.64,-2.7, +8.62,-2.74, +7.74,-8.57, +3.12,-12.05, +0.67,-10.88, +-2.27,-9.56, +-3.12,-10.44, +-3.83,-11.4, +-3.9,-11.67, +-4.45,-12.24, +-4.45,-12.24, +-3.58,-11.92, +-3.06,-11.04, +-2.2,-11.06, +-1.18,-10.83, +0.87,-11.42, +2.13,-12.08, +3.05,-12.78, +3.01,-12.51, +3.13,-12.16, +2.36,-11.65, +2.39,-10.79, +1.72,-9.76, +1.19,-10.46, +0.27,-11.19, +-1.7,-11.17, +-1.84,-10.42, +-3.01,-8.99, +-3.49,-8.47, +-4.17,-6.77, +-4.5,-5.36, +-4.25,-5.54, +-4.34,-5.41, +-4.99,-4.87, +-4.06,-6.34, +-1.07,-8.78, +3.36,-7.2, +3.99,-6.9, +4.32,-6.22, +4.21,-3.73, +3.88,-1.55, +3.66,0.41, +3.5,1.43, +3.41,2.46, +3.07,3.14, +2.29,3.36, +1.13,4.05, +0.45,4.6, +0.34,4.8, +1.19,2.73, +2.2,-0.46, +3.56,-1.96, +5.7,0, +5.28,-0.55, +5.66,-1, +6.07,-0.92, +4.67,2.25, +-0.82,3.97, +1.49,4.47, +4.43,2.61, +6.48,0.71, +6.91,-1.01, +6.68,-2, +6.12,-3.36, +5.52,-2.21, +6.06,-1.6, +5.48,-5.46, +5.26,-3.08, +6.08,2.42, +5.97,-0.39, +6.57,-1.34, +6.41,-1.13, +6.26,-0.99, +6.75,-1.19, +6.14,-1.22, +4.93,-1.05, +4.36,-0.77, +3.97,-0.56, +2.52,0.68, +2.48,1.46, +-0.21,2.41, +-0.76,3.63, +-2.63,3.32, +-3.47,4.3, +-3.79,5.46, +-4.14,6.12, +-2.43,4.21, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.54,-10.97, +-0.87,-11.19, +-0.72,-11.4, +-0.81,-11.55, +-0,-11.52, +-0,-11.1, +-0.03,-11.08, +-0,-11.12, +-0,-11.08, +-0,-11.68, +-0,-12.05, +-0,-12.05, +0.14,-12.05, +1.39,-11.95, +2.12,-12.04, +2.25,-12.76, +2.26,-12.83, +2.34,-12.85, +2.91,-13.27, +4.63,-13.18, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +5.19,-12.98, +6.84,-12.21, +7,-12.12, +7.05,-12.2, +7.34,-12.71, +7.99,-12.64, +8.94,-11.98, +9.6,-11.48, +9.62,-11.47, +9.8,-11.39, +10.86,-10.58, +10.58,-9.13, +9.56,-8.02, +8.79,-7.37, +2.9,-5.19, +-0.27,-0.42, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.93,-0.31, +4.02,1.1, +4.18,1.52, +4.14,1.44, +3.32,1.49, +3.48,2.13, +2.79,2.31, +2.74,3.02, +3.05,3.64, +3.64,4.34, +4.25,5.06, +4.73,5.64, +5.31,6.33, +5.83,6.95, +6.25,7.45, +3.49,4.59, +-0.57,-0.38, +0,0, +0,0, +-1.12,0.51, +0,0, +0,0, +0,0, +0,0, +0,0, +4.46,2.75, +5.66,2.58, +6.58,2.39, +6.54,2.09, +5.95,1.51, +5.73,1.81, +5.72,1.84, +4.97,2.52, +3.42,3.8, +2.54,5.29, +2.06,5.66, +1.72,6.51, +1.29,7.3, +2.09,7.76, +3.06,8.4, +3.53,7, +5.11,7.3, +6.65,7.55, +7.8,7.08, +7.77,8.76, +7.75,9.23, +7.66,9.13, +7.8,7.58, +8.05,7.01, +8.49,7.12, +9.1,6.24, +9.6,5.54, +9.68,4.96, +9.34,3.4, +8.25,2.56, +7.25,2.11, +7.49,1.95, +6.66,-3.72, +6.96,-5.59, +8.44,-5.16, +9.18,-4.59, +9.53,-4.76, +10.39,-3.3, +9.66,-2.2, +8.38,-6.61, +5.99,-12.07, +3.87,-11.56, +1.77,-9.81, +0.22,-10.35, +-1.33,-11.94, +-1.4,-11.93, +-3.1,-12.93, +-3.24,-12.56, +-2.09,-11.87, +-2.05,-11.63, +-1.83,-11.89, +-0.35,-11.86, +0.93,-11.86, +2.79,-12.25, +4.64,-12.75, +4.64,-12.76, +4.8,-12.23, +4.98,-11.33, +4.43,-10.29, +3.96,-9.62, +2.59,-9.73, +0.82,-10.84, +-0.43,-11.15, +-1.78,-10.09, +-3.02,-8.86, +-2.81,-8.47, +-4.09,-7.09, +-3.64,-5.96, +-3.23,-6.34, +-2.67,-6.95, +-3.15,-6.36, +-2.99,-6.98, +-1.58,-7.63, +3.71,-6.77, +3.99,-6.9, +4.69,-5.9, +4.36,-4.99, +3.84,-2.39, +3.38,-0.11, +2.32,1.14, +2.1,1.84, +2.05,2.29, +1.87,2.22, +1.53,2.7, +0.2,3.87, +-0.1,4.69, +-0.04,3.96, +1.82,1.04, +3.37,-0.8, +5.23,0.29, +5.78,0, +5.98,-0.52, +6.24,-1.21, +4.97,1.29, +-1.66,4.18, +-1.42,3.9, +1.82,4.41, +5.23,2.72, +6.92,-0.61, +7.08,-2.58, +6.65,-2.61, +5.5,1.55, +6.3,4.21, +6.3,3.35, +6.12,1.87, +6.69,-0.6, +6.61,-0.23, +6.58,-1.31, +6.53,-1.15, +6.41,-1.13, +6.55,-1, +5.35,0.07, +4.1,0.43, +3.79,1.68, +2.6,1.54, +1.81,0.71, +0.47,1.15, +-0,3.14, +-1.36,3.93, +-3.11,3.97, +-3.34,5.49, +-3.33,6.81, +-3.25,7.91, +-2.51,5.38, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-11.08, +-0,-11.25, +-0,-11.94, +-0,-12.02, +-0,-11.92, +-0,-11.84, +-0,-11.86, +0.19,-11.55, +0.33,-11.99, +0.61,-12.02, +0.35,-12.04, +1.44,-11.95, +2.07,-11.87, +2.09,-11.87, +2.17,-12.32, +2.56,-12.76, +3.67,-12.48, +4.22,-12.34, +4.63,-12.71, +4.78,-13.13, +4.79,-13.15, +5.18,-12.99, +6.52,-12.37, +7,-12.12, +7,-12.12, +7,-12.12, +7.63,-12.32, +9.09,-11.87, +9.62,-11.47, +9.62,-11.47, +9.84,-11.27, +10.67,-10.46, +11.49,-9.72, +10.96,-9.19, +9.66,-7.87, +8.38,-7.03, +1.16,-3.35, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.91,-0.46, +1.35,-0.92, +4,0.8, +3.99,0.83, +4,0.79, +3.84,1.35, +3.55,2, +3.2,2.53, +2.88,3.04, +3.26,3.86, +3.87,4.62, +4.4,5.25, +5,5.96, +5.5,6.55, +4.7,6.33, +2.39,3.55, +-0.63,0.23, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.67,0.39, +6.54,3.78, +6.33,2.63, +6.44,3.2, +6.62,1.61, +5.91,1.4, +5.76,1.17, +5.96,0.78, +5.11,1.22, +4.06,3.44, +2.7,5.37, +2.06,5.66, +2.13,6.36, +1.51,7.03, +1.38,7.85, +2.56,8.54, +3.59,7.55, +5.03,7.5, +7.08,7.44, +8.26,7.35, +8.14,8.15, +7.3,8.7, +6.75,8.04, +6.29,7.5, +6.55,7.42, +8.03,7.58, +8.61,7.23, +8.79,6.44, +8.96,5.18, +8.4,3.06, +7.74,1.37, +6.22,1.1, +6.64,1.5, +6.17,-1.54, +7.38,-4.51, +8.7,-3.94, +9.94,-3.62, +10.61,-3.86, +10.76,-4.19, +9.28,-1.21, +8.04,-3.87, +6.61,-10.18, +4.79,-12.44, +4.07,-10.53, +2.68,-8.79, +2.57,-10.59, +1.34,-11.61, +-0.26,-12.87, +-1.62,-12.88, +-2.09,-11.87, +-2.07,-11.75, +-0.6,-12, +0.93,-11.97, +2.09,-11.87, +3.91,-11.51, +4.99,-11.83, +6.23,-12.17, +6.94,-12.03, +6.51,-11.28, +6.14,-9.28, +5.25,-9.09, +3.79,-9.77, +1.39,-10.52, +-0.32,-10.47, +-1.67,-9.44, +-2.92,-8.77, +-2.74,-8.39, +-1.99,-7.44, +-1.52,-6.5, +-0.62,-6.36, +-0.41,-6.96, +-0.84,-7.77, +-1.15,-7.87, +-1.35,-7.64, +3.5,-6.06, +3.99,-6.9, +4.19,-6.61, +4.29,-5.11, +3.7,-3.76, +2.96,-1.73, +1.63,0.41, +1.38,1.18, +1.54,1.66, +1.78,1.4, +1.51,1.2, +0.39,2.16, +-0.82,3.26, +-0.3,4.35, +0.93,2.59, +3.03,-0.19, +5.05,0.37, +5.86,-0.52, +5.8,-1.55, +5.7,-1.92, +5.49,-1.06, +1.87,3.83, +-2.25,3.37, +0.08,4.46, +3.05,4.21, +5.49,1.71, +6.84,-0.43, +6.05,-1.57, +6.12,3.36, +6.58,2.39, +7.13,3.14, +6.79,3.52, +6.63,2.6, +7.47,0.15, +7.26,-1.46, +6.8,-1.06, +6.17,-0.17, +5.67,0.8, +3.99,2.01, +2.08,2.56, +0.63,3.58, +1.16,3.88, +1.65,3.57, +0.24,2.86, +0.21,4.16, +-1.88,4.35, +-3,5.46, +-2.45,6.73, +-2.82,7.75, +-3.06,8.4, +-2.04,5.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-11.08, +-0,-11.1, +-0,-11.4, +-0,-12.01, +0.08,-12.05, +0.49,-12.03, +1.13,-11.98, +1.7,-11.92, +2.02,-11.88, +2.09,-11.87, +2.07,-11.87, +2.06,-11.87, +2.14,-11.86, +2.25,-11.83, +3.5,-12.1, +4.29,-12.29, +4.42,-12.25, +4.45,-12.24, +4.5,-12.36, +4.86,-13.1, +5.75,-12.73, +6.34,-12.45, +7,-12.12, +7,-12.12, +7,-12.12, +7.23,-11.98, +8.55,-11.33, +9.59,-11.43, +9.71,-11.39, +10.63,-10.47, +10.98,-9.55, +11.09,-9.39, +11.07,-8.61, +10.38,-7.37, +8.78,-6.21, +1.78,-3.66, +-0.16,-0.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.46,-0.26, +1.94,-0.19, +2.76,-0.49, +2.22,-0.98, +4.61,0.38, +3.74,0.28, +3.94,0.37, +4.04,0.34, +4.02,0.69, +3.94,1.04, +3.68,1.74, +3.41,2.23, +3.46,2.98, +3.72,3.72, +4.04,4.73, +3.48,4.39, +0.88,2.38, +1.12,1.75, +-0.09,0.13, +-0.18,-0.17, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.81,0.84, +6.87,3.79, +7.29,2.84, +6.79,2.79, +6.3,1.46, +6.24,0.87, +5.95,0.58, +5.97,-0.6, +5.25,0.37, +4.21,3.39, +2.45,4.95, +2.13,5.87, +2.39,6.58, +1.6,7.07, +1.42,8.06, +2.05,8.67, +3.73,7.14, +5.59,6.96, +6.5,7.74, +8.49,7.12, +8.03,7.58, +6.65,7.93, +5.8,7.55, +5.02,7.36, +6.37,7.59, +7.21,8.17, +7.96,7.8, +8.38,7.03, +7.94,4.83, +7.53,2.92, +6.41,1.13, +4.94,0.95, +4.82,0.65, +5.93,-1.65, +7.2,-3.34, +9.24,-2.45, +10.15,-1.79, +10.58,-1.87, +10.65,-3.32, +8.98,-1.1, +6.7,-3.12, +4.81,-8.82, +4.9,-11.57, +5.88,-10.18, +4.99,-7.82, +4.89,-9.17, +4.36,-10.1, +3.02,-11.45, +0.57,-12.97, +-0.81,-12.1, +-1.02,-11.8, +0.58,-11.52, +2.48,-10.91, +4.33,-11.23, +5.4,-11.19, +6.78,-11.09, +7.27,-10.9, +8.35,-10.58, +7.95,-10.86, +8.19,-9.76, +6.66,-8.61, +5.13,-9.42, +1.64,-10.89, +-0.62,-10.9, +-2.22,-9.46, +-2.24,-8.25, +-1.23,-7.82, +0.43,-7.37, +2.85,-6.41, +2.63,-6.13, +1.63,-7.25, +1.16,-7.73, +0.86,-8.73, +0.22,-8.29, +3.18,-5.5, +3.64,-6.31, +3.92,-6.78, +4.05,-5.53, +3.52,-4.19, +2.88,-2.81, +1.63,-1.18, +1.01,0.52, +1.19,1.39, +1.78,0.7, +1.71,0.19, +0.56,0.27, +-1.13,1.95, +-1.62,3.59, +-0.59,3.28, +2.35,0.57, +4.23,-1.5, +5.46,-1.61, +5.56,-2.27, +5.66,-2.06, +5.61,-1.79, +4.44,1.16, +-1.64,3.83, +-2.28,3.32, +0.01,3.94, +1.92,3.49, +4.01,2.69, +5.08,1.93, +6.15,2.72, +6.97,2.54, +7.49,2.73, +7.1,2.58, +7,2.08, +7.89,-0.46, +7.18,-0.37, +6.52,1.43, +6.01,1.6, +4.96,2.02, +2.9,2.55, +0.92,2.7, +-0.3,4.06, +0.73,4.12, +1.45,3.99, +0.79,3.98, +-0.94,5.2, +-2.32,5.36, +-3.27,5.93, +-2.79,7.15, +-2.93,8.06, +-3.06,8.4, +-2.14,5.88, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-11.22, +0.05,-11.13, +0.5,-11.42, +1.39,-11.7, +1.71,-11.92, +1.91,-11.9, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.17,-11.85, +2.62,-11.75, +2.36,-11.81, +3.03,-11.65, +3.86,-11.41, +4.24,-11.64, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.65,-12.18, +5.55,-12.29, +6.97,-12.14, +7,-12.12, +7,-12.12, +7,-12.12, +7.08,-12.07, +8.52,-11.09, +9.02,-10.75, +9.61,-11.33, +10.93,-10.2, +11.28,-9.46, +10.93,-8.77, +11.41,-7.93, +11.28,-6.67, +10.46,-5.8, +4.4,-4.38, +-0.12,-0.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.14,-0.82, +4.77,-0.85, +5.68,0.31, +5.16,0.44, +4.55,-0.16, +3.96,-0.48, +2.98,-0.53, +3.11,-0.33, +3.77,-0.05, +4.17,0.26, +3.99,0.81, +3.8,1.55, +3.87,2.32, +3.9,3.12, +4.29,3.69, +2.15,3.54, +-0.48,0.1, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.8,2.04, +7.14,4.12, +7.07,2.2, +7.46,2.37, +7.27,2.46, +6.75,1.13, +5.97,0.3, +5.93,-1.05, +5.67,-0.35, +4.21,2.58, +2.4,5.13, +1.8,5.88, +1.48,6.82, +1.42,7.28, +1.44,8.17, +2,8.69, +4.01,6.49, +5.51,6.87, +6.21,7.07, +7.85,6.59, +7.36,6.67, +5.93,7.06, +4.91,6.7, +4.47,7.74, +5.43,8.26, +6.79,8.09, +7.12,8.49, +7.82,6.56, +7.35,5.66, +7.13,3.49, +6.29,1.38, +4.84,0.6, +4.47,0.41, +5.61,-0.34, +7.23,-1.07, +8.19,-1.23, +9.89,-0.29, +9.88,-0.43, +9.76,-1.15, +7.81,0, +4.89,-3.15, +3.32,-8.46, +3.74,-10.27, +5.49,-11.09, +6.88,-9.13, +7.57,-7.55, +6.64,-8.77, +4.52,-11.01, +2.5,-12.47, +1.78,-11.84, +1.59,-10.59, +2.79,-10.48, +4.45,-10.11, +5.88,-10.14, +6.76,-9.92, +8.05,-9.23, +9.52,-8.83, +9.8,-8.55, +8.83,-9.52, +8.28,-9.87, +7.51,-8.95, +5.53,-8.42, +2.56,-10.81, +0.65,-11.23, +-1.04,-8.85, +-0.81,-7.76, +1.01,-6.87, +4,-6.01, +5.18,-5.14, +4.02,-5.87, +2.91,-6.81, +2.53,-7.54, +1.4,-7.96, +1.21,-8.19, +3.5,-6.06, +3.5,-6.06, +3.82,-6.62, +3.55,-6.15, +3.64,-5.11, +3.25,-3.87, +1.94,-2.22, +1.06,-0.51, +0.83,0.41, +0.94,0.14, +1.23,-0.59, +0.55,-0.95, +-0.45,0.19, +-1.56,1.34, +-1.62,2.55, +0.31,-0.04, +3.7,-2.54, +4.47,-3.75, +5.29,-2.85, +5.7,-1.93, +5.64,-2.05, +5.26,-1.98, +3.42,2.37, +-1.66,3.51, +-1.13,2.6, +0.7,2.74, +3.61,2.25, +4.62,1.68, +5.53,2.01, +6.11,2.63, +6.44,3.45, +6.81,2.87, +6.62,2.23, +7.01,1.24, +6.89,1.22, +4.1,5.47, +4.01,5.22, +4.31,3.47, +1.91,2.94, +-0.15,3.67, +-0.7,4.71, +-0.23,5.45, +0.44,5.5, +-0.32,5.86, +-1.97,6.19, +-2.98,6.15, +-3.74,6.48, +-3.63,7.07, +-3.09,8.03, +-3.06,8.4, +-2.39,5.91, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.1,-11.08, +1.01,-11.01, +1.91,-11.14, +1.95,-11.04, +2.07,-11.72, +2.09,-11.84, +2.21,-11.85, +2.83,-11.7, +3.16,-11.6, +3.47,-11.52, +4.12,-11.33, +4.08,-11.34, +4.02,-11.36, +4.12,-11.33, +4.27,-11.73, +4.48,-12.23, +5.01,-12, +5.31,-11.87, +6.16,-11.46, +6.58,-11.49, +6.96,-12.05, +7,-12.12, +7,-12.12, +7.13,-12.04, +8.21,-11.3, +8.94,-10.77, +9,-10.72, +10.08,-10.37, +11.32,-9.5, +11.25,-8.94, +11.56,-7.41, +11.5,-6.61, +11.16,-5.6, +9.65,-4.19, +4.14,-3.52, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.57,-0.98, +5.12,-0.05, +5.05,-0.06, +4.81,-0.35, +4.17,-0.63, +3.81,-1.23, +2.82,-1.13, +2.98,-0.78, +2.94,-0.43, +0.58,0.07, +0.89,0.17, +3.16,1.05, +4.4,2.43, +4.01,2.91, +1.39,0.81, +-0.14,-0.14, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +7.26,4.19, +6.72,3.88, +5.32,3.07, +6.06,2.64, +6.47,3.19, +5.91,2.44, +5.9,0.92, +5.72,0.27, +4.28,1.85, +3.23,4.3, +2.22,5.59, +1.27,5.87, +1.22,6.89, +1.24,7.03, +1.38,7.85, +1.52,8.65, +3.55,6.14, +4.56,6.67, +5.67,6.76, +6.65,6.25, +6.14,5.67, +5.05,6.02, +4.47,6.56, +4.77,8.27, +6.15,8.22, +7.12,8.49, +7.88,7.73, +7.72,6.48, +7.94,5.15, +7.47,3.78, +6.72,1.86, +5.2,0.92, +5.08,0.52, +6.34,0.92, +6.7,0.95, +6.44,0, +6.64,-0.04, +7.47,0.19, +8.25,0, +6.73,-0.85, +2.99,-6.33, +2.09,-9.59, +3.92,-10.78, +6.18,-10.16, +7.38,-8.5, +8.49,-7.12, +6.84,-8.48, +4.97,-10.13, +4.26,-11.72, +4.24,-11.03, +4.43,-9.47, +5.64,-8.54, +6.98,-8.32, +7.9,-7.71, +8.74,-8.24, +9.36,-7.85, +10.38,-6.86, +10.14,-6.2, +8.47,-8.7, +7.56,-9.37, +6.75,-8.04, +5.14,-9.34, +3.25,-10.53, +1.02,-10.19, +-0,-8.33, +0.35,-6.97, +2.78,-5.61, +5.45,-4.12, +4.81,-5.11, +3.33,-5.68, +2,-6.01, +1.36,-6.59, +1.17,-7.38, +0.58,-8.04, +2.92,-6.36, +3.5,-6.06, +3.66,-6.34, +3.5,-6.06, +3.54,-5.9, +3.59,-4.82, +2.76,-3.29, +1.78,-1.85, +0.91,-0.5, +0.94,-0.08, +0.91,-1.01, +0.82,-1.88, +0.04,-1.67, +-1.35,-0.79, +-1.47,-0.33, +0.13,-1.5, +2.78,-4.04, +4.11,-4.86, +4.92,-3.44, +5.44,-2.54, +5.37,-2.7, +5.21,-2.52, +4.85,-1.11, +2.21,2.84, +-0.42,3.12, +0.14,2.83, +2.51,2.5, +4.77,2.08, +5.9,1.19, +5.55,-0.51, +5.88,0.29, +6.07,1.12, +6.64,2.63, +6.69,1.95, +6,4.07, +2.98,7.53, +3.26,5.69, +3.66,4.14, +1.15,3.77, +-0.65,4.35, +-0.94,5.34, +-1.14,6.48, +-1.4,6.97, +-2.37,7.01, +-2.77,6.93, +-3.84,6.65, +-3.98,6.9, +-3.98,6.9, +-4.2,7.72, +-3.94,7.99, +-3.19,5.53, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.24,-11.07, +1.69,-10.94, +1.92,-10.91, +1.92,-10.91, +1.95,-11.08, +2.1,-11.41, +3.61,-11.35, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.34,-11.24, +5.33,-11.39, +5.93,-11.58, +6.41,-11.33, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.92,-11.99, +7.02,-12.11, +7.68,-11.68, +8.53,-11.08, +8.98,-10.74, +9,-10.69, +9.87,-9.69, +10.57,-8.94, +10.93,-8.78, +11.19,-6.8, +10.81,-6.19, +10.96,-5.29, +9.26,-4.19, +2.23,-3.35, +-0.29,-0.72, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.1,-0.34, +0.03,-0.83, +4.42,-1.04, +4.97,-0.82, +4.99,-0.72, +4.81,-0.98, +4.11,-0.99, +3.81,-1.4, +3.58,-1.32, +3.68,-0.87, +2.43,-0.13, +0,0, +0,0, +4.47,2.12, +4.32,2.35, +0.1,-0.54, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.63,0.94, +7.56,4.36, +6.48,3.74, +5.52,3.43, +5.85,3.38, +6.25,3.41, +5.2,3.12, +4.72,3.73, +2.74,4.76, +2.37,5.34, +2.06,5.66, +2.06,5.66, +1.05,5.93, +1.4,5.95, +1.22,6.89, +1.38,7.85, +2.57,6.59, +4.31,5.49, +4,6.01, +4.62,6.31, +4.71,5.89, +4.44,5.63, +3.93,5.42, +3.74,6.48, +4.71,7.73, +6.69,7.98, +7.12,8.49, +8.49,7.12, +8.39,6.68, +8.61,5.19, +8.17,3.55, +7.12,2.45, +6.27,1.94, +6.66,1.31, +7.71,1.6, +8.37,0.98, +7.86,0, +6.79,-0.84, +6.77,-1.02, +7.81,-0.6, +5.5,-2.22, +1.06,-7.34, +2.97,-10.71, +5.69,-11.05, +6.96,-9.93, +6.75,-8.04, +5.89,-7.02, +5.27,-7.69, +4.8,-9.42, +4.09,-11.22, +4.46,-10.21, +6.42,-9.37, +8.21,-8.38, +8.59,-7.21, +9.23,-7.75, +9.23,-7.75, +9.56,-6.43, +9.72,-5.61, +8.63,-5.52, +6.28,-7.81, +5.75,-9.41, +5.73,-8.36, +4.92,-9.67, +3.57,-10.61, +1.8,-8.99, +1.27,-7.21, +2.19,-5.68, +4.35,-4.03, +5.64,-2.61, +3.83,-3.97, +2.47,-5.47, +0.7,-5.96, +-0.87,-5.76, +-1.63,-6.5, +-0.28,-7.31, +2.66,-5.84, +2.76,-6.4, +2.87,-6.36, +3.5,-6.06, +3.36,-5.82, +3.62,-5.58, +3.37,-4.71, +2.51,-3.47, +1.42,-1.49, +0.88,-1.05, +0.87,-1.73, +0.8,-2.57, +0.49,-3.21, +-0.13,-2.62, +0.03,-2.71, +0.84,-3.74, +2.35,-4.99, +4.14,-5.78, +4.44,-4.32, +4.82,-3.59, +5.13,-3.14, +5.27,-2.33, +4.75,-1.73, +4.94,-0.77, +3.74,2.28, +0.51,2.99, +0.57,2.82, +3.18,2.86, +2.56,2.65, +4.55,1.59, +4.21,1.14, +5.23,1.9, +6.62,2.59, +6.35,2.15, +3.71,4.51, +2.66,6.57, +2.87,4.98, +1.56,4.14, +0.2,4.03, +-0.81,4.62, +-1.24,5.63, +-1.72,6.42, +-2.43,6.67, +-3.51,7.12, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.47,7.74, +-4.47,7.74, +-3.19,5.53, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0,-10.76, +1.63,-10.85, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +2.52,-10.83, +3.8,-10.44, +3.95,-10.86, +4.08,-11.21, +4.12,-11.33, +4.12,-11.33, +4.15,-11.31, +4.82,-11.02, +5.54,-10.69, +6.26,-10.84, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.52,-11.3, +6.84,-11.64, +7.73,-11.62, +8.85,-10.84, +9,-10.72, +8.83,-10.52, +9.24,-9.7, +9.96,-8.52, +10.03,-8.13, +10.69,-6.79, +10.73,-6.19, +10.26,-4.95, +7.72,-4.42, +1.27,-3.11, +-0.05,-0.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.02,-0.51, +1.4,-1.5, +3.71,-1.81, +4.65,-1.89, +4.85,-1.75, +5.15,-1.82, +5,-1.78, +4.76,-1.51, +4.22,-1.25, +3.97,-0.91, +4.06,-0.32, +3.09,0, +1.46,0.22, +1,-0.21, +1.8,0.54, +0.6,-0.29, +-0.07,-0.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.68,2.12, +7.74,4.47, +7.06,3.9, +7.32,4.23, +7.19,3.62, +6.87,3.77, +5.22,3.21, +4.48,3.56, +2.79,4.48, +1.99,5.46, +2.06,5.66, +2.33,5.54, +1.96,5.69, +2.16,5.92, +1.95,6.69, +2.39,7.47, +3.89,6.51, +4.47,5.32, +3.93,5.76, +3.44,5.96, +3.38,5.55, +2.85,4.94, +2.91,5.27, +3.57,6.18, +5.56,7.32, +6.88,8.5, +7.91,7.93, +8.82,7.06, +9.25,6.45, +9.09,5.25, +8.66,3.88, +7.75,2.82, +8.03,1.2, +7.45,2.39, +8.15,1.86, +8.47,0.5, +8.76,-0.26, +7.74,-1.19, +6.81,-1.51, +7.43,-3.23, +2.89,-4.64, +0.4,-6.55, +3.24,-10.33, +6.44,-11.15, +8.28,-9.87, +7.16,-8.54, +5.6,-8.21, +4.34,-7.94, +4.1,-9.94, +3.84,-10.56, +4.33,-8.66, +6.02,-7.91, +7.85,-8.93, +8.97,-7.86, +9.23,-7.75, +8.8,-7.39, +9.49,-6.96, +8.86,-4.83, +6.94,-5.51, +4.45,-8.29, +3.92,-8.94, +4.68,-8.1, +4.06,-8.12, +4.03,-10.06, +4.09,-8.9, +3.42,-6.04, +2.96,-4.88, +4.95,-2.69, +6.31,-1.53, +4.79,-3.57, +3.68,-5.64, +1.59,-5.45, +-0.74,-5.05, +-1.83,-5.47, +-1.78,-6.07, +2.06,-5.66, +2.16,-5.92, +2.55,-6.5, +2.9,-6.23, +3.18,-5.5, +3.2,-5.53, +3.01,-5.22, +2.67,-4.62, +1.94,-3.19, +1.3,-2.18, +0.99,-2.62, +1.13,-3.09, +1.4,-3.84, +1.15,-3.88, +1.01,-4.44, +1.54,-4.69, +2.65,-6.1, +3.71,-6.08, +4.14,-4.94, +4.43,-4.06, +4.92,-3.44, +4.66,-2.69, +4.75,-1.73, +4.75,-1.73, +5,-0.44, +4.3,1.92, +0.76,2.98, +0.75,2.44, +-0.02,3, +2.11,2.99, +1.92,2.31, +3.74,2.15, +5,1.57, +2.71,2.44, +1.51,3.55, +1.65,4.17, +1.63,4.11, +0.18,4.05, +-0.71,4.02, +-1.56,4.54, +-1.98,5.43, +-2.36,5.98, +-3.34,6.13, +-3.71,6.42, +-3.93,6.81, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.47,7.74, +-4.47,7.74, +-3.35,5.81, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.17,-10.04, +1.5,-10.13, +1.85,-10.51, +1.91,-10.83, +1.98,-10.9, +2.82,-10.69, +3.79,-10.41, +3.79,-10.41, +3.87,-10.63, +4.03,-11, +4.31,-11.23, +4.96,-10.96, +6,-10.45, +6.03,-10.44, +6.22,-10.77, +6.51,-11.28, +6.51,-11.28, +6.62,-11.21, +6.73,-11.15, +7.06,-10.92, +8.08,-10.39, +8.92,-10.67, +8.64,-10.3, +7.26,-10.04, +1.12,-5.2, +-0.31,-3.04, +0.69,-3.68, +1.36,-4.05, +0.36,-2.92, +1.55,-3.32, +1.08,-3.02, +0.06,-1.16, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.09,-0.09, +-0.14,-0.22, +0.52,-2.28, +2.01,-2.61, +3.02,-2.82, +3.92,-3.1, +4.64,-2.46, +5.58,-2.4, +5.7,-2.15, +5.33,-1.82, +4.95,-0.88, +4.39,-0.61, +4.08,-0.05, +3.78,-0.04, +2.46,-0.5, +-0.12,-0.23, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +7.41,3.86, +8.66,3.15, +7.91,4.12, +7.46,4.5, +6.54,2.84, +5.82,3.2, +4.77,2.98, +3.59,2.65, +2.86,3.86, +2.4,4.41, +2.13,4.56, +2.14,5.03, +2.06,5.66, +2.1,5.76, +2.39,6.58, +2.68,7.36, +2.98,8.2, +3.92,7.14, +4.52,5.18, +3.56,5.13, +2.74,4.74, +2.42,4.54, +2.61,5.23, +3.97,5.88, +5.5,7.79, +7.12,8.49, +8.57,7.5, +9.46,7.19, +10.12,5.84, +9.87,5.45, +9.32,3.39, +8.45,2.89, +7.76,1.59, +6.66,3.85, +6.75,1.74, +6.69,0.32, +6.85,-1.5, +7.24,-2.41, +6.32,-3.3, +4.5,-4.92, +0.87,-5.09, +0.86,-7.29, +3.56,-8.57, +7.07,-8.92, +7.61,-9.07, +7.39,-8.81, +5.99,-9.28, +6.57,-8.17, +6.56,-9.56, +5.94,-10.25, +4.83,-8.36, +4.26,-7.38, +5.3,-7.49, +7.8,-8.22, +8.41,-7.71, +7.49,-6.28, +8.04,-5.78, +8.1,-5.09, +5.84,-6.66, +3.75,-8.67, +3.65,-8.74, +5.11,-7.47, +4.78,-7.89, +5.44,-9.43, +5.18,-8.96, +4.51,-7.38, +3.84,-4.88, +5.48,-2, +7.1,-1.69, +6.68,-3.83, +5.61,-5.42, +3.24,-5.96, +1.55,-6.17, +-0.03,-5.38, +-0.31,-5.46, +1.97,-5.4, +2.12,-5.81, +2.24,-6.5, +2.34,-6.42, +2.3,-5.55, +2.16,-5.26, +2.77,-4.8, +2.65,-4.59, +2.2,-3.82, +1.62,-2.81, +1.68,-2.9, +1.8,-3.65, +2.08,-3.78, +1.86,-4.69, +1.85,-5.09, +2.02,-5.55, +2.94,-5.92, +3.53,-5.59, +3.87,-4.62, +4.43,-4.06, +4.69,-3.77, +4.74,-2.61, +4.75,-1.73, +4.72,-0.89, +4.49,0.39, +5.67,0.57, +1.22,3.46, +-1.05,2.67, +-0.91,2.75, +0.34,2.88, +0.57,2.84, +1.69,2.53, +1.78,2.15, +0.43,2.15, +0.99,2.49, +1.56,2.69, +0.93,3.27, +-0.13,3.18, +-1,3.94, +-1.73,4.75, +-1.89,5.21, +-2.8,5.32, +-3.43,5.94, +-3.64,5.96, +-3.68,6.08, +-3.92,6.78, +-4.27,6.7, +-3.98,6.9, +-4.13,7.15, +-4.47,7.74, +-3.73,6.45, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.43,-9.9, +1.53,-9.79, +1.74,-9.85, +1.79,-10.15, +2.08,-10.68, +3.37,-10.54, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.93,-10.44, +5.59,-10.62, +5.99,-10.46, +6.03,-10.44, +6.03,-10.44, +6.3,-10.9, +6.51,-11.28, +6.96,-10.99, +7.39,-10.7, +8.28,-10.05, +8.27,-10.06, +8.37,-9.98, +8.41,-10.03, +6.67,-9.57, +1.9,-6.77, +-0.12,-0.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.08,-0.09, +-1.53,-1.57, +-0.99,-1.75, +-0.25,-3.21, +1.23,-3.93, +2.51,-4.09, +3.76,-3.65, +4.76,-3.46, +5.81,-3.24, +6.04,-2.59, +5.7,-1.95, +5.34,-1.16, +4.96,-0.3, +4.34,-0.11, +0.25,-0.6, +-0.12,-0.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +8.45,4.59, +9.15,3.51, +9.13,3.85, +8.52,4.92, +7.26,3.46, +6.2,2.55, +4.46,2.75, +3.58,2.6, +3.28,3.01, +3.53,3.59, +3.04,4.02, +2.53,4.38, +2.49,4.75, +2.38,5.51, +2.32,6.37, +2.73,7.49, +3.01,8.27, +3.57,6.29, +3.84,4.81, +3.95,4.71, +3.58,4.42, +3.36,3.96, +4.23,4.26, +4.84,5.53, +6.15,7.33, +7.43,8.18, +8.92,6.74, +10.44,6.03, +10.44,5.45, +10.26,4.05, +9.54,3.06, +8.77,2.16, +7.17,2.27, +5.23,2.6, +3.54,0.41, +3.88,-1.82, +4.84,-2.94, +5.88,-3.17, +5.18,-3.97, +2.9,-5.27, +1.56,-5.24, +2.37,-6.96, +4.07,-7.93, +5.7,-7.57, +7,-8.34, +7.51,-8.95, +7.87,-8.6, +8.86,-7.43, +8.66,-7.5, +7.87,-8.91, +7.08,-9.17, +5.07,-7.35, +4.78,-6.01, +6.04,-6.9, +7.18,-7.02, +7.72,-6.48, +8.36,-5.32, +7.76,-6.14, +6.29,-7.52, +5.12,-8.87, +5.32,-7.83, +7.07,-5.68, +7.34,-6.95, +7.36,-9.15, +6.86,-8.6, +6.06,-7.22, +5.17,-5.11, +6.24,-2.18, +8.19,-1.78, +8.47,-3.49, +7.37,-5.77, +5.41,-6.09, +4.04,-6.08, +2.07,-5.18, +0.98,-4.93, +2.1,-5.38, +1.91,-5.59, +1.1,-6.25, +1.82,-5.87, +2.06,-5.66, +2.06,-5.66, +1.95,-4.86, +2.41,-4.43, +2.49,-4.06, +2.11,-3.48, +2.43,-3.26, +2.55,-3.18, +2.59,-3.6, +2.53,-4.38, +2.23,-4.67, +2.06,-5.66, +2.54,-5.44, +3.26,-5.05, +3.98,-4.34, +4.62,-3.87, +4.62,-3.87, +4.9,-2.83, +4.61,-2.03, +2.98,1.98, +0.65,3.73, +2.95,3.9, +-0.74,3.14, +-1.81,2.27, +-0.99,2.72, +-0.36,2.87, +0.64,2.8, +1.79,2.74, +0.66,2.31, +0.47,1.85, +1.36,2.28, +1.81,2.28, +0.99,2.72, +-0.34,2.89, +-1.42,3.67, +-2.32,4.02, +-2.74,4.74, +-3.01,5.22, +-3.24,5.35, +-4.5,5.36, +-4.5,5.36, +-4.65,5.55, +-4.97,5.92, +-4.17,6.77, +-3.98,6.9, +-4.11,7.11, +-2.61,4.52, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.42,-9.8, +1.72,-9.76, +1.72,-9.76, +1.73,-9.81, +2.38,-10.26, +3.71,-10.32, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.21,-10.66, +5.99,-10.38, +6.03,-10.44, +6.03,-10.44, +6.2,-10.74, +6.64,-11.2, +8.08,-10.2, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.18,-9.89, +-0.1,-4.24, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.5,-0.57, +-2.32,-3.35, +-1.43,-4.39, +0.15,-4.54, +1.88,-4.62, +3.05,-4.87, +4.35,-4.21, +5.12,-3.61, +6.12,-3.25, +6.55,-2.32, +5.93,-1.28, +5.93,-0.5, +5.16,0.11, +0.44,-0.8, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.56,3.83, +7.82,4.93, +8.88,4.79, +10.06,5.56, +9.64,4.76, +8.98,2.51, +7.29,2.65, +5.87,1.82, +4.77,2, +4.21,2.12, +4.02,3.04, +3.72,3.4, +3.31,4.12, +3.19,4.93, +3.01,5.22, +2.85,6.37, +2.73,7.49, +2.93,8.06, +3.67,5.6, +4.34,4.73, +4.44,5.02, +5.24,3.91, +5.86,3.79, +5.85,4.58, +6.64,5.57, +6.9,7.89, +8.26,7.35, +8.95,6.65, +10.08,5.16, +10.27,4.03, +9.72,3.54, +8.82,2.03, +7.93,1.4, +6.35,0.7, +4.07,0.8, +3.43,-1.63, +3.46,-2.94, +3.43,-4.03, +4.54,-3.32, +2.98,-4.81, +2.25,-6.17, +2.95,-5.4, +2.52,-6.48, +2.06,-7.15, +2.2,-6.25, +3.75,-5.97, +6.36,-7.58, +9.66,-8.1, +10.45,-7.51, +11.28,-6.51, +9.7,-8.14, +8.37,-9.13, +6.93,-7.81, +6.91,-6.48, +7.14,-6.25, +8.09,-6.79, +7.71,-6.47, +8.71,-6.47, +8.34,-7.27, +7.29,-8.26, +7.02,-8.36, +7.6,-6.12, +8.81,-4.09, +9.47,-4.69, +9.29,-6.82, +7.19,-7.71, +6.49,-6.37, +7.13,-3.78, +8.54,-2.53, +9.57,-2.18, +9.04,-3.29, +7.46,-5.04, +6.2,-6.01, +5.05,-5.34, +4.09,-5.02, +1.93,-4.8, +2.54,-5.44, +1.92,-5.7, +1.24,-6.26, +1.33,-6.19, +1.67,-5.6, +1.81,-4.98, +2,-4.77, +2.41,-4.43, +2.53,-4.38, +2.42,-3.78, +2.81,-2.94, +3.16,-2.78, +3.33,-3.56, +2.67,-4.28, +2.41,-4.43, +2.17,-4.68, +2.46,-4.56, +2.89,-4.12, +3.45,-3.95, +4.19,-3.52, +4.62,-3.87, +4.98,-2.87, +4.38,-2.53, +4.24,0.33, +-0.25,3.67, +-2.6,2.96, +-2.5,2.04, +-1.92,2.17, +-1.37,2.57, +-0.83,2.78, +0.17,3.01, +2.51,3.14, +2.6,2.19, +1.69,2.05, +2.25,2.68, +2.79,2.59, +1.74,2.56, +-0.5,2.85, +-1.63,3.25, +-2.25,3.9, +-2.71,4.69, +-3.01,5.22, +-3.44,4.92, +-4.29,5.11, +-4.5,5.36, +-4.5,5.36, +-4.59,5.47, +-4.41,6.6, +-3.98,6.9, +-3.98,6.9, +-2.93,5.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.55,-9.79, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.8,-9.82, +3.24,-9.62, +3.72,-10.23, +3.79,-10.41, +3.79,-10.41, +4.22,-10.26, +5.79,-10.25, +6.03,-10.44, +6.03,-10.44, +6.65,-10.67, +8.01,-10.25, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.66,-9.71, +8.83,-9.55, +6.85,-9.04, +-0.4,-1.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.51,-2.38, +-2.24,-4.83, +-1.19,-5.19, +0.64,-5.63, +2.28,-5.63, +3.41,-5.66, +4.82,-4.9, +5.42,-4.2, +6.13,-3.38, +6.59,-2.34, +6.22,-1.32, +5.85,-0.46, +2.09,-1.12, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.83,4.95, +10.45,5.71, +10.65,5.57, +11.02,5.93, +8.76,4.13, +8.89,0.58, +7.32,1.5, +6.12,1.2, +5.01,1.68, +4.43,2.41, +4.56,3.01, +4.4,3.69, +4.06,4.43, +3.87,4.62, +3.83,4.84, +3.56,5.89, +3.22,6.19, +3.29,7.09, +4.56,4.26, +4.91,3.93, +5.59,3.98, +6.54,4.13, +7.62,4.4, +8.12,4.69, +8.01,6.11, +8.49,7.12, +8.39,7.04, +8.64,4.92, +8.43,3.3, +8.29,3.02, +8.07,2.44, +7.06,1.4, +6.93,0.74, +5.55,0.7, +4.53,0.7, +4.18,-0.96, +4.05,-3.45, +2.61,-5.44, +1.68,-5.62, +1.34,-5.18, +2.12,-6.41, +3.16,-5.39, +1.74,-5.88, +-1.24,-6.05, +-1.53,-5.12, +1.07,-4.31, +4.65,-4.62, +8.26,-6.93, +9.58,-7.26, +10.44,-6.03, +9.52,-6.7, +10.1,-8.48, +10.2,-8.23, +10.38,-6.39, +10.44,-6.03, +9.59,-7.02, +8.24,-6.12, +8.46,-5.7, +8.51,-6.46, +7.57,-7.57, +6.6,-7.23, +7.96,-4.83, +9.74,-2.37, +10.58,-2.6, +8.33,-4.77, +7.24,-6.07, +8.19,-5.33, +8.74,-3.36, +9.07,-2.43, +9.51,-1.68, +8.07,-3.26, +7.38,-4.26, +7.58,-5.27, +6.68,-4.98, +5.66,-5.17, +4.92,-6, +2.91,-6.03, +2,-6.01, +1.61,-6.23, +1.67,-6.05, +2.06,-5.66, +2.3,-5.55, +2.61,-4.52, +2.53,-4.38, +2.53,-4.38, +2.67,-3.69, +3.14,-2.74, +3.83,-3.02, +3.59,-3.53, +3.02,-3.83, +2.26,-3.38, +2.1,-3.63, +2.2,-3.82, +2.68,-3.68, +3.25,-3.87, +3.87,-3.25, +4.27,-3.43, +4.94,-2.85, +4.66,-1.93, +4.79,-1.59, +3.03,2.09, +-2.36,1.65, +-2.43,1.6, +-1.91,2.18, +-1.46,2.53, +-1.05,2.72, +-0.79,2.8, +1.76,2.6, +2.87,1.73, +2.29,2.43, +3.12,3.02, +3.23,2.71, +1.8,2.48, +-0.64,2.76, +-1.65,2.86, +-2.2,3.82, +-2.82,4.3, +-3.63,4.79, +-3.87,4.62, +-4.29,5.11, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.05,5.67, +-3.57,6.18, +-3.71,6.42, +-2.25,3.89, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.65,-9.78, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +2.61,-9.55, +3.45,-9.64, +3.64,-9.99, +3.79,-10.41, +3.79,-10.41, +5.32,-10.15, +6.03,-10.44, +6.03,-10.44, +7.21,-10.2, +8.34,-9.94, +8.37,-9.98, +9.1,-9.63, +9.32,-9.17, +9.51,-8.87, +5.56,-7.55, +0.12,-2.08, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.12,-1.61, +-1.59,-2.51, +-2.11,-4.22, +-2.16,-6.24, +-0.95,-6.43, +0.77,-6.46, +2.93,-6.57, +3.95,-6.34, +5.38,-5.23, +5.68,-4.15, +6.29,-3.04, +6.76,-1.74, +6.76,-0.89, +5.68,-0.53, +-0.15,-0.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +9.96,8.03, +10.05,7.01, +10.53,5.33, +10.06,3.71, +10.56,1.43, +9,0.94, +8.47,0.91, +7.14,-0.23, +5.66,1.18, +5.09,2.13, +5.22,3.01, +4.75,3.68, +4.72,3.96, +4.34,5.17, +4.5,5.36, +4.33,5.48, +3.93,6.2, +3.92,6.8, +4.47,4.87, +4.4,4.52, +5.4,5.05, +6.87,5.48, +8.06,5.79, +8.34,7, +8.9,7.47, +8.9,7.47, +7.79,5.99, +6.79,3.76, +6.33,2.52, +6.45,1.47, +6.25,1.1, +5.49,0.52, +5.38,0.22, +5.1,0.9, +5.02,0.44, +4.98,-1.09, +4.93,-3.19, +3.22,-4.98, +0.84,-6.56, +-0.72,-6.1, +1.86,-6.76, +1.41,-5.96, +-0.29,-5.26, +-0.69,-5.69, +0.12,-4.83, +0.27,-3.9, +1.87,-3.44, +4.48,-6.18, +7.96,-7.62, +8.46,-6.15, +8.77,-6.68, +9.84,-8.51, +12.2,-8.33, +12.65,-7.3, +12.54,-7.24, +11.86,-8.78, +9.74,-7.36, +9.72,-5.61, +9.12,-6.22, +8.49,-7.12, +8.23,-6.91, +8.61,-5.19, +8.39,-2.68, +7.8,-2.62, +6.65,-4.35, +6.38,-5.01, +8.15,-3.94, +8.63,-3.14, +9.33,-2.37, +8.52,-1.5, +7.7,-3.05, +7.14,-4.12, +6.83,-4.83, +5.48,-4.6, +4.71,-4.76, +3.84,-5.56, +2.6,-6.63, +2.5,-6.88, +2.39,-6.58, +2.55,-6.5, +2.7,-5.5, +2.87,-4.98, +3.34,-4.4, +3.67,-4.37, +3.34,-3.78, +3.2,-3.23, +3.4,-2.24, +4.21,-2.77, +3.87,-3.25, +2.9,-3.08, +2.59,-2.75, +2.49,-2.71, +2.49,-3.22, +2.7,-3.22, +3.46,-3.66, +3.81,-2.96, +4.66,-2.69, +5.27,-2.91, +5.55,-2.02, +5.05,-1.84, +4.59,-1.88, +-1.14,-2.97, +-2.47,1.66, +-1.31,2.55, +-0.56,2.18, +-0.25,2.11, +0.41,2.2, +2.08,1.92, +2.66,1.85, +2.64,2.83, +3.12,3.72, +2.5,2.98, +1.16,2.48, +-0.33,2.17, +-1.73,2.69, +-2.54,3.48, +-3.25,3.87, +-3.87,4.62, +-4.15,4.34, +-4.35,4.37, +-4.43,5.08, +-4.5,5.36, +-4.5,5.36, +-3.93,5.76, +-3.5,6.06, +-3.08,6.25, +-1.88,4.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.69,-9.77, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.89,-9.73, +3.33,-9.34, +3.45,-9.48, +3.77,-10.37, +3.92,-10.36, +4.89,-9.92, +5.87,-10.17, +6.34,-10.11, +7.51,-9.54, +7.87,-9.38, +8.36,-9.67, +9.43,-8.84, +9.79,-8.22, +9.24,-7.89, +1.18,-4.76, +-0.06,-0.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.21,-0.42, +0.02,-0.41, +-0.03,-0.15, +-0.18,-0.23, +-1.22,-2.08, +-2.12,-3.68, +-1.97,-5.09, +-1.83,-5.89, +-1.29,-6.82, +0.16,-6.97, +1.75,-6.76, +3.28,-6.66, +4.9,-6.25, +5.49,-5.03, +5.99,-3.62, +6.48,-2.62, +5.82,-2.24, +1.98,-1.56, +0.01,-1.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.22,1.86, +9.98,8.37, +10.29,6.87, +8.39,5.76, +7.19,4.42, +8.99,0.63, +9.41,0.46, +7.07,0.77, +6.41,-1.01, +6.46,0.58, +6.42,1.23, +5.56,2.15, +4.87,3.5, +5.17,4.34, +4.61,5.25, +4.5,5.36, +4.57,5.44, +4.24,6.21, +4.28,6.32, +4.58,4.21, +5.57,3.92, +6.52,5.47, +7.55,7.24, +8.61,7.23, +9.07,7.61, +9.23,7.75, +8.87,6.77, +7.8,4.57, +6,2.86, +5.18,1.16, +5.31,0.3, +5.29,0.42, +5.05,0, +5.05,0, +4.62,0.23, +4.91,-0.21, +5.1,-2.07, +4.77,-4.37, +3.37,-5.8, +0.52,-5.98, +-1.06,-6.02, +1.11,-7.46, +0.82,-6.34, +-0.1,-5.03, +0.15,-5.87, +0.73,-4.75, +-0.31,-4, +-0.68,-3.83, +1.13,-5.87, +4.12,-6.9, +4.52,-6.19, +6.16,-7.01, +8.9,-8.31, +12.41,-8.27, +12.96,-7.48, +13.08,-7.55, +12.43,-8.08, +11.47,-9.62, +11.44,-7.01, +10.7,-6.5, +9.36,-7.85, +9.14,-7.67, +8.97,-5.43, +8.47,-3.32, +8.48,-4.36, +6.82,-4.82, +5.21,-3.75, +6.77,-3.13, +8.1,-2.95, +8.82,-1.97, +7.99,-1.41, +7.18,-2.14, +7.99,-3.94, +7.6,-4.39, +5.93,-4.75, +3.6,-4.66, +2.78,-5.51, +2.52,-6.52, +2.72,-6.88, +3.15,-6.36, +3.22,-6.19, +3.4,-5.54, +3.77,-4.69, +3.98,-3.73, +3.87,-3.25, +4.1,-2.93, +3.98,-2.81, +4.3,-2.05, +4.74,-2.61, +4.09,-2.94, +3.5,-2.26, +3.52,-1.62, +2.89,-1.43, +3.18,-2.55, +2.84,-2.91, +3.21,-2.62, +3.68,-2.12, +4.38,-2.53, +5.04,-3.06, +5.35,-2.74, +4.83,-2.75, +4.04,-3.01, +0.44,-3.52, +-2.38,0.72, +-0.49,2.81, +0.88,2.48, +1.27,1.95, +1.95,1.93, +2.56,2.08, +3.13,2.62, +2.61,3.11, +2.3,3.52, +2.31,3.17, +1.45,2.52, +-0.17,1.89, +-1.74,2.53, +-2.46,2.93, +-3.25,3.87, +-4.34,4.15, +-4.62,3.87, +-4.62,3.87, +-4.62,3.87, +-4.21,4.74, +-4.06,4.84, +-3.58,5.8, +-3.5,6.06, +-2.64,6.46, +-1.91,3.71, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.72,-9.76, +1.73,-9.79, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.87,-9.73, +3.02,-9.46, +3.63,-9.97, +3.79,-10.41, +4.39,-10.15, +5.54,-9.6, +5.56,-9.63, +7.01,-8.96, +7.43,-8.85, +7.59,-8.78, +8.54,-7.7, +8.88,-7.46, +8.89,-7.46, +4.87,-6.29, +-0.11,-1.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.95,-0.99, +2.88,-1.07, +1.8,-1.04, +0.35,-1.4, +-0.31,-2.3, +-1.27,-3.76, +-1.92,-4.65, +-1.45,-5.26, +-0.71,-6.08, +-0.41,-6.91, +0.49,-6.96, +2.49,-6.5, +3.85,-6.18, +5.14,-5.98, +5.53,-4.3, +5.97,-3.47, +5.9,-2.39, +0.95,-1.49, +-0.19,-0.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +7.93,8.98, +9.65,8.09, +10.68,6.16, +10.75,6.21, +10.23,3.12, +8.25,0.49, +8.23,-0.79, +7.23,-0.17, +6.84,0.2, +6.9,1.08, +6.75,1.19, +7.3,1.29, +5.65,3.09, +5.47,4.59, +5.33,4.87, +5.14,5.02, +4.08,5.85, +4.1,5.64, +5.13,4.55, +5.73,3.48, +6.2,4.24, +7.17,6.02, +8.66,7.6, +8.9,7.47, +8.86,6.59, +9.22,5.35, +8.86,4.19, +7.54,2.53, +5.57,1, +5.03,0, +5.61,0, +5.59,0, +5.69,0.15, +5.4,0.26, +5.02,0.39, +5.03,0, +5.46,-2.15, +5.3,-4.28, +2.87,-6.36, +0.81,-6.58, +-0.7,-6.13, +0.97,-7.4, +0.48,-5.93, +1.02,-5.77, +1.03,-5.81, +1.19,-4.87, +0.44,-4.51, +-0.1,-5.43, +-0.95,-6.08, +0.14,-6.06, +2.39,-5.99, +5.78,-8.09, +10.63,-8.92, +12.03,-7.37, +10.86,-6.27, +10.31,-6.2, +11.59,-7.52, +12.23,-8.54, +12.56,-8.25, +11.89,-9.01, +10.26,-8.61, +10.71,-7.33, +10.16,-5.86, +9.81,-5.22, +10.44,-6.03, +8.01,-6.38, +6.74,-4.82, +7.49,-3.38, +8.73,-2.79, +7.45,-1.14, +6.33,-0.2, +7.26,-0.75, +8.77,-2.92, +7.85,-4.73, +6.71,-5.2, +5.02,-5.93, +3.01,-6.26, +2.46,-6.24, +3.31,-6.15, +3.55,-6.15, +3.5,-6.06, +3.67,-5.05, +3.98,-3.92, +4.04,-3.01, +4.47,-2.33, +4.64,-2.34, +4.93,-1.91, +5.46,-1.99, +5.59,-2.22, +5.29,-2.3, +4.73,-1.72, +4.81,-1.05, +4.83,-0.83, +3.86,-2.06, +3.06,-2.7, +3.16,-2.29, +3.35,-1.76, +3.92,-2.19, +4.12,-3.46, +3.83,-3.87, +3.5,-3.79, +3.56,-3.56, +2.66,-2.39, +1.63,1.4, +0.61,2.5, +1.35,2.56, +2.21,2.37, +3.02,2.53, +3.23,2.71, +3.06,2.69, +2.48,3.23, +2.04,3.54, +2.61,3.11, +2.36,2.65, +0.35,2.16, +-1.33,2.2, +-2.46,2.75, +-3.71,3.11, +-4.4,3.69, +-4.62,3.87, +-4.62,3.87, +-4.62,3.87, +-4.19,4.3, +-3.87,4.62, +-3.23,5.07, +-2.9,5.54, +-2.23,6.12, +-1.8,3.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.85,-10.49, +1.8,-10.23, +1.75,-9.93, +1.75,-9.91, +1.74,-9.88, +1.79,-10.12, +2.86,-10.32, +3.78,-10.39, +3.79,-10.41, +4.41,-10.09, +5.44,-9.43, +6.08,-9.06, +6.96,-8.49, +7.25,-8.19, +8.04,-7.39, +8.4,-7.05, +8.18,-6.71, +4.99,-6.37, +-0.39,-0.73, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.48,-0.75, +2.46,-1.27, +2.01,-1.36, +1.26,-1.57, +0.46,-2.7, +-0.29,-3.41, +-1.05,-4.39, +-1.31,-5.42, +-0.71,-5.95, +-0.06,-6.53, +0.62,-6.95, +2.02,-6.68, +3.12,-6.24, +4.47,-5.48, +5.25,-4.99, +5.79,-3.84, +5.73,-2.27, +1.55,-2.22, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +10.91,9.15, +10,7.61, +9.73,5.84, +9.89,6.56, +9.49,-0.22, +8.98,-1.13, +7.61,-1.02, +7.05,-0.08, +8.64,1.72, +8.49,1.5, +8.52,2.22, +8.04,1.59, +6.93,1.53, +6.11,5.12, +6.11,5.12, +4.94,5.31, +4.79,5.07, +4.83,5.49, +5.21,3.86, +5.86,4.54, +6.88,4.57, +7.61,6.39, +8.12,8.52, +8.86,7.07, +8.99,5.19, +8.86,3.22, +8.18,2.03, +7.44,0.52, +6.19,0, +5.85,-0.63, +6.01,0.13, +6.33,0.2, +6.49,0.89, +6.34,1.12, +6.01,0.15, +5.57,-0.23, +5.85,-1.81, +5.62,-4.74, +4.17,-6.61, +2.38,-6.97, +0.9,-7.46, +0.68,-7.44, +-0.57,-5.93, +2.49,-6.67, +2.69,-5.34, +2.53,-4.38, +1.54,-5.43, +0.78,-6.77, +-0.72,-6.57, +0.17,-6.33, +2.96,-6.21, +7.96,-7.74, +11.32,-7.94, +10.92,-6.3, +9.37,-6.42, +8.06,-6.39, +9.31,-7.39, +11.28,-8.19, +10.85,-9.1, +11.8,-9.9, +11.45,-9.22, +11.76,-6.79, +11.19,-6.46, +10.86,-6.27, +11.45,-7.47, +10.37,-7.6, +9.54,-5.51, +9.43,-5.23, +9.09,-2.92, +7.38,-0.37, +7.22,1.16, +8.42,0, +8.65,-2.68, +8.36,-4.58, +7.29,-5.4, +6.13,-6.33, +5.29,-6.58, +3.26,-5.99, +3.94,-5.75, +4.08,-5.85, +3.93,-5.76, +4.3,-4.95, +4.12,-3.13, +4.56,-2.13, +4.9,-1.62, +4.98,-1.5, +5.22,-1.24, +6.05,-1.7, +6.86,-1.76, +6.66,-1.53, +5.95,-0.87, +5.65,-0.11, +5.75,0.15, +5.07,-1.88, +5.02,-1.89, +4.94,-2.04, +4.4,-1.34, +3.55,-0.75, +3.34,-3.78, +3.38,-3.91, +2.39,-4.44, +2.84,-4.16, +2.75,-2.63, +2.44,0.37, +1.49,2.2, +1.6,2.43, +2.62,3.13, +2.75,3, +2.88,2.88, +2.7,3.06, +2.62,3.13, +3.07,3.66, +3.72,3.4, +3.4,2.51, +0.78,2.12, +-0.99,1.96, +-2.29,2.36, +-3.53,2.47, +-4.38,2.53, +-4.72,3.24, +-4.87,3.5, +-4.43,3.72, +-4.19,3.71, +-3.87,4.62, +-3.26,5.05, +-2.59,5.42, +-2.06,5.66, +-2.08,3.86, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.92,-10.91, +1.91,-10.85, +1.92,-10.86, +1.91,-10.85, +1.9,-10.78, +1.92,-10.91, +2.69,-10.7, +3.76,-10.34, +3.64,-9.92, +4.36,-9.58, +5.02,-8.66, +5.81,-8.07, +6.53,-7.69, +7.38,-7.36, +7.38,-6.19, +7.49,-6.28, +5.65,-5.5, +0.42,-1.89, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.13,-0.22, +0.54,-1.02, +1.48,-1.06, +2.36,-1.25, +1.38,-1.4, +0.76,-2.11, +0.21,-3, +-0.22,-4, +-0.79,-4.88, +-0.76,-5.72, +0.15,-6.02, +0.98,-6.47, +1.62,-6.77, +2.98,-6.31, +3.86,-5.82, +5.1,-4.78, +5.66,-3.91, +5.58,-2.92, +3.75,-1.92, +1.13,-1.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +9.56,8.02, +9.69,7.47, +10.25,6.29, +9.68,6.28, +7.31,3.15, +7.46,-2.3, +5.35,-1.93, +6.32,1, +7.33,3.99, +9.34,2.5, +9.8,2.34, +8.17,0.25, +6.97,-1.36, +6.01,3.61, +6.02,5.05, +5.35,4.68, +5.75,3.95, +5,4.65, +5.13,5.08, +6.54,5.23, +7.46,6.01, +7.74,8.28, +8.68,8.3, +8.83,6.6, +8.21,3.85, +8.3,2.05, +7.91,0.69, +7.61,-0.43, +6.8,-1.2, +5.96,-0.78, +6.3,0, +7.12,0.88, +7.37,1.3, +7.29,0.8, +6.88,0, +6.77,-0.81, +6.88,-2.18, +6.81,-4.33, +5.43,-6.43, +4.26,-7.3, +1.93,-7.56, +0.71,-8.68, +3.6,-7.06, +3.89,-6.76, +4.16,-5.16, +3.41,-4.94, +1.65,-5.56, +0.06,-7.27, +1.83,-7.98, +3.54,-6.53, +6.49,-4.68, +8.82,-4.49, +10.85,-6.64, +10.67,-7.01, +9.42,-7.9, +8.67,-7.28, +8.95,-7.51, +9.11,-8.1, +10.21,-8.9, +11.36,-9.53, +11.71,-9.83, +12.59,-7.27, +11.88,-6.86, +12.12,-7, +12.5,-7.21, +11.47,-7.09, +10.8,-6.56, +10.8,-6.23, +9.53,-3.23, +7.9,-0.05, +7.99,1.41, +9.01,0.58, +9.17,-2.05, +8.83,-4.43, +5.05,-6.62, +4.45,-6.35, +3.58,-6.2, +4.36,-6.03, +4.66,-5.01, +4.32,-5.15, +3.87,-6.1, +1.85,-4.85, +2.14,-3.16, +2.2,-2.31, +3.05,-1.1, +6.4,-1.19, +8.36,-1.97, +8.15,-3.54, +7.56,-3.8, +4.49,-2.21, +6.69,0.7, +7.59,1.19, +7.68,3.07, +8.31,2.35, +8.94,0, +7.27,-1.28, +6.08,-0.26, +3.59,-0.7, +4.04,-3.32, +2.95,-4.8, +1.42,-4.67, +1.66,-4.87, +1.92,-3.57, +2.38,0.08, +0.65,2.28, +1.22,2.62, +2.33,2.98, +2.62,3.13, +2.62,3.13, +2.62,3.13, +3.04,3.17, +3.8,3.32, +4.3,2.63, +3.61,1.81, +1.14,1.96, +-0.78,1.77, +-2.03,2.08, +-3.58,1.95, +-4.18,2.3, +-4.38,2.53, +-4.38,2.53, +-4.12,2.89, +-3.87,3.25, +-3.35,4, +-2.83,4.9, +-2.61,5.41, +-2.2,5.6, +-1.87,3.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.92,-10.91, +2.03,-10.89, +2.02,-10.89, +2.28,-10.83, +3.15,-10.6, +3.07,-10.62, +3.61,-10.42, +3.56,-9.72, +4.07,-9.02, +4.91,-8.59, +4.96,-8.16, +5.8,-7.02, +6.13,-6.77, +6.91,-5.85, +6.31,-5.22, +5.76,-5, +3.36,-3.85, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.11,-0.16, +0.27,-0.69, +1.81,-1.23, +3.02,-0.72, +2.71,-0.91, +1.94,-1.08, +1.39,-1.35, +0.89,-1.78, +0.48,-2.67, +0.05,-3.5, +-0.22,-4.27, +-0.35,-5.01, +0.51,-5.26, +1.55,-5.44, +2.18,-5.8, +2.97,-5.48, +3.95,-4.73, +4.93,-3.98, +5.42,-3.26, +5.49,-2.12, +4.54,-1.5, +0.52,-0.91, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.16,2.65, +10.21,8.04, +12.27,7.84, +13.62,6.23, +13.21,3.04, +10.66,3.18, +8.1,-1.7, +5.33,-2.08, +8.54,3.33, +9.25,4.16, +9.9,3.6, +7.2,1.9, +5.55,-2.53, +6.36,0.33, +6.26,4.34, +5.67,5.33, +5.91,4.98, +6.62,3.82, +5.51,3.75, +5.55,5.34, +6.39,6.06, +7.28,8.68, +7.75,9.23, +8.49,7.9, +8.56,5.58, +8.56,2.77, +8.17,0.84, +7.87,-1.19, +7.53,-1.33, +6.89,-1.22, +6.85,-0.3, +6.89,0, +7.71,1.15, +8.02,0.62, +8.42,0.37, +7.63,-0.2, +7.44,-0.49, +7.35,-2.31, +7.86,-4.54, +7.44,-6.01, +5.48,-7.83, +4.06,-7.77, +3.8,-7.62, +-1.32,-8.17, +3.97,-5.91, +5.29,-5.77, +3.67,-5.9, +2.92,-5.26, +1.55,-6.65, +3.72,-9.33, +5.69,-6.92, +8.56,-2.29, +8.57,-1.51, +8.48,-4.14, +8.24,-7.78, +9.37,-8.97, +9.39,-8.62, +8.44,-9.13, +8.16,-9.73, +9.84,-9.1, +12.02,-8.6, +13.06,-7.54, +12.82,-7.4, +11.88,-6.86, +12.81,-6.67, +13.39,-5.27, +11.79,-5.93, +10.22,-7.18, +10.49,-7.36, +9.47,-3.56, +8.53,0.03, +9.22,1.63, +9.75,1.13, +10.57,-1.58, +9.79,-4.72, +7.05,-6.53, +6.67,-6.61, +6.19,-6.4, +6.14,-4.54, +5.19,-5.06, +5.26,-5.52, +4.85,-5.71, +5.35,-4.79, +5.44,-3.2, +6.34,-1.41, +7.14,-1.45, +7.41,-0.17, +6.88,-2.5, +6.65,-1.35, +5.56,-1.63, +8.35,-2.57, +9.6,-0.38, +9.83,0.98, +9.54,2.73, +9.56,2.59, +8.21,-0.41, +7.14,-2.16, +7.09,-0.54, +4.25,-1.84, +3.82,-3.88, +2.11,-6.32, +0.36,-5.68, +0.2,-5.31, +0.34,-4.24, +-0.99,-1.3, +-1.48,2.42, +-0.27,2.81, +1.32,2.56, +2.25,2.68, +2.62,3.13, +3.13,2.62, +3.87,3.25, +4,3.07, +4.48,2.3, +3.18,1.28, +0.44,2.06, +-0.85,1.92, +-1.92,2.02, +-3.74,1.61, +-4.1,1.49, +-4.56,2.13, +-4.38,2.53, +-4.31,2.62, +-3.87,3.25, +-3.39,3.73, +-2.63,4.3, +-2.53,4.38, +-1.89,4.91, +-1.46,3.29, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.92,-10.91, +3.38,-10.7, +3.65,-10.71, +3.71,-10.54, +3.79,-10.41, +3.79,-10.41, +3.79,-10.31, +4.23,-9.14, +4.88,-8.6, +4.68,-8.1, +5.21,-6.88, +5.14,-6.09, +5.66,-5, +5.55,-4.39, +4.78,-4.23, +0.02,-1.29, +-0.2,-0.33, +0,0, +0,0, +0.18,-0.56, +-0.01,-0.66, +0.46,-0.88, +0.1,-0.56, +0.15,-0.61, +1.35,-1.49, +2.08,-1.41, +2.41,-1.06, +2.35,-0.98, +1.92,-0.88, +1.5,-1.23, +1.31,-1.43, +0.95,-1.69, +0.68,-2.4, +0.42,-2.94, +0.1,-3.64, +0.39,-3.69, +1.03,-3.99, +1.7,-4.22, +2.53,-4.16, +3.22,-4.03, +3.81,-3.42, +4.83,-2.72, +5.21,-2.14, +4.85,-1.11, +3.17,-0.71, +0.91,-0.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.45,1.99, +12.74,7.71, +12.59,7.29, +10.45,7.84, +13.64,0.95, +12.64,0.73, +9.63,-0.73, +7.98,-0.35, +6.27,4.29, +6.2,0.96, +7.66,2.06, +4.71,-0.31, +4.5,-0.69, +6.58,2.78, +6.04,5.58, +5.02,5.75, +5.26,5.97, +6.79,4.38, +6.58,3.84, +5.65,5, +6.7,6.91, +7.3,8.7, +7.84,9.34, +8.49,7.46, +8.45,5.1, +8.69,1.98, +7.94,-0.31, +7.85,-1.38, +7.85,-1.38, +7.78,-0.9, +7.68,-0.17, +7.97,0, +8.37,0.33, +8.74,0.67, +8.94,0, +8.94,0, +8.21,-0.95, +8.4,-1.66, +9.05,-3.5, +9.44,-5.77, +8.52,-7.1, +5.39,-8.33, +3.2,-9.14, +-1.26,-9.58, +3.58,-6.01, +3.28,-6.89, +3.42,-6.16, +1.49,-5.99, +0.79,-5.69, +2.2,-7.99, +5.94,-6.82, +7.78,-2.36, +7.51,-1.5, +5.97,-6.05, +5.17,-8.42, +7.95,-9.94, +9.08,-10.19, +8.86,-10.56, +9.09,-10.83, +11.16,-9.34, +12.87,-7.43, +12.48,-7.21, +11.65,-8.93, +12.67,-7.69, +13.41,-5.77, +13.3,-3.31, +11.41,-3.57, +9.6,-5.24, +9.92,-7.07, +10.7,-4.26, +10.37,-0.49, +10.65,1.63, +11.29,1.31, +11.79,-1.57, +11.44,-4.16, +8.59,-7.21, +9.04,-6.33, +8.93,-5.82, +8.59,-4.96, +8.2,-5.35, +8.14,-5.03, +7.95,-4.59, +8.02,-4.63, +7.59,-3.28, +7.88,-2.68, +7.94,-2.65, +8.23,0.23, +6.54,-1.26, +6.36,0.67, +2.26,2.88, +3.04,0.78, +6.18,0.12, +9.73,-1.03, +10.36,0.86, +9.9,0.93, +8.08,-1.54, +7.3,-2.2, +6.57,-0.17, +1.94,-3.08, +2.5,-5.4, +2.88,-6.92, +1.65,-7.06, +-1.23,-5.61, +-1.81,-4.51, +-3.36,-1.64, +-3.04,1.1, +-1.55,3.11, +1.02,3.37, +2.16,2.42, +2.52,3, +2.98,2.77, +3.35,3.01, +3.96,3.13, +4.3,2.31, +3.05,1.89, +-0.56,2.75, +-1.47,2.68, +-2.12,2.63, +-3.72,1.64, +-3.84,1.4, +-4.14,1.51, +-4.29,2.14, +-3.96,2.28, +-3.57,2.84, +-3,3.27, +-2.59,3.74, +-2.53,4.38, +-1.73,4.75, +-1.17,3.22, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.07,-11.19, +4.12,-11.33, +4.01,-11.02, +3.99,-10.96, +3.93,-10.47, +4.18,-10.24, +4.98,-8.85, +4.82,-8.34, +4.62,-7.68, +5.01,-6.28, +4.84,-5.46, +4.87,-4.38, +4.47,-2.84, +2.75,-2.72, +-0.06,-0.07, +0,0, +0,0, +0.03,-0.56, +2.31,-0.39, +2.32,-0.62, +2.12,-0.71, +1.26,-0.65, +1.1,-0.86, +0.93,-0.95, +1.01,-0.89, +1.48,-1.1, +1.55,-1.17, +1.57,-1.14, +1.47,-1.26, +1.25,-1.49, +1.11,-1.59, +0.9,-1.77, +0.74,-2.21, +0.52,-2.72, +0.68,-2.9, +1.49,-2.86, +2.19,-3.22, +2.86,-3.08, +3.38,-2.72, +4.27,-2.19, +2.23,-2.1, +0.77,-1.21, +1.24,-0.96, +-0.05,-0.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +10.85,9.1, +13.49,7.12, +13.26,6.56, +10.87,4, +12.79,-1.57, +12.71,0.81, +11.71,1.19, +6.26,3.34, +-0.78,-0.35, +4.37,2.65, +6.77,1.44, +7.55,1.13, +7.98,1.74, +6.11,4.9, +5.08,5.69, +5.33,6.35, +6.85,5.75, +7.45,4.9, +6.25,5.52, +6.48,7.72, +7.66,9.13, +7.75,9.23, +8.34,7, +8.51,4.14, +8.65,1.7, +8.23,-0.2, +8.21,-1.45, +7.86,-1.25, +8.53,0, +8.66,0, +8.51,0, +8.92,0.26, +9.29,0.92, +9.67,0, +9.75,0, +9.34,-0.35, +9.35,-1.65, +10.39,-3.13, +10.41,-5.74, +9.58,-7.26, +6.57,-8.82, +8.87,-4.13, +7.58,-6.36, +7.48,-4.37, +3.55,-8.45, +1.78,-7.58, +-0.04,-6.62, +-0.93,-6.92, +-0.93,-7.53, +6.06,-6.41, +7.23,-4.43, +5.85,-5.2, +1.08,-8.16, +0.81,-9.33, +5.11,-10.53, +7.63,-10.63, +9.31,-11.09, +10.98,-10.7, +12.49,-8.36, +12.17,-6.12, +10.56,-6.7, +10.54,-8.51, +12.2,-8.33, +12.82,-3.38, +11.65,0.45, +9.54,0.14, +8.48,-2.99, +9.39,-6.63, +11.19,-4.74, +11.25,-1.17, +11.86,0.6, +12.74,0.52, +13.29,-1.69, +13.15,-4.79, +9.45,-7.22, +10.51,-6.41, +9.7,-5.6, +9.12,-4.64, +8.59,-4.96, +8.59,-4.96, +8.91,-3.68, +9.79,-4.21, +10.37,-3.77, +11.07,-4.03, +10.75,-2.55, +9.91,-1.55, +9.49,-1.67, +8.51,-0.86, +7.04,1.57, +4.67,1.08, +5.44,0.1, +9.26,-1.83, +10.64,0.61, +9.28,0.95, +7.76,-1.57, +8.03,-1.94, +6.99,-0.14, +4.22,-2.63, +5.05,-4.34, +3.67,-7.05, +3.8,-6.94, +0.32,-5.56, +-2.13,-4.59, +-4.97,-1.74, +-4.8,1.93, +-1.37,3.47, +1.92,3.52, +3.29,2.36, +1.44,2.5, +1.42,2.68, +2.13,3.07, +2.56,3.68, +2.12,3.4, +-0,3.94, +-2.01,2.83, +-2.33,3.15, +-2.94,2.78, +-3.76,1.56, +-3.84,1.4, +-3.84,1.4, +-3.84,1.4, +-3.58,1.95, +-3.28,2.41, +-2.91,2.84, +-2.37,3.3, +-2.01,3.79, +-1.4,3.84, +-1.2,3.29, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.12,-11.33, +4.12,-11.33, +4.09,-11.11, +4.26,-10.73, +5.2,-9.78, +5.34,-9.51, +4.94,-8.56, +4.46,-7.62, +4.78,-6.37, +4.64,-5.53, +3.95,-4.25, +3.92,-3.25, +3.28,-2.03, +-0.09,-0.89, +0,0, +0,0, +0,0, +0.48,-0.14, +1.91,0.49, +1.48,0.36, +1.14,-0.03, +0.91,-0.33, +0.71,-0.65, +0.46,-0.86, +0.52,-0.82, +0.7,-0.82, +1.06,-1, +1.49,-1.25, +1.49,-1.25, +1.4,-1.35, +1.32,-1.42, +1.26,-1.47, +1.07,-1.61, +0.79,-1.63, +0.91,-1.79, +1.46,-1.65, +2.37,-1.74, +2.83,-1.71, +3.39,-1.62, +1.87,-1.33, +-0.02,-0.77, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.79,1.61, +13.95,8.41, +14.4,6.78, +13.66,4.97, +14.22,3.19, +14.02,-1.62, +11.66,2.09, +6.95,-0.45, +9.2,-0.02, +6.36,0.51, +7.86,1.01, +8.59,-0.26, +8.65,0.52, +8.53,0.55, +5.87,1.25, +5.15,3.07, +6.74,5.66, +7.28,6.1, +7.52,5.3, +6.81,6.47, +6.81,7.78, +7.36,8.77, +7.75,9.23, +8.32,6.23, +8.47,3.77, +8.51,2.03, +8.88,-0.69, +8.77,-0.58, +8.62,0, +8.94,0, +8.94,0, +9.43,0, +9.82,0, +10.06,0.25, +10.21,0, +10.15,0, +9.91,0, +9.76,-1.72, +10.47,-3.58, +9.45,-6.6, +8.43,-8.43, +6,-10.4, +8.23,-5.22, +7.84,-8.9, +9.16,-3.49, +8.29,-4.5, +5.24,-7.2, +1.16,-8.32, +0.09,-6.79, +-0.46,-7.07, +6.42,-6.4, +5.51,-6.87, +2.67,-7.95, +-1.04,-8.69, +-0.91,-9.42, +2.19,-10.71, +5.54,-12.19, +9.44,-11.67, +11.5,-9.68, +11.84,-6.84, +9.6,-5.91, +7.95,-7.35, +8.78,-9.34, +11.19,-8.33, +11.81,-1.95, +8.7,3.81, +6.39,5.18, +7.24,1.87, +9.46,-5.1, +10.66,-5.27, +10.93,-2.25, +12.9,0, +13.75,0, +14.06,-2.48, +13.86,-5.05, +10.99,-6.69, +11.92,-5.14, +11.33,-4.12, +10.06,-4.54, +8.35,-4.82, +7.46,-4.31, +7.65,-3.52, +8.49,-4.58, +9.52,-4.66, +9.73,-4.07, +9.01,-4.07, +8.3,-3.18, +8.9,-0.52, +9.42,-1.84, +8.38,-1.74, +7.07,-1.27, +7.2,-0.14, +9.49,-1.15, +9.85,0.74, +8.44,1.75, +8.09,-1.43, +8.09,-1.43, +6.55,-1.1, +3.77,-3.96, +7.17,-1.3, +4.54,-6.52, +4.01,-6.21, +3,-5.59, +0.38,-4.39, +-5.16,-2.03, +-5.64,0.32, +-0.6,2.58, +3.98,1.97, +3.06,1.84, +-0.32,2.38, +-0.56,2.51, +0.6,3.22, +0.53,3.94, +-1.29,3.75, +-2.6,3.1, +-3.06,2.69, +-2.81,2.94, +-3.18,2.51, +-3.76,1.56, +-3.47,1.26, +-3.23,1.17, +-3.05,1.11, +-3.15,1.49, +-2.78,1.76, +-2.49,2.32, +-2.18,2.97, +-1.77,3.38, +-1.4,3.84, +-0.5,2.27, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.25,-11.27, +5.01,-10.66, +5.01,-9.88, +5.48,-9.58, +5.28,-9.15, +4.87,-8.43, +4.32,-7.48, +3.71,-6.42, +3.4,-5.46, +3.37,-4.59, +2.24,-3.06, +1.68,-2.31, +0.65,-1.09, +-0.05,-0.08, +0,0, +0,0, +0,0, +0.13,-0.15, +0.97,-0.03, +1.53,0.74, +1,0.24, +0.97,-0.06, +0.41,-0.44, +0.14,-0.51, +0.59,-0.77, +0.63,-0.74, +0.76,-0.71, +1.08,-0.91, +1.4,-1.11, +1.59,-1.11, +1.59,-1.11, +1.29,-0.85, +1.14,-0.91, +0.85,-0.75, +1.11,-0.62, +1.78,-0.63, +2.27,-0.51, +2.86,-0.33, +1.85,-0.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.19,3, +12.89,7.53, +11.59,3.95, +11.93,4.74, +8.87,2.04, +11.08,-1.93, +9.95,2.66, +8.4,-0.62, +10.23,0.8, +10.01,3.06, +7,-1.77, +7.67,-2.52, +9.17,1.06, +7.7,-1.15, +6.65,-1.75, +6.73,2.45, +7.15,4.68, +7.59,6.37, +6.85,5.75, +6.67,6.47, +7.07,8.09, +7.98,9.51, +8.26,9.48, +8.77,6.68, +8.56,3.57, +8.78,1.55, +8.94,0, +8.94,0, +8.93,0.17, +8.99,1.32, +9.42,1.46, +9.85,0.77, +10.47,0.32, +10.62,0.24, +10.93,0, +10.69,0, +10.55,-0.19, +10.22,-1.56, +10.14,-3.98, +8.73,-7.38, +7.26,-9.58, +6.11,-10.58, +8.79,-2.43, +9.02,-5.32, +9.8,-4.12, +10.23,-4.65, +9.71,-5.29, +7.38,-7.44, +5.41,-6.69, +5.86,-5.17, +4.74,-6.87, +3.4,-8.21, +1.64,-9.28, +-0.19,-9.62, +0.27,-10.29, +3.11,-11.43, +6.27,-12.59, +8.82,-12.03, +11.43,-8.71, +9.48,-6.33, +4.9,-7.47, +4.17,-8.95, +6.04,-9.79, +9.83,-6.89, +9.32,0.35, +5.84,6.07, +3.25,7.7, +4.69,5.7, +8.06,-1.18, +11,-4.28, +12.22,-2.66, +13.92,-0.91, +14.48,0, +14.53,-2.56, +13.91,-5.04, +12.17,-6.12, +12.04,-4.38, +10.87,-3.96, +9.16,-3.34, +6.81,-3.93, +3.75,-4.44, +5.07,-5.44, +7.04,-5.85, +8.74,-5.75, +8.21,-5.49, +8.44,-5.93, +7.87,-3.48, +9.8,-0.26, +9.38,-3.65, +8.48,-3.67, +8.2,-2.98, +7.89,0.22, +9.53,-0.22, +9.77,0.22, +7.25,-0.94, +7.51,-2.13, +7.48,-0.71, +6.62,-2.02, +4.53,-4.43, +7.05,0.4, +5.33,-6.35, +5.05,-6.02, +4.41,-5.18, +0.45,-3.87, +-3.73,-4.15, +-5.97,-1.05, +0.65,0.12, +2.99,0.52, +1.86,1.5, +-2.45,1.35, +-2.29,1.97, +-0.97,3.51, +-1.77,3.59, +-3.09,2.86, +-3.55,2.86, +-3.24,2.46, +-3.13,2.62, +-3.31,2.36, +-3.17,1.26, +-2.74,1, +-2.69,1.11, +-2.63,1.23, +-2.62,1.26, +-2.53,1.46, +-2.18,1.91, +-1.72,2.34, +-1.3,2.6, +-0.84,2.93, +-0.51,2.87, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.31,-9.86, +5.74,-10.08, +5.49,-9.51, +5.12,-8.87, +4.98,-8.62, +4.35,-7.53, +3.9,-6.75, +3.35,-5.81, +1.95,-4.14, +0.17,-2, +-0.03,-0.45, +-0.14,-0.27, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.11,-0.13, +0.96,0.36, +1.67,0.99, +1.36,0.74, +0.94,0.19, +0.46,-0.09, +-0.01,-0.18, +0.53,-0.63, +0.72,-0.65, +0.74,-0.63, +0.75,-0.61, +0.94,-0.57, +1.4,-0.72, +1.56,-0.62, +0.98,-0.21, +0.97,-0, +1.03,0.22, +1.51,0.53, +1.79,0.69, +2.01,0.66, +2.73,1, +0.68,-0.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.38,1.16, +10.1,9.39, +8.8,5.06, +11.63,4.23, +11.07,3.79, +11.43,0.33, +12.53,2.94, +12.01,2.12, +11.34,2.55, +8.77,2.09, +5.72,-0.11, +6.43,0.49, +5.25,0.56, +2.48,1.9, +7.72,-1.52, +9.14,1.96, +8.37,4.27, +7.86,5.78, +7.12,5.55, +7.54,6.7, +7.51,8.95, +8.06,9.6, +8.8,8.86, +9.31,6.7, +9.24,4.59, +8.69,1.98, +8.9,0.47, +8.94,0, +8.87,0.78, +9.02,1.28, +9.49,1.67, +9.95,1.5, +10.93,1.71, +11.01,0.82, +11.08,0, +11.08,0, +10.87,-0.52, +10.45,-2.37, +9.56,-4.31, +8.13,-7.18, +7.45,-9.29, +7.12,-8.45, +9.93,-1.75, +9.6,-3.61, +10.59,-4.11, +10.69,-3.89, +10.22,-4.77, +9.31,-5.57, +6.48,-6.04, +3.76,-5.29, +3.08,-6.63, +1.89,-8.29, +1.85,-9.51, +1.96,-10.84, +3.39,-11.19, +5.47,-10.65, +6.79,-11.76, +9.31,-11.09, +8.11,-7.23, +4.58,-5.93, +1.18,-7.98, +0.02,-9.66, +3.02,-9.48, +8.48,-3.87, +5.88,4.47, +1.96,7.15, +1.11,7.46, +1.92,7.3, +6.92,1.41, +10.44,-5.1, +12.43,-3.28, +14.34,-1.68, +14.87,-2.33, +14.6,-2.58, +13.52,-4.03, +12.75,-5.42, +11.9,-4.33, +10.52,-3.83, +8.07,-3.26, +5.6,-3.87, +3.17,-4.94, +3.61,-5.98, +6.25,-6.18, +7.84,-6.02, +7.59,-6.37, +8.74,-5.42, +9.15,-2.92, +9.64,-1.9, +6.65,-5.26, +8.85,-2.63, +10.43,-2.71, +9.15,0.17, +9.51,0.94, +9.14,-0.56, +7.72,-1.71, +9.02,-0.54, +9.27,0, +6.06,-1.91, +5.51,-3.59, +6.74,1.78, +5.1,-5.61, +5.15,-5.49, +4.26,-4.54, +1.34,-3.75, +-4.07,-3.57, +-6.35,-2.22, +-4.5,1.56, +-2.26,2.52, +-2.13,0.95, +-4.27,0.92, +-3.92,2.27, +-2.79,3.06, +-3.42,3.02, +-4.15,2.57, +-3.63,2.09, +-3.39,1.96, +-2.6,1.69, +-2.64,1.8, +-2.55,1.4, +-2.29,1.76, +-1.67,1.62, +-1.16,1.52, +-1.37,1.46, +-1.93,1.18, +-2.33,1.74, +-1.79,2.29, +-1.15,2.67, +-0.65,2.83, +-0.23,2.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.54,-9.6, +5.54,-9.6, +5.32,-9.21, +4.87,-8.44, +4.63,-7.85, +4.36,-6.89, +3.19,-5.52, +2.23,-4.39, +0.08,-1.97, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.11,0.19, +0.51,0.59, +1.48,1.25, +1.63,1.05, +1.56,1.02, +1.07,0.55, +0.51,0.07, +0,0, +0.17,-0.36, +0.79,-0.56, +0.77,-0.6, +0.81,-0.54, +0.87,-0.44, +0.92,-0.32, +1.3,-0.21, +1.18,0.18, +1.3,0.7, +1.28,1.3, +1.21,1.51, +1.51,1.81, +1.77,1.79, +1.71,1.5, +-0.17,-0.28, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.14,0.78, +14.32,8.93, +12.31,7.83, +13.11,4.43, +14.47,5.27, +14.12,3.5, +12.42,2.19, +12.17,3.88, +9.62,1.85, +8.07,0.42, +7.12,2.63, +5.5,-1.6, +1.6,2.33, +4.56,-3.67, +8.86,-0.43, +10.36,2.31, +9.25,4.2, +8.1,4.68, +7.97,6.13, +8.36,7.01, +8.61,8.37, +9.58,8.77, +9.96,8.03, +10.44,6.03, +10.1,3.68, +9.39,1.85, +8.81,1.55, +8.84,1.11, +8.81,1.55, +8.81,1.55, +9.17,1.62, +9.89,1.74, +10.91,1.92, +11.22,1.2, +11.93,0, +11.08,0, +11,-0.96, +9.63,-2.79, +8.26,-5.43, +7.63,-7.98, +7.43,-8.86, +6.23,-10.1, +9.91,-1.75, +11.72,-2.66, +10.71,-3.62, +10.84,-3.45, +10.89,-3.62, +11.04,-4.36, +8.83,-5.67, +4.1,-6.04, +1.22,-6.89, +0.78,-8.31, +3.37,-8.1, +4.76,-9.78, +7.87,-7.77, +8.59,-7.92, +7.68,-9.62, +7.91,-8.43, +4.68,-7.54, +0.27,-7.3, +-0.7,-7.94, +-1.54,-8.72, +2.35,-8.3, +2.29,0.48, +0.34,6.33, +-1.33,4.89, +0,6.75, +1.31,6.43, +6.19,-1.08, +9.12,-7.09, +12.22,-4.33, +13.65,-5.91, +14.26,-5.21, +14.13,-5.2, +13.55,-5.61, +13.3,-4.84, +12.39,-4.51, +10.06,-3.66, +6.6,-3.33, +5.21,-4.6, +5.12,-6.11, +4.95,-6.62, +6.2,-6.01, +6.93,-5.82, +7.88,-5.97, +8.46,-4.66, +6.96,-1.2, +5.07,-1.06, +8.72,-3.5, +10.83,-2.13, +11.47,-2.35, +10.11,-0.26, +10.69,0, +9.06,-0.49, +8.76,-0.91, +9.2,-0.22, +9.16,-1.84, +6.28,-3.67, +6.73,-2.57, +7.63,-0.3, +5.82,-5.4, +4.29,-5.11, +2.84,-3.93, +1.56,-2.51, +-3.87,-2.69, +-5.35,-2.69, +-6.24,0.55, +-5.45,3.12, +-4.94,3.05, +-5.09,2.53, +-4.35,2.8, +-4.32,2.57, +-4.3,2.6, +-3.64,2.1, +-3.54,2.04, +-2.78,1.6, +-2.53,1.46, +-2.28,1.8, +-1.91,1.75, +-1.28,2.01, +-0.67,2.83, +-0.22,2.9, +-0.1,2.52, +-1.17,1.46, +-1.55,1.35, +-1.67,2.21, +-1.19,2.65, +-0.51,2.87, +0,2.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.39,-9.34, +5.26,-9.11, +4.95,-8.57, +4.19,-7.26, +4.14,-6.67, +3.7,-5.46, +2.08,-4.19, +0.6,-3.13, +-0.16,-0.23, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.01,-0.08, +0.9,0.6, +1.57,1.12, +1.72,0.88, +1.92,0.71, +1.82,0.74, +1.25,0.45, +0.81,0.14, +0.53,-0.07, +0.84,-0.2, +0.91,-0.32, +0.86,-0.44, +0.91,-0.34, +0.94,-0.23, +0.98,-0.13, +1.68,0.1, +1.71,0.74, +1.45,1.27, +1.24,1.6, +1.23,2.13, +1.4,2.43, +0.91,2.16, +-0.26,0.02, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.7,1.74, +15.99,8.63, +13.38,6.14, +13.02,5.06, +13.91,5.06, +11.2,3.35, +10.34,1.82, +10.38,3.78, +10.16,3.15, +10.15,0.96, +6.58,3.21, +1,-4.22, +2.73,-1.87, +7.2,-0.06, +9.56,0.38, +11.93,1.35, +10.25,2.36, +8.94,4.73, +8.65,6.63, +9.34,7.84, +10.05,8.43, +10.39,8.26, +11.06,6.82, +11.4,6, +10.87,3.96, +10.34,1.82, +9.89,1.74, +9.28,1.64, +9.13,1.61, +9.13,1.61, +9.4,1.66, +9.6,1.69, +10.91,1.92, +11.15,1.97, +11.77,0, +11.18,0, +10.28,-0.86, +9,-4.06, +8.24,-5.67, +7.74,-7.17, +7.21,-8.59, +6.59,-8.86, +9.95,-1.76, +10.09,-1.78, +10.85,-1.91, +11.66,-2.34, +11.8,-2.35, +11.57,-3.97, +9.88,-4.9, +5.97,-6.43, +3.92,-6.19, +6.65,-5.95, +7.11,-6.9, +8.19,-6.87, +9.26,-6.86, +9.63,-6.94, +9.02,-7.6, +8.48,-6.55, +5.85,-7.99, +2.99,-9.35, +2.7,-9, +1.9,-9.14, +1.91,-9.21, +2.16,-8.69, +-4.04,-5.29, +-6.06,0.2, +-4.86,4.14, +4.97,1.68, +5.65,-8.91, +6.87,-10.44, +11.46,-8.13, +12.96,-7.48, +13.56,-7.83, +13.57,-7.84, +14.02,-5.59, +13.15,-4.79, +11.11,-4.27, +7.69,-3.81, +6.08,-3.75, +4.94,-5.33, +5.3,-6.66, +6.1,-7.28, +6.44,-6.16, +7.47,-6.27, +8.37,-5.06, +8.79,-3.03, +7,-1.44, +5.68,-1.93, +7.84,-1.94, +8.15,-2.14, +9.45,-2.74, +11.12,-0.8, +11.58,-1.69, +10.18,-1.41, +9.65,-1.15, +8.86,-0.89, +8.97,-0.83, +9.15,-2.14, +9.84,-1.21, +9.17,-0.78, +7.29,-5.33, +5.14,-4.23, +-0.36,-2.64, +-2.48,-1.07, +-4.69,-1.59, +-5.35,-2.74, +-5.96,-0, +-5.53,2.29, +-5.13,3.14, +-4.14,2.39, +-3.93,3.16, +-4.5,2.26, +-3.83,2.3, +-3.15,1.91, +-2.69,1.56, +-2.53,1.46, +-2.36,1.69, +-1.53,1.65, +-0.89,1.71, +-0.61,2.68, +-0.38,2.88, +0,3.35, +0.59,2.85, +-0.04,1.99, +-0.87,1.64, +-1.16,1.55, +-0.99,2.61, +-0.36,2.88, +0,2.43, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.81,-8.33, +5.24,-7.78, +4.84,-7.29, +4.22,-6.35, +3.38,-5.59, +2.67,-4.61, +1.49,-3.58, +0.05,-2.65, +-0.86,-1.25, +-0.21,-0.15, +0,0, +0,0, +0,0, +-0.09,-0.08, +-0.33,-0.2, +0.15,-0.12, +1.13,0.2, +1.84,0.39, +2.02,0.29, +2.7,0.29, +2.06,0.2, +1.8,0.29, +0.97,0.05, +0.96,0.14, +0.97,0.01, +0.97,-0.08, +0.96,-0.11, +0.96,-0.12, +0.97,-0.08, +1.29,0.08, +1.86,0.28, +1.69,0.96, +1.37,1.37, +1.25,1.76, +1.47,2.55, +0.74,1.55, +-0.2,-0.17, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.16,1.83, +13.34,7.7, +12.24,7.07, +11,6.35, +10.54,5.03, +11.02,4.38, +10.91,3.52, +11.41,4.15, +12.39,4.51, +8.8,2.14, +0.93,0.62, +2.51,-2.89, +6.22,0.17, +9.61,1.54, +10.93,1.34, +11.37,2, +10.82,2.67, +9.6,5.54, +9.37,7.55, +9.89,8.3, +10.41,7.75, +11.22,6.81, +12.07,6.19, +12.24,4.45, +11.67,4.25, +11.18,2.8, +10.91,1.92, +9.91,1.75, +9.76,1.72, +9.76,1.72, +9.76,1.72, +10.15,1.79, +10.91,1.92, +11.02,0.72, +11.01,0.86, +10.89,0, +9.84,-0.86, +9.06,-2.69, +8.55,-4.94, +7.99,-6.74, +7.43,-8.18, +8,-6.23, +10.09,-1.34, +8.96,-2, +8.49,-1.5, +9.87,-1.96, +10.36,-2.31, +10.91,-5.02, +10.55,-4.86, +7.6,-5.6, +6.59,-4.85, +7.15,-3.91, +7.17,-4.86, +7.89,-6.62, +8.69,-5.82, +9.09,-5.25, +8.6,-5.33, +8.68,-6.09, +6.37,-8.26, +3.61,-9.21, +4.24,-9.07, +3.45,-9.47, +1.78,-9.9, +-3.49,-7.81, +-6.98,-3.58, +-7.33,-0.92, +-6.62,1.12, +-0.36,-0.24, +5.54,-9.16, +7.39,-10.79, +11.97,-8.34, +13.24,-7.65, +13.34,-7.7, +12.96,-7.48, +13.33,-5.54, +11.96,-4.35, +8.78,-5.07, +6.68,-4.06, +5.15,-4.67, +5.12,-6.11, +5.9,-7.04, +6.43,-7.4, +7,-5.54, +7.84,-4.27, +8.87,-3.69, +8.83,-1.29, +7.73,-3.1, +8.92,-2.72, +7.9,-2.3, +4.34,-0.9, +7.84,0.29, +9.69,-0.98, +10.19,-1.31, +9.83,-1.73, +9.65,-2.26, +3.63,-2.57, +6.06,1.59, +8.55,-1.28, +7.63,-1.13, +7.61,-1.34, +7.56,-4.1, +3.69,-4.64, +-3.19,-2, +-5.02,0.17, +-5.85,0.13, +-5.54,-2.33, +-5.73,0.52, +-4.67,2.23, +-4.52,2.61, +-3.82,2.2, +-3.85,2.86, +-4.44,2.39, +-3.18,2.55, +-2.59,1.33, +-2.56,1.39, +-2.25,1.81, +-1.92,2.16, +-0.89,1.71, +-0.52,2.49, +-0.51,2.87, +-0.48,3.75, +0,4.08, +0.65,3.41, +0.97,2.48, +-0.7,1.69, +-1.16,1.55, +-0.76,2.08, +-0.25,2.89, +0,1.94, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.25,-7.14, +5.53,-6.71, +4.85,-5.77, +4.32,-5.15, +3.32,-4.35, +1.66,-3.24, +0.44,-2.45, +-0.93,-1.91, +-2.63,-1.06, +-1.1,-0.48, +0,0, +0,0, +0,0, +-0.21,-0.17, +-1.17,-0.8, +0.03,-0.73, +0.98,-0.4, +1.83,-0.45, +2.52,-0.32, +2.9,-0.2, +2.67,-0.04, +2.01,0.05, +1.53,0.02, +0.99,0.05, +0.97,0.04, +0.97,0.01, +0.97,0.07, +0.97,0.06, +0.96,0.11, +1.51,0.24, +1.9,0.42, +1.73,0.87, +1.41,1.37, +1.36,2.17, +1.48,2.57, +1.26,2.85, +-0.21,-0.03, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +13,8.88, +13.62,7.86, +13.08,7.55, +11.64,6.72, +11.45,6.37, +12.59,5.67, +13.33,3.93, +11.44,3.92, +7.04,1.41, +2.38,-4.47, +4.27,-0.85, +7.23,2.79, +10.07,1.8, +10.57,2.88, +11.24,2.84, +11.22,2.28, +9.97,6.29, +9.55,8.01, +10.6,7.24, +10.9,6.3, +11.07,5.33, +12.09,4.4, +12.43,3.72, +12.61,3.08, +12.1,2.49, +11.87,2.09, +11.03,1.95, +10.09,1.78, +10.6,2.09, +10.62,1.87, +10.91,1.92, +10.91,1.92, +10.94,1.65, +11.23,1.98, +11,0.66, +10.56,-0.25, +9.69,-2, +9.78,-3.53, +9.34,-5.39, +8.81,-6.94, +8.21,-6.99, +9.71,-1.93, +8.79,-3.2, +8.31,-2.17, +9.18,-2.84, +9.63,-3.51, +10.45,-4.94, +10.52,-4.73, +8.26,-4.4, +7.64,-4.61, +7.84,-4.27, +8.11,-2.95, +8.46,-2.37, +8.93,-3.8, +7.68,-5.69, +7.08,-4.41, +7.48,-5.13, +5.11,-8.22, +4.5,-8.94, +4.9,-8.98, +3.06,-9.92, +0.02,-9.74, +-3.4,-7.52, +-6.49,-5.14, +-7.46,-4.77, +-6.72,-3.61, +-4.84,-4.3, +5.41,-10, +7.88,-11.67, +11.07,-8.5, +11.46,-8.51, +12.71,-7.84, +12.99,-7.1, +12.83,-4.96, +11.33,-4.12, +8.5,-4.91, +6.13,-4.34, +4.37,-5.21, +4.64,-6.11, +5.51,-6.57, +7.16,-6.35, +7.31,-5.28, +7.37,-3.17, +9.22,-0.52, +10.51,-0.8, +10.92,-3.21, +9.59,-4.53, +1.06,-2.85, +0.82,2.85, +4.84,3.64, +9.91,-1.13, +10.95,-0.85, +3.81,-3.66, +3.73,-2.9, +-2.57,-1.95, +2.78,2.62, +3.42,-2.34, +2.14,0.77, +8.72,0.09, +6.75,-3.11, +0.92,-4.09, +-4.81,-2.41, +-5.86,-0.21, +-6.12,0.38, +-5.93,-1.05, +-5.7,0.85, +-4.64,1.96, +-4.26,2.46, +-3.66,2.11, +-3.92,2.7, +-4.44,2.56, +-3.5,2.97, +-3.4,0.63, +-2.21,0.82, +-1.39,2.55, +-1.15,2.67, +-0.63,2.84, +-0.51,2.87, +-0.57,3.25, +-0.18,4.19, +-0.63,4.72, +0.28,3.62, +0.4,2.54, +-1.49,1.12, +-1.24,1.42, +-0.52,1.86, +-0.14,2.74, +0.2,2.02, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.16,-6.15, +5.04,-6.01, +4.25,-5.06, +3.37,-4.02, +2.35,-3.12, +0.84,-2.21, +-0.7,-1.78, +-2.23,-1.21, +-3.7,-0.54, +-3.75,-0.69, +-1.65,-0.53, +0,0, +-0.65,-0.42, +-1.98,-1.1, +-1.13,-1.47, +0.33,-1.38, +1.28,-1.08, +1.81,-0.96, +2.71,-0.77, +3.05,-0.58, +2.97,-0.41, +2.3,-0.16, +1.92,0.11, +1.23,0.2, +0.96,0.16, +0.96,0.15, +0.95,0.18, +0.95,0.2, +0.97,0.11, +1,-0.15, +0.86,-0.23, +0.95,0.22, +1.39,1.32, +1.46,2.33, +1.38,2.57, +0.05,1.5, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.16,2.65, +14.84,10.32, +16.14,9.32, +14.99,7.57, +13,7.11, +12.68,7.32, +13.46,7.44, +10.05,3.76, +9.95,1.53, +8.72,1.58, +6.06,-0.6, +5.51,-1.03, +6.74,3.93, +8.55,3.41, +10.33,4.95, +9.93,5.22, +10.77,4.19, +11.43,4.89, +9.63,7.75, +10.54,6.09, +10.44,4.88, +10.84,3.03, +11.96,2.58, +12.83,2.26, +12.83,2.26, +12.83,2.26, +12.35,2.18, +11.63,2.05, +11.12,1.96, +10.72,3.9, +10.91,1.92, +11.23,1.98, +10.91,1.92, +11.77,1.81, +12.06,2.13, +12.09,1.42, +12.17,0, +11.72,-1.34, +11.44,-3.08, +11.2,-4.71, +10.09,-6.34, +8.74,-7.33, +9.97,-1.23, +9.04,-2.56, +8.6,-3.63, +9.01,-3.48, +9.83,-2.82, +11.93,-2.49, +11.59,-4.22, +9.97,-3.83, +8.31,-4.79, +7.11,-5.38, +7.46,-3.73, +8.87,0.32, +8.69,-1.08, +6.29,-5.4, +1.73,-7.54, +4.11,-6.73, +4.99,-9.02, +5.7,-9.88, +4.32,-9.62, +2.18,-10.17, +0.32,-10.27, +-3.28,-8.02, +-6.03,-7.19, +-6.62,-7.89, +-3.06,-7, +1.46,-6.83, +6.07,-10.52, +8.36,-12.35, +10.94,-9.54, +11.84,-9.51, +12.84,-7.41, +12.46,-6.84, +11.93,-5.11, +10.1,-3.94, +7.84,-4.53, +5.44,-4.76, +3.14,-5.4, +2.61,-5.14, +3.77,-4.58, +6.32,-5.3, +7.38,-4.93, +8.31,-1.64, +10.22,1.2, +9.78,-0.69, +6.82,-4.24, +0.53,-2.79, +-5.4,1.75, +-1.32,5.53, +4.83,4.08, +9.22,0.59, +10.68,-1.47, +4.93,-3.42, +-2.74,-3.88, +-6.32,0.04, +-0.77,2.03, +-1.33,-3.15, +-1.35,2.14, +7.69,1.46, +3.39,-3.64, +-1.54,-3.95, +-5.42,-2.53, +-5.97,-0.6, +-6.01,0.15, +-5.9,-0.23, +-4.94,1.02, +-4.64,1.96, +-3.62,2.09, +-3.54,2.04, +-4.24,2.45, +-4.28,2.83, +-3.09,4.57, +-4.02,-0.59, +-2.72,-0.01, +-0.81,2.77, +-0.51,2.87, +-0.51,2.87, +-0.42,2.88, +0,3.73, +-0.15,4.72, +-0.88,4.98, +-0.51,4.12, +-1.32,1.98, +-1.91,0.28, +-0.99,1.53, +-0.19,1.93, +0,2.43, +0.38,2.52, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.75,-5.66, +4.41,-5.26, +3.7,-4.41, +2.88,-3.45, +1.7,-2.62, +0.69,-2.07, +-0.94,-1.65, +-2.3,-1.11, +-3.22,-0.63, +-4.02,-0.71, +-3.11,-0.97, +-2.08,-0.97, +-3.01,-1.61, +-2.13,-1.78, +-0.66,-1.78, +0.72,-1.85, +1.7,-1.86, +2.25,-1.56, +2.68,-1.28, +3.4,-1.11, +2.93,-0.71, +2.16,-0.37, +1.93,-0.07, +1.57,0.28, +1.21,0.3, +1.04,0.34, +0.97,0.35, +0.92,0.32, +0.13,-0.18, +0,0, +0,0, +-0.05,-0.05, +0.17,0.31, +0.94,2.02, +-0.09,0.99, +-0.08,-0.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.39,5.72, +14.15,11.87, +15.67,10.5, +16.89,6.7, +17.67,6.06, +15.74,6.38, +14.6,4.48, +9.72,2.13, +8.9,0.54, +9,2.7, +7.58,2.3, +8.41,1.19, +6.39,1.01, +6.94,6.56, +9.31,6.1, +10.67,5.13, +10.8,5.14, +11.37,5.31, +10.96,4.71, +10.33,5.97, +10.2,3.93, +10.58,1.87, +11.6,2.04, +12.72,2.24, +12.83,2.26, +12.83,2.26, +12.51,2.21, +12.01,2.12, +11.87,2.09, +11.38,3.91, +11.63,2.05, +11.71,2.06, +12.1,1.9, +12.64,0.26, +13.25,1.16, +13.44,1.88, +13.82,0.59, +13.52,-0.67, +13.25,-2.34, +12.35,-4.5, +10.72,-6.19, +9.26,-6.58, +10.77,-0.24, +9.22,-1.89, +8.39,-3.95, +8.46,-3.51, +8.93,-2.97, +9.39,-3.44, +10.08,-5.1, +11.02,-4.01, +9.55,-4.96, +8.42,-6.16, +9.07,-4.8, +10.47,-0.55, +9.43,0.56, +5.62,-4.99, +2.02,-7.55, +3.74,-7.04, +4.49,-7.39, +5.47,-10, +3.78,-10.37, +1.76,-9.95, +-1.39,-9.43, +-4.64,-7.69, +-5.52,-8.47, +-5.44,-9.91, +-0.91,-9.56, +4.15,-8.86, +6.44,-11.16, +9.47,-11.79, +11.71,-9.83, +13.69,-9.33, +13.52,-7.81, +11.81,-6.82, +9.53,-5.16, +8.99,-5.19, +6.65,-4.07, +4.35,-4.88, +2.09,-4.45, +0.21,-3.03, +2.21,-2.52, +5.35,-2.91, +8.69,-1.8, +9.63,-0.44, +10.48,0.22, +5.56,-2.25, +-3.19,-1.09, +-5.25,2.53, +-5.99,4.73, +-1.11,6.67, +5.41,4.88, +9.83,0.66, +4.22,-2.77, +-1.07,-0.46, +-0.48,-4.6, +-8.03,-0.82, +-4.98,-1.27, +-3.86,-3.55, +-4.92,-1.73, +-1.75,-1.37, +0.7,-4.85, +-3.5,-4.5, +-5.26,-2.86, +-5.95,-0.87, +-6.03,0, +-5.32,0.13, +-4.9,1.16, +-4.15,2.12, +-3.3,2.37, +-3.48,2.12, +-4.63,1.99, +-3.75,3.89, +-3.11,4.9, +-4.62,-0.51, +-2.98,-0.36, +-0.82,2.76, +-0.51,2.87, +-0.51,2.87, +-0.17,2.9, +0,3.64, +-0.22,4.55, +-1.12,4.91, +-1.51,4.22, +-2.07,1.5, +-1.76,0.68, +-0.32,1.82, +0.08,1.94, +0.23,2.41, +0.34,1.91, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.72,-4.43, +3.28,-3.9, +2.39,-2.85, +1.43,-2.28, +0.41,-1.85, +-0.81,-1.55, +-1.83,-0.86, +-2.96,-0.59, +-3.81,-0.75, +-3.43,-1.24, +-3.47,-1.47, +-3.02,-1.92, +-1.72,-2.32, +-0.41,-2.23, +0.66,-2.17, +1.73,-2.34, +2.26,-2.01, +2.72,-1.82, +3.51,-1.71, +2.89,-1.18, +2.37,-0.64, +1.96,-0.22, +1.91,0.29, +1.74,0.6, +1.68,0.9, +1.49,1.09, +0.39,0.02, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.33,-0.15, +-0.18,-0.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +13.03,14.79, +15.43,12.95, +15.02,11.3, +10.61,8.8, +8,1.46, +9.55,1.12, +10.3,0, +11.06,1.05, +11.25,2.65, +10.03,2.02, +7.13,1.28, +4.39,2.43, +7.72,6.09, +8.26,7.64, +8.82,7.68, +9.23,7.46, +7.5,9.03, +9.66,5.92, +10.12,5.84, +10.41,3.79, +10.91,1.92, +11.04,1.73, +12.09,1.42, +12.83,2.26, +12.93,2.28, +12.83,2.26, +12.51,2.21, +12.38,2.69, +11.78,4.29, +11.67,2.85, +12.08,2.13, +12.83,2.26, +13.58,0, +14.43,0, +14.48,0, +14.83,0, +14.62,-0.27, +14.06,-2.48, +12.43,-4.2, +10.14,-4.73, +10.03,-4.27, +11.19,-1.97, +9.53,-1.86, +8.79,-3.2, +8.48,-3.67, +9.01,-5.7, +9.5,-4.57, +9.61,-4.14, +11.14,-4.06, +11.1,-4.04, +10.11,-3.21, +10.6,-5.58, +12.04,-4.43, +9.44,-1.93, +6.18,-4.33, +4.83,-5.76, +6.47,-5.09, +6.18,-5.69, +5.57,-8.44, +3.84,-10.54, +1.2,-10.54, +-1.9,-9.6, +-6.98,-6.98, +-5.97,-7.89, +-3.23,-10.22, +0.67,-11.12, +4.06,-10.46, +6.8,-11.78, +8.91,-11.96, +11.89,-10.4, +12.64,-8.8, +13.52,-7.81, +11.64,-6.72, +9.19,-5.31, +6.68,-5.03, +4.63,-4.78, +1.77,-4.94, +0.39,-3.64, +-0.1,-2.18, +1.24,-1.74, +5.93,-0.47, +9.65,-0.44, +10.64,0, +6.58,-1.23, +-1.08,-0.78, +-6.1,1.85, +-6.21,4.01, +-5.54,5.1, +-1.99,6.11, +4,3.53, +1.68,-1.5, +-3.8,-2.24, +-5.8,-0.44, +-2.73,-3.58, +-6.49,-4.73, +-3.53,-7.25, +-2.27,-8.14, +-3.93,-9.8, +-4.96,-10.35, +-0.14,-8.81, +-3.14,-6.85, +-4.8,-3.83, +-5.71,-1.8, +-5.95,-0.9, +-5.31,0.25, +-3.81,1.39, +-3.3,2.37, +-3.13,2.62, +-3.51,2.03, +-4.68,1.44, +-4.19,2.98, +-3.56,3.98, +-4.46,0.74, +-3.08,0.12, +-1.77,2.94, +-1.14,2.64, +-0.63,2.84, +-0.34,2.89, +-0.32,3.67, +-0.42,4.41, +-1.52,4.81, +-1.93,3.59, +-2.16,1.69, +-1.17,1.38, +0.28,1.92, +0.34,1.91, +0.44,2.49, +0.34,1.91, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.03,-3.61, +2.55,-3.04, +1.77,-2.25, +0.86,-1.7, +-0.08,-1.17, +-0.92,-0.8, +-1.87,-0.77, +-2.79,-0.57, +-2.82,-0.86, +-2.57,-1.37, +-2.36,-1.71, +-1.97,-2.12, +-1.21,-2.63, +0.02,-2.94, +1.25,-3.23, +1.91,-2.96, +2.52,-2.86, +3.09,-2.66, +3.41,-2.19, +2.67,-1.5, +2.68,-1.07, +2.05,-0.29, +1.91,0.3, +1.79,0.74, +1.67,0.99, +1.37,1.28, +-0.21,-0.13, +-0.03,0.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +18.17,13.51, +18.74,12.11, +16.41,10.46, +11.47,9.48, +6.75,3.78, +13.21,-1.68, +13.06,0.42, +14.16,1.32, +12.37,0.82, +10.42,0.48, +10.74,2.09, +12.75,3.14, +12,4.34, +11.33,5.26, +10.01,7.49, +9.46,8.89, +9.08,8.64, +9.61,7.65, +10.51,5.19, +10.92,2.71, +11.75,0.89, +12.21,-0.77, +12.77,0.48, +13.05,1.29, +12.49,1.29, +12.74,2.01, +12.55,2.21, +12.66,2.89, +12.41,3.8, +11.92,3.76, +11.78,2.43, +12.61,2.22, +13.65,1.19, +14.48,0, +13.67,0, +14.24,0, +12.38,-2.42, +12.08,-4.6, +11.25,-4.8, +9.75,-5.73, +9.33,-5.41, +11.18,-4.6, +10.18,-2.07, +9.54,-2.56, +9.54,-4.13, +10.01,-5.41, +10.41,-3.79, +10.58,-3.17, +10.65,-3.53, +10.17,-3.09, +9.16,-1.79, +8.11,-4.29, +8.78,-5.93, +8.42,-3.7, +8.92,-4.04, +7.18,-4.71, +6.87,-4.3, +7.04,-4.01, +5.65,-5.95, +4.67,-10.67, +0.53,-10.77, +-3.66,-9.33, +-7.23,-6.34, +-7.34,-5.87, +-1.62,-9.35, +1.93,-12.2, +4.97,-10.66, +6.76,-11.7, +9.78,-10.5, +12.12,-10.17, +12.59,-8.3, +12.37,-7.49, +11.13,-6.73, +8.45,-5.88, +5.01,-5.16, +3.52,-5.01, +2.21,-4.53, +1.17,-2.97, +0.82,-1.38, +2.08,-0.73, +5.78,0.84, +9.4,0.76, +6,-0.62, +-1.99,-0.83, +-4.67,1.7, +-6.34,3.66, +-6.8,4.13, +-6.11,4.45, +-5.29,3.58, +-4.09,2.77, +-5.07,-1.83, +-5.02,-3.3, +-5.11,-4.28, +-3.21,-5.69, +-2.89,-8.03, +-1.44,-9.77, +0.5,-10.87, +0.83,-12.71, +0.35,-13.84, +3.08,-13.14, +-2.78,-9.31, +-4.36,-5.84, +-5.81,-3.6, +-5.8,-1.55, +-5.31,-0.02, +-4.2,1.22, +-3.3,2.37, +-2.87,2.41, +-3.07,1.92, +-4.66,0.9, +-4.72,1.95, +-3.97,2.55, +-3.75,0.9, +-2.96,1.01, +-3.36,2.04, +-2,2.34, +-0.99,2.73, +-0.57,2.86, +-0.84,3.58, +-0.81,3.99, +-1.65,4.06, +-2.33,3.33, +-1.92,1.83, +-0.41,1.79, +0.34,1.91, +0.42,2.39, +0.51,2.87, +0.32,1.79, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.64,-3.15, +2.15,-2.56, +1.41,-2.05, +0.59,-1.66, +-0.25,-0.93, +-1,-0.85, +-1.83,-0.89, +-2.54,-1.02, +-2.56,-1.38, +-2.38,-1.66, +-1.92,-2.19, +-1.55,-2.45, +-0.75,-2.8, +0.44,-3.04, +1.59,-3.65, +2.13,-3.48, +2.61,-3.14, +3.05,-2.71, +3.32,-2.25, +2.68,-1.52, +2.71,-0.93, +2.1,-0.19, +1.91,0.3, +1.74,0.85, +1.63,1.05, +0.94,1.09, +-0.22,-0.21, +-0.03,-0.03, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +21.21,10.88, +20.42,9.77, +19.75,7.7, +17.45,4.2, +16.77,-0.33, +16.72,-2.2, +16.91,0, +16.26,0, +14.43,0, +14.37,1.3, +15.46,2.73, +16.34,2.88, +16.38,2.89, +13.31,3.86, +9.7,7.56, +8.71,9.45, +8.33,9.43, +9.58,7.79, +11.19,5.4, +12.56,0.6, +13.47,-1.04, +13.66,-2.41, +14.28,-0.41, +13.13,0, +12.05,0, +11.96,1.05, +12.49,1.92, +12.83,2.26, +12.83,2.26, +12.66,2.89, +12.63,4.12, +12.96,2.29, +14.37,1.24, +13.8,0, +13.35,0, +12.06,-1.14, +8.76,-4.61, +9.26,-6.99, +9.41,-5.8, +9.48,-7.96, +10.6,-6.42, +12.02,-4.38, +11.9,-2.36, +10.48,-1.85, +9.91,-3.08, +9.68,-5.36, +10.21,-4.51, +11.39,-2.01, +10.14,-2.01, +9.42,-3.02, +7.4,-5.16, +6.26,-5.31, +6.81,-5.06, +8.5,-3.09, +10.09,-5.22, +8.33,-4.81, +6.25,-5.89, +8.15,-3.87, +7.38,-3.29, +2.91,-9.74, +0.38,-10.58, +-2.89,-9.74, +-6.49,-7.3, +-7.85,-6.55, +-1.26,-9.8, +2.71,-11.15, +5.47,-10.16, +6.11,-10.62, +9.32,-8.63, +10.83,-10.26, +11.91,-10, +10.44,-8.76, +8.86,-7.43, +6.17,-6.25, +3.26,-5.64, +3.01,-5.22, +3.33,-4.55, +3.06,-3.4, +2.28,-1.16, +2.98,0.41, +5.15,2.42, +4.62,0.41, +-1.72,-0.38, +-4.8,0.79, +-4.79,2.4, +-6.52,2.37, +-7.73,1.71, +-5.82,2.86, +-5.02,0.1, +-6.37,-0.92, +-5.39,-2.51, +-3.93,-4.73, +-2.54,-6.69, +-1.51,-7.69, +-0.42,-9.04, +1.2,-10.68, +2.32,-11.14, +3.3,-12.98, +3.49,-14, +2.43,-12.83, +-2.45,-9.24, +-4.83,-7.1, +-6.29,-4.43, +-5.86,-1.95, +-5.49,-0.52, +-4.75,1.11, +-3.31,2.09, +-2.23,1.87, +-2.76,1.79, +-4.45,0.88, +-4.99,0.61, +-3.9,1.85, +-3.33,1.55, +-3.32,2.5, +-3.96,2.92, +-3.08,2.9, +-1.78,3.34, +-1.28,2.79, +-1.86,3.75, +-1.7,3.83, +-2.36,4.1, +-2.23,2.78, +-1.23,1.83, +0.11,1.92, +0.38,2.15, +0.51,2.87, +0.48,2.71, +0.25,1.44, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.5,-2.98, +1.88,-2.54, +1.35,-2.29, +0.72,-1.75, +-0,-1.46, +-0.66,-1.44, +-1.31,-1.44, +-1.63,-1.22, +-1.87,-1.63, +-1.92,-2.13, +-1.64,-2.41, +-1.07,-2.71, +-0.47,-2.87, +0.33,-2.96, +1.44,-3.66, +1.98,-3.56, +2.61,-3.14, +3.03,-2.73, +3.29,-2.25, +2.74,-1.37, +2.86,-0.53, +2.69,0.14, +2.26,0.47, +1.75,0.85, +1.03,0.59, +-0.24,-0.16, +-0.06,0.13, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +7.07,2.57, +22.47,8.18, +21.25,7.73, +19.73,6.75, +18.35,3.61, +17.3,0, +17.66,-0.33, +17.69,0, +16.94,0, +15.94,0, +15.46,1.81, +15.7,2.77, +15.97,2.82, +16.78,2.96, +15.55,4.07, +10.73,5.39, +10.69,7.35, +9.41,9.64, +9.14,10.35, +10.86,6.29, +11.98,1.88, +12.83,-2.26, +13.42,-2.37, +13.91,-0.97, +13.35,0, +12.47,0, +13.13,0, +12.97,0.65, +12.96,2.29, +12.83,2.26, +13.07,2.3, +13.1,4.34, +13.19,3.4, +12.99,2.6, +13.64,0.38, +11.81,-1.04, +9.78,-1.73, +7.44,-4.64, +7.95,-6.11, +9.13,-7.1, +10.63,-7.44, +11.82,-5.68, +12.87,-3.35, +13.24,-2.33, +11.87,-2.09, +9.84,-2.95, +9.32,-3.39, +10.8,-3.93, +11.02,-3.38, +10.71,-2.13, +10.56,-2.48, +8.08,-4.82, +7.15,-3.79, +8.62,-2.86, +9.57,-2.61, +9.87,-5.21, +9.57,-5.52, +7.41,-6.47, +8,-5.04, +6.38,-5.61, +2.48,-10.08, +0.26,-10.83, +-1.8,-8.69, +-6.38,-8, +-7.76,-6.79, +-2.91,-8.31, +3.82,-10.59, +4.24,-11.03, +6.31,-8.44, +9.48,-7.5, +8.51,-10.14, +10.34,-9.58, +10.3,-8.64, +7.68,-6.83, +4.35,-5.92, +2.8,-5.14, +2.54,-5.44, +2.68,-4.42, +3.77,-4.26, +3.08,-1.93, +1.33,-0.35, +0.1,3.2, +-3.16,1.04, +-4.02,0.15, +-3.33,1.55, +-4.72,1.37, +-6.62,-0.12, +-7.43,-0.17, +-5.25,1.58, +-3.82,-1.34, +-5.76,-3, +-4.47,-4.87, +-3.07,-6.47, +-1.15,-8.03, +-0,-8.78, +0.71,-9.68, +2.19,-10.84, +3.79,-10.41, +4.34,-11.93, +5.5,-12.82, +0.41,-11.59, +-4.03,-7.68, +-6.17,-6.19, +-6.77,-4.17, +-6.37,-2.6, +-6.05,-0.94, +-4.96,0.73, +-3.72,1.92, +-2.27,1.82, +-2.69,1.56, +-4.04,1.03, +-5.03,0.29, +-3.76,1.76, +-2.83,3.46, +-3.52,4.47, +-4.49,4, +-4.04,3.7, +-2.53,3.39, +-2.61,3.11, +-3.61,3.26, +-2.53,4.38, +-2.32,3.54, +-2.06,2.46, +-0.85,1.85, +0.23,1.92, +0.44,2.51, +0.51,2.87, +0.51,2.87, +0.27,1.56, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.18,-2.7, +1.59,-2.44, +1.1,-2.32, +0.61,-1.84, +0.14,-1.93, +-0.35,-1.91, +-0.84,-1.74, +-1.23,-1.49, +-1.1,-1.72, +-1.25,-2.55, +-1.05,-2.71, +-0.83,-2.79, +-0.14,-2.91, +0.31,-2.94, +1.23,-3.68, +1.83,-3.64, +2.27,-3.31, +2.86,-2.72, +3.16,-2.2, +2.72,-1.46, +2.82,-0.73, +2.91,0.04, +2.8,0.79, +2.43,1.11, +1.6,1.15, +0.05,-0.07, +-0.04,-0.04, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +21.86,7.96, +21.46,7.81, +20.92,5.93, +19.6,3.46, +18.33,1.6, +18.08,0, +18.36,0, +18.23,0, +17.74,0, +16.45,2.05, +15.8,2.79, +15.84,3.17, +16.57,5.51, +14.13,5.7, +11.83,4.57, +12.08,4.8, +11.12,6.16, +10.93,9.93, +10.4,8.76, +11.03,2.36, +12.51,-2.21, +12.95,-2.28, +13.78,-2.43, +13.61,0, +13.13,0, +13.86,0, +13.67,0, +12.97,2, +13.15,2.32, +13.42,2.37, +13.82,2.71, +13.68,2.82, +12.42,2.19, +11.22,0.89, +9.78,-1.48, +9.28,-1.64, +7.77,-4.09, +8,-5.59, +8.6,-6.86, +11.42,-5.45, +12.75,-4.64, +13.47,-3.61, +12.8,-2.83, +11.55,-2.04, +10.3,-2.72, +10.54,-2.67, +11.18,-2.19, +10.56,-3.84, +10.62,-2.88, +10.22,-1.03, +10,-2.04, +9.92,-1.81, +10.67,-2.75, +9.23,-2.63, +8.71,-5.36, +7.25,-6.9, +6.45,-7.38, +7.15,-5.32, +5.4,-6.65, +3.15,-9.83, +0.41,-10.42, +-2.44,-8.57, +-4.84,-9.19, +-5.86,-6.67, +-3.3,-8.09, +1.45,-10.31, +2.34,-10.46, +4.44,-8.12, +5.94,-8.71, +6.89,-10.42, +10.1,-8.48, +9.73,-8.16, +7.31,-7.4, +3.78,-5.86, +2.85,-5.17, +2.55,-4.97, +2.53,-4.38, +1.89,-4.5, +0.93,-3.41, +-1.83,-1.79, +-2.54,-0.4, +-2.65,-0.59, +-2.34,-0.3, +-3.49,0.56, +-4.81,0.02, +-5.73,-1.83, +-6.59,-2.21, +-5.72,-2, +-4.53,-4.6, +-4.68,-4.72, +-3.36,-6.06, +-1.72,-7.62, +-0.23,-8.76, +1.11,-9.28, +1.69,-9.6, +2.19,-9.94, +2.01,-8.96, +3.07,-8.17, +4.99,-8.94, +-3.39,-7.82, +-6.11,-5.58, +-7.32,-4.23, +-7.68,-3.18, +-7.1,-2.58, +-6.06,-1.51, +-5.14,0.44, +-3.92,1.86, +-2.43,1.93, +-2.2,1.55, +-3.44,0.9, +-4.72,0.12, +-3.59,2.86, +-1.7,5.94, +-3.5,5.66, +-4.78,4.88, +-4.4,4.24, +-3.17,3.78, +-4.11,3.04, +-3.77,3.32, +-2.21,3.97, +-2.08,3.11, +-1.87,2.23, +-0.75,1.78, +-0.08,2.06, +0.12,2.77, +0.29,2.89, +0,2.92, +0.36,2.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.51,-2.57, +1.4,-2.55, +0.91,-2.51, +0.52,-1.96, +0.15,-1.93, +-0.06,-1.94, +-0.39,-1.92, +-0.56,-1.85, +-0.67,-2.2, +-0.64,-2.84, +-0.52,-2.87, +-0.44,-2.88, +0.12,-2.91, +0.56,-2.95, +1.3,-3.73, +1.65,-3.36, +1.84,-2.68, +2.13,-2.1, +2.5,-1.67, +2.68,-1.11, +2.85,-0.57, +2.9,0.15, +2.78,0.88, +2.64,1.22, +-0.05,-0.45, +-0.06,-0.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +21.55,7.84, +20.75,7.55, +20.56,5.33, +20.13,3.55, +18.75,1.89, +18.94,0, +18.81,0, +19.54,0, +19.24,0, +18.27,0, +16.62,1.9, +16,4.15, +16.73,6.42, +13.78,6.65, +10.81,4.57, +11.04,3.54, +11.85,4.31, +12.45,6.29, +11.61,8.29, +10.76,3.86, +11.71,-2.06, +12.47,-2.2, +13.25,-2.34, +13.19,0, +13.44,0, +13.67,0, +14,0, +14.07,0.69, +14.23,1.74, +13.96,1.84, +13.78,2.43, +13.78,2.43, +12.71,2.24, +11.73,0, +10.36,-1.61, +9.53,-1.86, +8.78,-3.78, +9.12,-4.98, +10.07,-5.03, +11.2,-4.39, +12.58,-4.24, +13.78,-2.43, +14.02,-2.47, +11.71,-2.06, +11.12,-2.39, +10.79,-2.39, +10.91,-1.92, +10.31,-3.5, +10.2,-1.82, +9.89,0.29, +10.04,-0.49, +11.31,-1.22, +10.08,-2.24, +7.86,-4.5, +4.24,-7.21, +3.92,-7.04, +6.85,-7.66, +4.58,-8.71, +2.62,-9.42, +0.61,-10.12, +0.24,-11.57, +-3.38,-9.41, +-4.64,-9.37, +-3.4,-7.84, +-2.18,-7.94, +-0.86,-9.84, +1.91,-9.55, +3.86,-8.03, +5.58,-9.66, +6.47,-9.45, +8.76,-8.22, +7.1,-8.46, +5.29,-7.56, +3.62,-6.27, +3.01,-5.22, +2.22,-5.32, +0.89,-4.31, +0.57,-4.29, +-0.24,-4.04, +-1.54,-3.02, +-1.99,-3.13, +-1.07,-2.83, +-2.14,-1.4, +-3.42,-0.6, +-5.03,-1.3, +-5.56,-2.21, +-5.93,-3.86, +-5.28,-5.71, +-4.17,-5.59, +-3.83,-5.83, +-2.63,-6.64, +-1.38,-7.81, +0.16,-8.54, +1.64,-9.28, +1.58,-8.97, +1.46,-9.54, +-0.92,-8.76, +-3.44,-5.93, +-2.59,-4.65, +-3.55,-8.8, +-6.08,-5.03, +-7.59,-2.62, +-7.64,-2.15, +-6.73,-2.45, +-5.7,-1.92, +-5.1,-0.63, +-3.96,1.41, +-2.76,1.71, +-2.34,1.72, +-3.36,0.82, +-4.48,-0.23, +-2.15,3.76, +-1.44,6, +-2.91,5.74, +-3.44,4.92, +-3.73,4.72, +-3.25,3.87, +-3.76,3.64, +-3.05,3.82, +-1.58,3.91, +-1.67,3.21, +-1.87,2.23, +-1.24,2.27, +-0.71,2.54, +0,2.92, +0,2.92, +0,2.92, +0.36,2.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.49,-2.67, +1.25,-2.7, +1,-2.74, +0.86,-2.64, +0.42,-2.69, +0.09,-2.61, +-0.28,-2.73, +-0.46,-2.7, +-0.5,-2.83, +-0.51,-2.87, +-0.51,-2.87, +-0.2,-2.9, +0.14,-2.91, +0.59,-2.88, +1.08,-3.03, +1.65,-2.98, +1.84,-2.26, +2.31,-1.76, +2.65,-1.2, +2.83,-0.67, +2.91,-0.1, +2.91,0.6, +2.66,0.8, +2.17,0.85, +0.49,-0.09, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.39,1.96, +21.45,7.81, +20.77,7.56, +20.95,4.22, +20.04,3.53, +18.95,2.5, +19.05,0, +19.37,0, +20.02,0, +19.21,0, +19.05,0, +17.9,0.33, +16.76,3.84, +17.14,6.24, +14.28,6.76, +10.54,5.81, +12.06,4.05, +12.92,3.57, +12.24,4.45, +12.35,5.36, +11.09,3.37, +11.91,-2.34, +11.87,-2.09, +12.75,-1.59, +13.02,0, +13.63,0, +13.19,0, +13.24,0, +13.24,0, +13.67,0, +13.69,0.32, +14.22,0.73, +13.84,1.74, +13.49,0.99, +12.23,-0.21, +11.32,-2, +10.69,-1.88, +10.59,-3.86, +10.18,-4.22, +10.58,-3.17, +12.76,-2.52, +13.36,-2.36, +14.42,-2.54, +13.95,-2.08, +12.84,-2.05, +12.11,-0.9, +11.07,-1.46, +10.93,-0.21, +10.58,-1.59, +10.39,-1.22, +9.9,0.43, +10.64,1.37, +11.02,0, +8.67,-2.84, +6.77,-5.25, +3.91,-7.03, +4.75,-6.2, +5.13,-8.15, +3.16,-9.53, +2.48,-9.09, +1.13,-11.63, +-0.52,-11.42, +-2.11,-8.99, +-3.02,-9.31, +-2.03,-8.3, +-1.52,-8.65, +-1.03,-9.09, +1.13,-8.5, +2.49,-7.51, +5.15,-8.92, +5.76,-9.97, +6.76,-8.06, +5.19,-8.42, +4.42,-7.65, +3.85,-6.66, +2.62,-5.58, +2.06,-5.66, +0.53,-4.78, +-0.1,-4.07, +-0.46,-4.41, +-1.01,-4.69, +-0.38,-4.53, +0.07,-3.74, +-1.44,-2.4, +-3.57,-1.88, +-4.56,-2.46, +-5.14,-2.68, +-4.47,-3.52, +-3.66,-5.44, +-3.41,-5.91, +-2.81,-5.67, +-2.28,-6.94, +-1.38,-7.85, +-0,-7.97, +0.56,-8.65, +1.49,-8.45, +1.43,-9.51, +-1.3,-11.04, +-5.2,-11.09, +-6.94,-12.77, +-2.52,-10.16, +-6.58,-5.33, +-7.46,-2.72, +-7.49,-1.32, +-6.32,-1.75, +-5.66,-2.06, +-4.86,-1.3, +-4.19,0.6, +-2.98,1.52, +-1.85,1.3, +-3.22,0.62, +-3.99,-0.78, +-2.23,3.04, +-0.93,5.3, +-2.42,5.5, +-2.71,5.22, +-2.73,4.43, +-2.47,4.27, +-2.55,3.77, +-2.06,3.86, +-1.6,3.87, +-1.94,3.36, +-1.27,1.81, +-1.14,2.27, +-0.91,2.19, +-0.06,2.91, +0.06,2.91, +0.23,2.57, +0.32,1.79, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.56,-3.67, +1.24,-3.39, +1.09,-3, +0.96,-2.75, +0.53,-2.87, +0.41,-2.89, +-0.03,-2.91, +-0.33,-2.89, +-0.51,-2.87, +-0.51,-2.87, +-0.51,-2.87, +-0.31,-2.89, +0.1,-2.91, +0.5,-2.87, +0.98,-2.74, +1.42,-2.44, +1.91,-1.9, +2.5,-1.37, +2.83,-0.68, +2.9,-0.29, +2.94,0.08, +3.4,0.97, +0.14,-0.55, +-0.13,-0.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.27,1.92, +20.34,7.4, +12.02,4.37, +20.32,4.97, +20.04,3.53, +19.39,2.28, +19.02,0.37, +19.24,3.39, +19.34,1.47, +19.05,0, +19.05,0, +18.52,0.52, +18.12,3.2, +17.79,6.09, +13.81,7.34, +11.49,6.63, +12.88,4.13, +13.1,2.31, +12.89,3.61, +12.3,4.24, +11.31,3.1, +11.58,-1.72, +12.29,-2.17, +12.57,-1.45, +13.51,0, +13.75,0, +14.1,0, +14,0, +13.08,0.97, +13.56,0, +14.12,0, +14.32,0, +14,0, +14,0, +13.19,0, +13.05,-1.13, +12.58,-1.74, +10.88,-3.64, +10.48,-3.52, +11.63,-2.98, +13.65,-2.41, +13.89,-1.24, +15.06,-1.44, +14.76,-0.93, +13.21,-1.59, +13.09,0.59, +11.71,0.23, +11.42,0.87, +11.4,-0.49, +9.63,-1.23, +9.59,0.49, +11.9,1.74, +9.18,-1.52, +8.34,-2.83, +7.94,-4.32, +8.33,-7.04, +8.25,-5.96, +6.23,-6.79, +2.97,-8.87, +2.39,-9.87, +2.12,-12.03, +-0,-11.02, +-1.28,-9.37, +-1.38,-9.24, +-0.26,-8.92, +-1.55,-8.81, +-1.5,-8.29, +0.95,-7.72, +1.85,-8.35, +2.91,-9.89, +4.53,-9, +4.89,-8.47, +4.34,-7.8, +4.12,-7.14, +3.64,-6.71, +2.29,-5.91, +1.63,-5.78, +0.56,-4.52, +0.18,-4.28, +-0,-5.05, +-0,-5.38, +0.18,-4.55, +0.57,-3.59, +-1.34,-2.47, +-3.87,-1.88, +-5.05,-2.69, +-3.98,-2.81, +-3.6,-3.52, +-2.1,-5.32, +-2.06,-5.66, +-2.22,-5.72, +-2.17,-6.75, +-1.24,-7.03, +-0,-7.65, +0.37,-8.79, +1.42,-9.03, +1.86,-10.53, +1.81,-12.12, +0.18,-13.26, +-3.45,-16.82, +-6.51,-11, +-6.82,-6.29, +-6.95,-3.51, +-7.03,-1.77, +-6.11,-1.52, +-5.66,-2.06, +-4.94,-1.64, +-4.4,-0.51, +-3.36,0.82, +-2.39,1.35, +-3.76,0.04, +-3.94,-1, +-2.91,1.61, +-1.52,4.81, +-2.14,4.71, +-2.44,4.72, +-2.19,4.3, +-2.14,4.06, +-1.82,3.39, +-2.06,3.56, +-1.33,3.65, +-1.57,3.12, +-0.39,2.11, +-0.41,1.6, +-0.93,2.75, +-0.14,2.76, +-0.28,2.18, +0.17,2.16, +0.22,1.65, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.4,-3.84, +1.4,-3.84, +1.33,-3.65, +1.14,-3.42, +0.64,-3.58, +0.45,-3.12, +0.02,-3, +-0.03,-2.96, +-0.13,-2.91, +-0.17,-2.91, +-0.26,-2.9, +-0.25,-2.9, +0.1,-2.91, +0.58,-2.54, +0.87,-2.24, +1.28,-1.61, +1.56,-1.15, +2.57,-0.76, +2.91,-0.1, +2.94,0.3, +3.18,0.6, +0.72,-0.77, +-0.11,-0.29, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +15.34,2.7, +19.31,3.4, +18.76,3.31, +18.97,3.35, +18.9,2.96, +19.21,0, +20.02,0, +19.49,1.12, +18.76,3.31, +19.61,4.34, +15.05,7.21, +12.72,7.35, +12.49,4.14, +12.93,2.28, +13.42,2.37, +12.17,2.85, +10.48,3.17, +10.68,-1.11, +11.6,-2.04, +12.72,-1.91, +13.9,0, +14,0, +14.13,0, +13.67,0, +12.17,0, +13.51,0, +13.39,0, +14,0, +13.96,-0.41, +14,0, +13.67,0, +13.75,0, +13.09,-1.1, +11.85,-2.62, +10.95,-3.03, +12.01,-2.12, +13.86,-1.58, +14.54,0, +15.29,0, +14.32,0, +14.49,0.02, +14.09,2.13, +12.03,2.12, +11.9,2.92, +12.09,0.75, +10.63,-1.35, +11.04,1.24, +10.97,0.87, +7.93,-3.68, +8.86,-1.8, +9.11,-0.38, +9.06,-4.07, +10.87,-5.87, +7.2,-3.56, +6.8,-5.11, +2.82,-8.37, +2.7,-10.9, +1.35,-10.86, +-1.41,-9.63, +-1.55,-8.81, +-1.18,-8.59, +-1.58,-8.94, +-1.04,-8.16, +2.05,-7.89, +2.08,-9.42, +2.62,-9.72, +3.4,-9.34, +3.89,-8.61, +3.29,-8.29, +3.27,-7.24, +2.57,-6.69, +2.17,-5.97, +0.93,-5.3, +0.92,-4.74, +1.12,-4.34, +0.44,-5.02, +-0.43,-5.66, +-0.84,-3.91, +-1.07,-2.69, +-2.35,-2.47, +-4.54,-1.92, +-5.6,-2.2, +-4.03,-2.1, +-1.88,-1.26, +-0.73,-4.12, +-1.05,-5.93, +-1.05,-5.93, +-0.61,-6.82, +-1.22,-6.89, +0.17,-7.95, +0.91,-8.5, +2.03,-8.51, +3.74,-8.78, +5.16,-8.02, +3.93,-7.8, +-0.1,-9.8, +-7.16,-6.58, +-7.53,-5.43, +-6.62,-3.82, +-6.25,-2.83, +-5.66,-2.06, +-5.66,-2.06, +-4.98,-1.81, +-4.57,-1.32, +-3.55,0.12, +-2.96,0.61, +-4.03,-0.41, +-3.84,-1.4, +-2.72,1.47, +-1.45,4.27, +-1.02,4.44, +-1.36,3.98, +-1.43,3.77, +-1.43,3.18, +-1.64,3.08, +-1.33,3.37, +-1.28,2.63, +-0.44,2.68, +-0.33,1.92, +-0.52,1.28, +-1.16,2.67, +-0.56,1.89, +-0.37,2, +0.55,1.85, +0.56,1.55, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.4,-3.84, +1.4,-3.84, +1.4,-3.84, +1.29,-3.87, +0.77,-3.99, +0.68,-3.94, +0.2,-3.88, +-0,-3.43, +-0,-2.92, +-0,-2.92, +-0.07,-2.91, +-0.19,-2.9, +0.23,-2.53, +0.68,-1.86, +1.08,-1.61, +1.45,-1.28, +1.78,-0.75, +2.77,-0.15, +3.04,0.3, +2.6,0.32, +1.81,-0.11, +-0.12,-0.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +16.49,2.91, +18.76,3.31, +18.76,3.31, +18.76,3.31, +19,3.35, +19.65,3.11, +20.58,3.1, +19.9,2.98, +19.62,3.46, +20.32,3.58, +20.2,3.56, +18.31,4.42, +14.47,3.36, +12.73,2.63, +12.83,2.26, +11.69,2.77, +9.9,3.13, +10.69,0, +12.12,-0.47, +12.85,-1.96, +14,0, +13.87,0, +13.78,0, +13.3,0, +11.47,-0.27, +12.31,-0.38, +13.02,0, +14,0, +13.81,-2.16, +14,0, +14,0, +14.41,0, +13.32,-1.53, +12.83,-2.26, +11.87,-2.09, +12.68,-2.24, +14.34,-0.28, +14.97,0, +14.89,0.85, +13.54,1.89, +12.83,2.26, +12.72,1.91, +12.65,2.49, +12.06,4.07, +11.82,0.78, +12.09,0.79, +11.48,1.03, +10.56,-1.77, +7.89,-4.34, +9,-0.63, +8.02,3.4, +5.59,2.32, +9.94,-4.51, +8.2,-3.32, +8.69,-1.29, +8.74,-4.85, +5.91,-8.04, +3.83,-11.05, +-0.99,-9.22, +-2.5,-7.88, +-2.59,-7.86, +-2.53,-8.38, +0.27,-7.68, +1.93,-8.42, +3.16,-9.41, +3.75,-10.3, +3.12,-9.14, +3.22,-8.86, +2.82,-8.47, +2.15,-7.98, +1.2,-7.76, +1.63,-6.37, +0.88,-4.98, +0.96,-4.79, +1.39,-3.81, +0.91,-5.3, +-0.68,-5.34, +-1.65,-3.61, +-1.9,-2.03, +-2.9,-2.38, +-5.08,-2.16, +-5.71,-1.89, +-4.91,-1.54, +-3.99,-0.6, +-1.17,-3.98, +-0.86,-5.41, +-0.45,-5.85, +0.17,-6.01, +0.6,-6.41, +1.49,-6.29, +2.39,-6.58, +2.78,-6.7, +3.65,-5.1, +5.12,-2.08, +5.24,-2.09, +0.15,-2.37, +-7.45,-2.71, +-7.2,-4.56, +-5.95,-4.41, +-5.16,-3.38, +-5.22,-3.01, +-5.44,-2.54, +-4.99,-2.75, +-4.6,-1.67, +-3.89,-0.3, +-3.69,0.06, +-3.94,-1, +-3.69,-1.72, +-2.42,0.89, +-0.77,3.41, +-1.77,3.93, +-1.42,3.17, +-0.37,3.23, +-0.83,3.16, +-1.25,2.91, +-1.58,2.63, +-0.45,2.41, +-0.34,2.38, +-1.53,1.41, +-1.03,1.13, +-1.38,1.84, +-0.69,1.29, +0.05,1.86, +0.92,1.78, +1.01,1.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.61,-3.93, +1.4,-3.84, +1.4,-3.84, +1.09,-3.93, +0.65,-4.03, +0.08,-3.97, +-0.01,-3.05, +-0.01,-2.92, +-0,-2.92, +-0,-2.92, +0.11,-2.7, +0.47,-1.96, +1.11,-1.59, +1.53,-1.16, +1.8,-0.66, +2.32,-0.21, +2.83,0.25, +2.99,0.57, +1,-0.23, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +18.76,3.31, +17.8,3.14, +18.44,3.25, +18.76,3.31, +19.72,3.48, +20.54,3.62, +20.99,3.7, +20.32,3.58, +19.88,3.5, +19.4,2.96, +19.78,2.76, +19.49,2.91, +15.61,2.58, +13,2.75, +11.74,1.85, +11.06,1.66, +8.97,3.27, +9.45,0.84, +11.89,0, +12.88,-0.28, +13.56,0, +13.44,0, +13.83,0, +12.38,0, +11.45,-0.66, +12.54,0, +12.89,0, +13.51,0, +13.81,-2.08, +13.97,-0.35, +14.19,0, +14.6,0, +13.54,-1.56, +12.83,-2.26, +12.7,-2.75, +13.85,-1.62, +14.41,0, +14.43,0, +15.01,1.09, +13.62,2.4, +12.44,2.45, +11.89,1.88, +11.86,4.35, +13.28,4.83, +13.34,3.27, +12.48,0.99, +10.39,-1.18, +9.41,-2.99, +8.64,-3.6, +10.17,-1.15, +7.93,3.06, +7.11,3.8, +10.68,-3.67, +9.57,-3.33, +9.32,0.01, +8.63,-5.43, +6.02,-5.09, +4.41,-9.6, +2.01,-10.76, +-0.69,-8.15, +-1.7,-8.48, +0.06,-7.64, +2.36,-7.09, +3,-8.25, +3.41,-9.36, +3.52,-10.48, +4.19,-9.48, +2.51,-8.67, +1.59,-9.05, +1.55,-8.81, +0.22,-8.64, +0.52,-7.56, +0.75,-4.99, +0.1,-4.63, +0.04,-4.05, +0.73,-4.71, +-0.71,-5.03, +-3.08,-2.43, +-2.79,-1.19, +-2.98,-2.64, +-5.61,-1.72, +-6.36,-1.12, +-6.06,-1.81, +-4.57,-2.1, +-1.04,-3.92, +0.25,-4.75, +0.76,-5.15, +1.47,-5.57, +1.93,-5.7, +2.08,-5.39, +2.57,-5.1, +2.8,-4.45, +2.03,-1.49, +4.35,1.07, +3.75,-0.24, +-3.62,-0, +-6.89,-3.24, +-6.31,-4.62, +-5.55,-4.65, +-4.62,-3.87, +-4.79,-3.63, +-5.14,-3.12, +-4.98,-2.87, +-4.61,-2.03, +-3.93,-1.05, +-4.02,-0.71, +-3.82,-1.39, +-3.35,-2.3, +-3.29,-3.14, +-0.5,2.84, +-0.43,2.88, +-0.17,2.87, +0.42,2.86, +0.46,2.38, +-0.26,2.01, +-0.83,2.05, +-1,2.02, +-0.98,1.87, +-1.05,1.28, +-0.02,1.4, +-1.33,0.19, +-0.21,1.33, +0.95,1.47, +0.85,2.01, +0.26,1.33, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.32,-4.27, +1.54,-3.9, +1.4,-3.84, +1.08,-3.93, +0.6,-4.03, +0.01,-3.62, +-0.12,-2.93, +-0.23,-2.9, +-0.01,-2.92, +-0,-2.79, +0.14,-2.09, +0.63,-1.71, +1.28,-1.34, +1.82,-0.62, +2.15,-0.1, +2.75,0.36, +2.81,0.34, +-0.15,-0.35, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +17.91,3.16, +17.91,3.16, +18.44,3.25, +19.17,3.38, +20.04,3.94, +20.56,5.01, +20.21,5.39, +18.69,4.81, +16.79,3.6, +16.68,2.94, +16.57,2.92, +17.31,3.05, +16.12,2.27, +9.96,4.87, +10.18,2.31, +10.04,1.25, +9.11,3.31, +7.46,2.28, +10.25,0.58, +11.91,0, +12.38,0, +11.84,0, +12.16,0, +12.54,0, +12.86,0.32, +13.35,0, +13.63,0, +13.83,0, +13.97,-0.3, +13.97,-0.27, +14,0, +14.72,0, +14.01,-0.96, +13.42,-2.37, +12.83,-2.26, +14.42,-1.37, +14.36,0, +13.85,1.62, +14.26,1.78, +13.4,3.44, +12.37,4.5, +13.25,3.16, +13.49,5.24, +12.06,6.36, +13.55,3.85, +12.08,-0.74, +10.56,-0.54, +12.77,-0.32, +11.93,-1.4, +11.36,-1.94, +8.03,-1.39, +8.27,1.69, +10.89,-2.21, +10.24,-4.56, +8.24,-2.88, +6.56,-5.48, +7.56,-3.79, +4.56,-8.68, +2.16,-10.22, +1.68,-8.4, +0.36,-9.17, +2.7,-6.48, +4.3,-5.63, +4.67,-7.18, +3.46,-9.5, +3.59,-9.86, +5.31,-8.34, +3.05,-7.93, +1.91,-8.42, +1.49,-8.45, +0.19,-8.56, +-0.49,-7.28, +-0.15,-5.04, +-0.63,-5.1, +-2.37,-5.12, +-1.83,-4.61, +-2.39,-4.16, +-4.25,-1.55, +-3.96,-0.94, +-3.42,-2.05, +-6.3,-1.54, +-6.57,-1.16, +-6,-1.41, +-3.31,-1.57, +-0.4,-2.51, +0.92,-4.7, +1.09,-4.9, +0.17,-4.98, +0.67,-4.32, +2.22,-4.14, +3.53,-2.8, +3.85,-1.71, +3.46,0.53, +5.84,1.84, +1.52,-0.78, +-4.42,-2.6, +-5.97,-3.62, +-5.47,-4.59, +-4.99,-4.19, +-4.62,-3.87, +-4.62,-3.87, +-4.62,-3.87, +-5.22,-3.01, +-4.47,-2.58, +-4.02,-1.62, +-3.9,-1.17, +-3.61,-1.88, +-3.17,-2.56, +-3.12,-3.54, +0.02,2.7, +0.62,2.07, +0.85,2.39, +0.07,1.8, +0.04,1.87, +-0.48,-0.03, +-0.91,1.48, +-0.05,1.26, +-0.31,1.06, +-0.64,1.23, +-0.33,1.23, +-0.4,0.09, +-0.47,0.7, +0.49,0.72, +0.71,1.61, +0.25,1.41, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.53,-4.38, +1.99,-4.11, +1.4,-3.84, +0.99,-3.93, +0.54,-4, +-0.02,-3.39, +-0.27,-2.9, +-0.44,-2.88, +-0.03,-2.76, +0.1,-2.18, +0.3,-1.66, +0.6,-0.85, +1.4,-0.58, +2.09,-0.16, +2.76,0.63, +3.01,0.93, +3.11,1.04, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +11.84,2.94, +17.52,3.09, +18.02,3.18, +18.66,3.68, +19.12,6.96, +19.63,7.14, +19.12,6.53, +16.93,6.16, +14.56,5.69, +13.31,5.73, +13.86,5.69, +14.95,4.92, +6.87,6.82, +6.79,6.86, +8.84,4.23, +9.07,2.83, +9.34,2.63, +7.18,2.73, +8.29,1.89, +9.86,0.17, +10.37,0.33, +9.5,-0.27, +10.53,-0.03, +11.5,1.36, +12.12,2.38, +12.83,-0.38, +13.82,-0.61, +13.35,0, +13.44,0, +14,0, +13.9,1.08, +13.89,1.22, +14.81,0, +13.66,-2.41, +13.23,-2.09, +14.04,-1.04, +14.64,0, +14,2.47, +13.75,3.04, +13.26,4.39, +12.42,5.49, +13.05,6.53, +14.38,6.08, +14.14,6.1, +15.31,5.63, +14.65,2.3, +12.85,1.96, +12.64,-0.26, +13.09,-0.47, +11.52,-2.65, +10.27,-2.69, +8.2,1.39, +8.69,1.25, +8.79,-5.4, +8.53,-3.91, +8.61,-3.32, +8.51,-2.03, +6.09,-8.91, +4.5,-10.44, +3.41,-8.73, +3.54,-8.89, +4.54,-5.97, +6.56,-3.89, +6.4,-4.93, +4.52,-8.32, +4.9,-8.86, +5.5,-7.7, +3.48,-6.96, +2.62,-7.77, +1.5,-8.49, +1.01,-8.16, +-0.2,-6.56, +-0.87,-5.95, +-1.44,-5.66, +-1.5,-6.15, +-2.52,-6.58, +-4.59,-5.33, +-5.33,-1.44, +-4.25,-1.36, +-4.39,-1.93, +-7.41,-0.84, +-7.47,-0.15, +-5.77,-0.4, +-0.8,-0.03, +0.48,-2.84, +0.5,-3.7, +0.16,-3.2, +-1.35,-3.49, +-0.31,-2.43, +1.8,-2.12, +3.17,-0.46, +5.43,1.51, +6.45,2.86, +3.23,0.32, +-1.75,-0.87, +-5.62,-2.32, +-5.95,-3.43, +-5.24,-4.39, +-5.04,-3.55, +-4.82,-3.59, +-4.62,-3.87, +-4.82,-3.59, +-5.22,-3.01, +-4.38,-2.53, +-3.93,-2.06, +-3.61,-1.88, +-3.54,-2.04, +-3.26,-2.43, +-2.96,-3.07, +0.01,2.12, +0.66,1.74, +0.68,2.09, +0.95,1.46, +1.23,1.38, +-0.4,-0.44, +0.61,0.91, +0.62,0.43, +1.16,-0.26, +-0.38,0.79, +-0.03,0.69, +0.94,1.09, +-0.93,0.3, +0.54,0.48, +1.67,0.9, +1.58,1.33, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.5,-4.33, +2.15,-3.89, +1.47,-3.81, +1.04,-3.8, +0.53,-3.13, +0.12,-3, +-0.26,-2.9, +-0.03,-2.71, +0.32,-2.02, +0.6,-1.7, +0.61,-1.03, +1.3,-0.53, +2.06,-0.09, +2.79,0.53, +3.37,1.13, +0.92,-0.39, +0.18,-0.45, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +14.49,5.27, +16.46,3.65, +17.8,3.14, +18.55,5.48, +19.27,7.01, +19.47,7.09, +18.95,6.9, +17.04,7.22, +14.27,7.53, +12.57,7.76, +11.78,8.8, +12.3,8.43, +6.68,8.59, +4.47,8.18, +2.05,6.36, +2.09,5.18, +6.63,4.34, +7.98,3.08, +8.29,1.81, +8.12,1.43, +8.51,1.24, +8.27,0.31, +9,0.54, +10.34,2.68, +10.8,4.23, +10.82,0.9, +12.05,-1.24, +12.96,-0.75, +12.81,0, +13.46,0, +13.85,1.62, +13.78,2.43, +14.73,0.27, +13.89,-1.22, +13.89,-1.22, +13.92,-0.91, +14.42,0.75, +13.81,2.11, +13.42,4.24, +12.95,4.71, +12.05,6.16, +12.82,7.4, +13.42,8.1, +12.81,7.04, +14.78,5.87, +13.63,3.61, +13.6,0.77, +13.56,0, +13.72,0, +14.4,-0.95, +12.87,-1.74, +10.44,-1.7, +8.81,0, +8.23,-2.74, +9.91,-3.84, +10.47,-3.84, +10.59,-2.06, +9.87,-7.75, +6.97,-9.31, +5.67,-8.6, +5.3,-7.94, +6.4,-4.83, +8.1,-2.95, +7.95,-3.08, +7.52,-5.31, +7.62,-9.08, +6.22,-6.65, +4.35,-5.7, +3.2,-7.12, +2.44,-8.07, +1.23,-7.98, +0.45,-8.49, +-1.38,-7.85, +-1.44,-8.17, +-1.37,-7.74, +-2.27,-7.47, +-3.28,-6.64, +-5.94,-3.06, +-5.36,-3.17, +-5.84,-3.14, +-7.41,-0.84, +-7.94,-0.31, +-6.57,0.13, +-0.39,2.08, +2.7,-2.81, +0.05,-2.08, +-0.32,-1.68, +-0.99,-3.03, +1.03,-1.49, +3.93,-0.78, +5.22,1.03, +7.42,2.86, +6.59,1.5, +-0.39,-0.92, +-4.46,-0.5, +-6.46,-1.43, +-5.75,-2.65, +-4.84,-3.55, +-5.22,-3.01, +-5.22,-3.01, +-5.12,-3.16, +-5.16,-3.1, +-4.98,-2.87, +-4.38,-2.53, +-4.02,-2.32, +-3.54,-2.04, +-3.54,-2.04, +-3.4,-2.24, +-3.13,-2.62, +-0.92,0.98, +0.57,1.42, +0.86,1.44, +0.68,0.95, +1.07,0.42, +-0.31,-1.14, +-0.91,0.6, +-1.35,0.32, +0.63,-0.04, +0.16,-0.62, +0.86,0.74, +0.45,-0.07, +-0.49,0.26, +1.53,0.76, +1.96,1.32, +1.25,0.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.53,-4.28, +2.07,-3.58, +1.6,-3.68, +1.14,-3.26, +0.65,-2.84, +0.35,-2.89, +0.17,-2.6, +0.34,-2.21, +0.76,-1.77, +1.25,-1.34, +1.53,-0.78, +2.04,-0.21, +2.85,0.46, +3.47,1.07, +1.85,-0.49, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +16.8,6.12, +17.08,4.76, +17.53,4.15, +18.42,6.71, +19.47,7.09, +19.73,7.18, +18.81,6.85, +16.36,9.44, +15.28,8.82, +13.02,9.43, +12.21,10.25, +11.53,10.24, +4.4,10.62, +-1.59,11.59, +-3.65,10.02, +0.17,8.46, +2.15,7.31, +2.78,6.51, +6.5,3.66, +7.33,2.61, +7.8,1.98, +8.19,1.18, +8.05,2.09, +8.24,4.29, +7.95,6.92, +8.17,4.87, +9.14,0.35, +11.06,-0.32, +10.53,0.22, +11.61,0.96, +13.25,2.34, +13.78,2.43, +14.21,1.24, +14.41,0, +14.83,-0.29, +15.08,-0.35, +14.81,0, +13.66,2.41, +13.46,4.04, +11.97,5.36, +10.8,6.23, +9.42,7.65, +11.67,9.34, +11.56,6.67, +13.27,4.83, +12.68,4.04, +12.79,0.03, +13.66,-2, +13.3,-0.54, +13.78,0, +13.47,-1.24, +10.83,-2.97, +10.65,-1.49, +9.52,-1.13, +7.09,-3.5, +7.4,-3.63, +10.08,-0.65, +9.85,-5.29, +8.25,-8.12, +7.92,-8.76, +7.59,-7.7, +8.46,-3.69, +8.58,-2.39, +8.76,-1.74, +7.83,-2.18, +7.43,-7.77, +7.44,-5.48, +4.93,-4.93, +3.48,-6.96, +2.6,-7.65, +1.74,-7.88, +0.75,-8.71, +-0,-8.21, +-0,-8.16, +-1.27,-8.67, +-1.52,-7.28, +-3.66,-7.29, +-5.79,-5.4, +-5.97,-5.26, +-6.21,-4.73, +-7.27,-2.83, +-7.88,-1.89, +-7.59,-0.69, +-3.51,0.92, +1.1,-0.86, +1.17,-1.3, +1.71,-0.72, +1.75,-1.27, +4.12,-0.09, +6.92,0.84, +7.95,1.24, +8.65,2, +2.68,-1.59, +-2.87,-1.7, +-6.16,-2.22, +-6.84,-1.41, +-5.91,-1.75, +-5.18,-2.65, +-5.11,-2.95, +-5.22,-3.01, +-5.22,-3.01, +-5.22,-3.01, +-4.8,-2.77, +-4.26,-2.46, +-3.59,-2.17, +-3.26,-2.43, +-3.23,-2.48, +-3.54,-2.04, +-3.33,-2.33, +-0.31,0.3, +0.67,1.44, +0.47,0.81, +0.29,0.73, +0.41,0.83, +-0.84,-0.76, +0.55,0.38, +0.38,0.77, +0.15,0.37, +0.86,0.02, +1.37,0.47, +-0.38,-0.82, +0.69,0.25, +0.31,1.55, +0.67,0.75, +1.07,0.88, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.54,-4.11, +2.07,-3.58, +1.46,-3.5, +1.02,-2.8, +0.82,-2.79, +0.47,-2.43, +0.4,-1.95, +0.71,-1.79, +1.26,-1.45, +1.78,-0.8, +2.35,-0.28, +2.7,0.26, +3.23,0.81, +3.82,1.13, +0.61,-0.45, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +16.99,6.18, +17.12,5.68, +17.09,5.8, +18.41,6.7, +19.5,7.1, +20.03,7.29, +18.6,8.16, +17.06,9.85, +16.26,9.39, +14.74,9.68, +13.11,10.54, +11.8,10.66, +6.74,10.62, +-0.3,11.03, +-1.83,10.27, +-0.53,10.38, +1.06,8.71, +2.24,7.06, +3.44,5.96, +4.89,5.2, +5.02,4.38, +4.03,4.12, +4.45,4.1, +6.63,6.65, +7.1,8.47, +6.73,7.46, +4.65,4.36, +5.34,3.85, +5.51,3.81, +7.55,4.24, +10.31,4.86, +13.88,3.13, +14.48,2.82, +14.4,0.95, +15.29,0, +14.6,0, +14.21,0, +13.68,2.06, +13.21,3.95, +11.22,4.92, +10.44,6.03, +9.99,7.63, +11.12,8.91, +10.86,6.27, +13.11,5.86, +13.22,3.92, +12.92,-1.4, +13.57,-2.39, +13.19,0, +13.85,1.62, +14.57,-0.83, +13.78,-2.43, +13.37,-2.36, +12.51,-3.65, +8.22,-4.05, +8.42,-2.21, +9.91,0.49, +7.71,-0.16, +6.59,-4.85, +9.4,-7.46, +10.21,-7.2, +8.84,-4.31, +9.06,-1.86, +9.48,-1.67, +9.06,-4.76, +8.81,-6.81, +8.27,-4.78, +5.31,-4.46, +4.13,-5.97, +2.91,-7.41, +1.61,-7.79, +0.95,-8.74, +-0,-8.51, +0.44,-8.76, +-0.44,-8.9, +-1.01,-8.14, +-4.05,-8.4, +-5.43,-6.87, +-5.99,-6.8, +-6.41,-6.56, +-7.15,-5.32, +-7.96,-4, +-7.83,-2.89, +-6.92,-2.18, +-3.76,-1.58, +-1,-1.43, +0.37,0.96, +3.5,1.38, +5.96,1.58, +7.98,1.65, +7.58,1.34, +4.34,-1.89, +-3.03,-4.05, +-4.53,-3.55, +-6.19,-3.22, +-6.63,-2.2, +-5.92,-1.52, +-5.1,-1.67, +-4.84,-2.51, +-5.37,-2.7, +-5.22,-3.01, +-5.22,-3.01, +-4.62,-2.67, +-3.71,-2.3, +-3.04,-2.71, +-2.71,-3.04, +-2.81,-2.94, +-3.06,-2.67, +-3.47,-2.14, +-0.22,0.65, +1.15,1.02, +0.85,0.57, +0.18,0.83, +0.69,0.67, +-0.69,-0.84, +-0.34,0.66, +0.09,0.21, +0.32,-0.1, +0.5,0.87, +0.25,0, +0.7,-1.07, +1.44,0.75, +2,0.22, +1.76,0.71, +1.67,0.97, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.54,-3.23, +2.15,-3.47, +1.26,-2.87, +1.1,-2.7, +1.08,-2.52, +0.75,-1.87, +0.91,-1.69, +1.33,-1.36, +1.77,-0.77, +2.21,-0.37, +3.2,0.39, +3.34,0.87, +2.76,0.5, +1.34,-0.17, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +14.86,5.41, +16.33,5.94, +16.36,5.95, +17.75,6.46, +19.12,6.96, +19.73,7.18, +18.84,9.08, +17.58,10.15, +16.87,9.74, +16.31,9.42, +15.63,9.02, +12.45,9.9, +5.99,10.05, +2.42,10.03, +0.57,10.06, +0.24,11.3, +-1.13,9.51, +1.37,8.06, +2.44,6.71, +2.73,6.42, +1.49,6.45, +-0.32,6.12, +1.67,7.1, +4.9,8.03, +7.36,8.77, +4.67,8.37, +3.01,6.82, +3.2,5.08, +3.86,5.3, +6.89,5.34, +9.95,5.23, +11.43,5.17, +14.73,3.89, +15.04,1.99, +15.04,0.24, +14.27,0, +13.51,0, +12.64,1.61, +12.57,4.04, +11.59,4.22, +9.9,6.52, +10.61,8.6, +11.53,8.59, +11.15,6.03, +12.55,5.51, +13.68,3.2, +12.97,-0.67, +12.99,-0.41, +12.61,0.52, +11.44,2.02, +12.18,-0.35, +12.76,-1.13, +14.07,-1.68, +11.93,-5.85, +8.51,-4.37, +9.47,-3.74, +9.24,-0.36, +7.25,3.98, +-2.93,-6.5, +4.97,-7.93, +7.27,-8.94, +8.39,-5.51, +9.08,-1.78, +9.2,-0.71, +7.85,-3.59, +9.09,-5.25, +8.82,-4.52, +5.42,-4, +4.3,-5.43, +2.69,-6.89, +2.43,-7.57, +1.48,-8.4, +1.21,-8.19, +0.46,-7.93, +-0,-9.21, +0,-8.69, +-3.15,-8.66, +-4.63,-7.63, +-5.23,-8.28, +-6.13,-7.8, +-6.44,-6.16, +-6.83,-5.73, +-7.31,-4.82, +-6.63,-4.09, +-6.13,-3.34, +-5.11,-2.15, +-5.29,0.43, +-1.47,2.32, +0.48,2.45, +-0.35,2.46, +-3.12,-0.39, +-4.78,-2.78, +-4.53,-3.28, +-5.33,-3.98, +-5.83,-3.83, +-5.97,-3.45, +-5.51,-2.38, +-5.36,-1.95, +-4.6,-2.37, +-5.22,-3.01, +-5.22,-3.01, +-5.08,-2.93, +-4.28,-2.47, +-3.6,-2.43, +-2.84,-2.91, +-2.21,-3.42, +-2.19,-3.43, +-2.26,-3.38, +-2.8,-3.15, +1.04,0.5, +1.06,-0.36, +0.64,0.18, +0.81,0.21, +0.66,0.35, +-0.85,-1.25, +-1.64,0.21, +1.32,-0.28, +0.67,-0.04, +0.56,0.31, +0.22,-0.12, +0.27,-1.1, +1.29,0.25, +1.53,0.52, +2.21,0.35, +1.56,0.64, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.62,-3.13, +2.48,-3.17, +1.58,-2.68, +1.69,-2.36, +1.7,-2.02, +1.39,-1.42, +1.61,-1.13, +2.08,-0.49, +2.68,0.05, +3.05,0.42, +3.55,0.98, +0.62,-0.61, +0.07,-0.32, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +10.62,3.86, +16.07,5.85, +16.01,5.83, +17.35,6.32, +19.08,6.94, +19.73,7.18, +18.69,10.79, +18.29,10.56, +17.62,10.17, +16.92,9.77, +15.73,9.08, +14.12,8.15, +7.13,9.57, +3.57,9.19, +1.79,10.15, +0.85,10.98, +-0.32,9.52, +2.78,7.75, +3.33,6.35, +3.13,6.23, +2.2,6.63, +2.91,6.24, +4.43,6.72, +6.43,7.66, +6.64,8.62, +4.68,8.88, +5.18,7.25, +5.08,5.69, +7.37,5.38, +9.76,4.34, +11.97,3.58, +13.42,4.42, +14.13,3.64, +14.21,2.51, +13.93,0.78, +14.36,0, +13.41,0, +13.17,1.11, +12.12,3.64, +11.75,4.92, +10.92,6.84, +11.34,9.52, +12.15,8.08, +11.99,5.49, +13,5.61, +13.41,3.19, +13.36,0.3, +13.22,0, +12.89,0, +10.6,2.22, +12.25,0.5, +13.13,0, +13.97,-2.75, +9.36,-7.85, +10.28,-3.87, +10.29,-3.94, +8.11,-3.92, +7.03,-1.69, +1.48,-7.17, +4.03,-9.25, +7.07,-8.92, +8.81,-6, +9.16,-2.38, +10.07,-2.04, +9.42,-3.7, +9.33,-5.14, +8.33,-4.25, +5.05,-5.03, +3.99,-6.11, +3.35,-6.56, +2.97,-7.64, +2.22,-8.13, +1.41,-7.97, +1.48,-8.38, +1.01,-8.69, +-0.02,-9.32, +-1.62,-9.2, +-4.62,-8.44, +-5.54,-9.6, +-5.93,-8.31, +-6.16,-7.35, +-5.75,-6.85, +-6.15,-6.64, +-6.12,-5.62, +-6.08,-4.9, +-6.06,-3.5, +-6.26,-2.67, +-5.83,-2.48, +-5.4,-1.56, +-5.97,-1.43, +-6.08,-2.42, +-5.74,-3.18, +-5.49,-3.6, +-5.8,-3.58, +-5.36,-4.5, +-5.36,-4.5, +-4.92,-3.44, +-5.28,-2.88, +-4.69,-2.71, +-4.72,-3.73, +-4.69,-3.77, +-4.19,-2.98, +-3.86,-2.7, +-3.2,-2.53, +-2.75,-3, +-2.11,-3.49, +-1.5,-3.79, +-1.05,-3.93, +-1.43,-4.07, +0.67,0.64, +0.97,-0.69, +1.11,-0.56, +-0.41,-0.21, +0.79,0.38, +-0.79,-2.04, +-1.47,-0.12, +0.46,-0.55, +0.21,-0.73, +0.79,-0.51, +1.4,-0.74, +1.57,0.04, +2.31,-0.02, +2,0.29, +2.38,0.92, +1.29,0.62, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.62,-3.13, +2.69,-3.07, +2.27,-2.57, +2.07,-2.12, +2.34,-1.72, +2.62,-1.09, +2.76,-0.4, +2.99,0.04, +3.58,0.7, +3.89,1.29, +3.2,0.49, +-0.2,-0.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +16.62,6.05, +16.01,5.83, +16.44,5.98, +17.64,6.42, +19.12,6.96, +20.13,7.33, +19.29,10.4, +18.6,10.74, +17.81,10.69, +15.81,10.51, +15.26,9.75, +13.2,8.64, +7.89,8.91, +5.1,8.84, +3.48,10.5, +3.4,10.05, +3.41,8.88, +4.65,6.39, +5.1,4.71, +4.15,4.74, +4.09,5.12, +5.09,4.54, +5.33,5.51, +6.32,6.73, +5.55,7.49, +3.76,8.38, +4.83,8.02, +5.74,6.82, +7.23,6.78, +11,3.23, +12.38,2.69, +12.65,2.5, +13.66,2, +14.9,2.63, +15.11,0.85, +14.4,0.97, +13.09,1.13, +13.48,1.78, +13.23,4.35, +13.59,6.72, +13.83,7.99, +12.84,8.54, +12.64,6.4, +13.91,5.06, +14.22,5.17, +13.97,2.44, +13.32,-0.27, +13.09,-1.05, +12.32,-0.52, +13.3,2.35, +13.37,2.36, +14.14,2.05, +13.38,-3.74, +11.34,-6.53, +11.53,-3.9, +10.19,-4.82, +7.37,-4.25, +4.61,-6.34, +4.6,-7.45, +6.37,-8.54, +8.72,-7.59, +10.87,-5.57, +12.23,-3.25, +12.81,-2.94, +11,-4.56, +10.05,-4.77, +7.48,-4.62, +5.12,-5.09, +4.44,-5.53, +4.55,-6.5, +3.83,-7.43, +2.46,-7.96, +1.41,-7.99, +1.36,-7.69, +1.41,-8.01, +0.83,-8.65, +-0.49,-9.45, +-2.12,-9.56, +-3.81,-9.76, +-5.46,-8.59, +-6.37,-7.59, +-6.37,-7.59, +-6.06,-7.22, +-5.59,-6.66, +-5.29,-5.94, +-5.97,-5.26, +-5.67,-5.56, +-4.71,-5.34, +-5.21,-4.6, +-5.62,-3.92, +-5.2,-3.46, +-4.92,-3.44, +-5.69,-3.81, +-5.78,-3.34, +-4.96,-3.94, +-4.62,-3.87, +-4.43,-4.06, +-4.53,-3.94, +-4.49,-3.77, +-4.62,-3.87, +-4.15,-4.34, +-3.68,-3.75, +-3.38,-2.83, +-3.13,-2.62, +-2.79,-2.96, +-1.95,-3.56, +-0.94,-3.96, +-0.71,-4.02, +-0.41,-4.05, +0.39,-1.03, +0.56,-0.36, +0.46,-1.28, +-0.56,-0.66, +0.98,0.18, +0.11,-2.01, +-0.72,-1.3, +-0.03,-0.19, +0.08,-0.46, +1.67,-0.37, +2.33,-0.17, +3.02,-0.86, +2.88,0.14, +3.01,-0.37, +2.65,0.64, +2.04,-0.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.96,-2.81, +3.05,-2.71, +3.06,-2.5, +2.4,-1.83, +2.69,-1.31, +3.65,-0.45, +3.6,0.33, +3.79,0.58, +3.22,0.37, +1.03,-0.38, +0.47,-0.47, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +15.89,5.78, +16.3,5.93, +16.69,6.82, +17.95,6.97, +18.97,6.9, +19.96,7.26, +19.26,9.2, +17.43,10.61, +16.12,11.19, +11.96,12.57, +10.94,11.93, +11.84,10.42, +9.03,9.41, +5,8.32, +5.57,8.79, +5.37,9.31, +4.75,8.23, +5.87,6.11, +7.8,3.21, +6.79,2.6, +6.79,3.24, +6.17,3.85, +6.63,4.8, +7.58,5.27, +6.27,5.21, +6.03,6.53, +6.23,7.76, +7.78,7.28, +10.35,5.71, +11.73,3.34, +12.01,2.12, +12.93,1.1, +13.71,0.77, +15.14,1.79, +14.85,1.3, +14.1,2.49, +13.36,2.36, +13.23,2.64, +15.03,6.48, +14.74,7.99, +14.31,8.26, +15.3,8.83, +13.23,5.77, +14.26,4.85, +15.15,5.21, +14.32,1.09, +14.77,-0.41, +15.21,-0.3, +12.36,-1.43, +14.22,3.01, +15.22,2.68, +14.42,2.54, +13.21,-0.86, +13.39,-4.5, +11.92,-4.05, +11.33,-4.37, +10,-4.14, +7.6,-6.11, +6.47,-6.78, +6.32,-5.8, +7.83,-6.15, +8.45,-4.88, +8.29,-3.59, +7.5,-3.67, +6.66,-4.61, +6.74,-5.36, +5.43,-6.36, +5.88,-5.16, +5.02,-5.98, +4.55,-6.34, +3.71,-7.12, +3.25,-7.05, +1.32,-7.49, +0.63,-7.19, +0.71,-7.37, +0.64,-7.59, +-0,-8.78, +0.71,-8.76, +-1.06,-8.87, +-5.9,-7.92, +-6.06,-7.22, +-5.75,-6.85, +-4.65,-7.62, +-4.84,-7.49, +-4.79,-7.22, +-4.98,-6.2, +-5.44,-6.48, +-4.91,-5.86, +-4.21,-5.43, +-4.5,-5.36, +-5.09,-4.54, +-4.99,-4.19, +-5.59,-4.17, +-5.97,-3.45, +-4.82,-3.59, +-4.21,-4.09, +-3.72,-4.43, +-3.98,-4.51, +-4.51,-3.98, +-4.06,-4.43, +-3.61,-4.3, +-3.25,-3.87, +-2.89,-3.32, +-2.79,-2.96, +-2.62,-3.13, +-1.76,-3.66, +-0.79,-4, +-0.41,-4.05, +-0,-3.89, +0.49,-1.81, +0.89,-0.72, +0.66,-1.73, +-0.08,-1.53, +0.8,-0.45, +0.9,-1.13, +1.13,-1.58, +-0.49,-0.04, +0.24,0.83, +0,-0.25, +2.25,-0.08, +1.81,-0.28, +2.6,-0.25, +2.97,-0.82, +3.77,0.09, +2.35,0.29, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.29,-2.63, +3.38,-2.43, +3.57,-1.92, +3.57,-1.55, +3.78,-0.73, +4.13,0, +4.43,0.95, +4.56,1.46, +1.68,-0.39, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +7.06,2.57, +16.01,5.83, +16.1,7.29, +15.66,9.04, +15.94,9.2, +16.63,8.73, +17.26,8.22, +15.88,9.69, +13.83,11.12, +11.71,12.36, +8.43,13.68, +7.29,13.36, +7.55,12.46, +6.45,10.99, +4.12,9.35, +7.71,8.68, +7.48,8.58, +6.07,7.83, +6.39,6.94, +8.74,2.92, +9.71,1.71, +13.49,1.35, +12.42,1.54, +11.2,3.18, +11.62,4.19, +12.35,4.33, +11.76,4.53, +8.22,5.99, +8.34,7.58, +8.77,7.75, +9.18,4.56, +10.06,1.77, +11.96,0.1, +14.55,0, +15.62,0, +14,2.47, +14.53,2.56, +13.61,4.95, +13.76,5.01, +14.9,7.56, +14.15,7.8, +15.19,8.2, +15.64,8.46, +14.52,5.29, +14.94,3.51, +14.63,3.02, +14.67,-0.2, +13.62,-1.83, +15.52,-0.69, +14.16,0, +16.42,3.64, +16.72,3.63, +15.83,2.79, +12.42,0.37, +10.28,-3.65, +10.15,-4.13, +10.43,-5.65, +9.71,-5.6, +9.91,-5.92, +8.66,-5.48, +5.74,-4.52, +6.31,-5.04, +8.1,-4.68, +7.51,-4.56, +5.78,-5.45, +4.37,-6.42, +3.79,-7.32, +4.61,-7.98, +5.55,-4.65, +4.65,-5.55, +4.56,-5.91, +3.01,-7, +1.61,-7.79, +0.86,-7.65, +0.14,-6.88, +-0,-6.86, +-0.43,-7.61, +-0.6,-7.81, +1.9,-7.53, +-0.17,-6.08, +-5.48,-6.73, +-5.99,-4.73, +-5.5,-6.19, +-4.47,-7.74, +-4.12,-7.14, +-4.28,-7.73, +-4.65,-7.62, +-5.25,-6.61, +-5.12,-6.11, +-5.03,-6, +-4.94,-5.89, +-5.07,-5.81, +-5.45,-5.17, +-4.79,-5.07, +-5.43,-3.84, +-4.73,-3.52, +-3.75,-3.53, +-2.97,-4.26, +-3.3,-5.02, +-4.02,-4.47, +-3.47,-4.31, +-2.69,-4.66, +-2.53,-4.38, +-2.1,-3.63, +-2.11,-3.48, +-2.24,-3.4, +-1.67,-3.71, +-0.94,-3.96, +-0.41,-4.05, +0.2,-3.68, +0.72,-2.11, +0.65,-1.21, +0.73,-1.88, +1.59,-1.84, +0.71,-1.33, +1.14,-1.26, +0.34,-1.54, +0.51,-1.77, +0.6,-0.22, +1.5,0.18, +3.11,-0.1, +2.82,-0.64, +3.33,-0.73, +3.27,-0.07, +4.03,-0.31, +2.71,-0.91, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.42,-2.31, +4.21,-2.03, +4.4,-1.45, +4.53,-0.88, +4.52,-0.3, +4.73,0.58, +4.84,1.45, +1.35,-0.54, +-0.04,-0.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +7.53,1.33, +13.84,4.14, +16.17,5.88, +16.69,6.82, +15.79,8.75, +15.66,9.04, +15.28,8.82, +14.89,8.6, +13.56,9.88, +11.58,11.56, +9.08,13.62, +7.94,14.41, +5.53,15.21, +5.28,14.11, +2.12,10.31, +3.98,10.88, +7.08,9.64, +8.86,7.43, +8.65,6.9, +8.65,6.4, +9.11,4.41, +11.42,1.66, +11.41,1.77, +11.85,1.17, +13.34,0.87, +13.3,2.35, +11.92,2.86, +9.77,2.13, +9.16,4.12, +7.53,7.53, +6.67,9.99, +6.1,8.54, +6.95,4.49, +9.2,0.68, +13.21,2.06, +14.57,2.29, +14.47,2.55, +14.64,2.96, +13.76,5.01, +14.17,5.16, +15.41,6.13, +14.85,8.01, +15.71,5.72, +15.93,5.47, +15.86,2.8, +15.86,2.8, +14.67,2.34, +13.9,-0.62, +13.85,-1.62, +16,1.26, +17.62,1.82, +19.42,3.42, +18.06,3.18, +14.76,2.32, +10.98,-0.94, +9.01,-4.15, +9.51,-5.14, +10.78,-5.01, +10.83,-4.42, +9.97,-4.89, +6.4,-4.7, +5.77,-4.34, +6.74,-4.78, +8.57,-4.41, +7.84,-5.3, +5.32,-6.85, +4.17,-7.62, +3.76,-8.07, +4.68,-7.59, +4.7,-4.09, +4.22,-5, +3.93,-5.76, +2.3,-7.46, +0.69,-7.26, +-0,-7.41, +-0.2,-6.82, +-0.13,-6.01, +-1.57,-6.07, +-1.58,-6.63, +1.68,-6.73, +-3.1,-5.33, +-5.66,-4.4, +-6.6,-2.59, +-5.74,-4.62, +-4.83,-6.02, +-3.93,-6.5, +-3.78,-7.53, +-4.07,-7.93, +-3.9,-7.37, +-4.26,-6.91, +-4.74,-6.37, +-5.12,-6.11, +-5.12,-6.11, +-5.12,-6.11, +-5.1,-5.45, +-4.83,-4.05, +-4.24,-3.56, +-3.48,-3.64, +-2.53,-4.38, +-3.01,-5.22, +-3.14,-5.13, +-2.88,-4.28, +-2.53,-4.38, +-1.99,-4.63, +-1.62,-4.44, +-1.4,-3.84, +-1.48,-3.78, +-1.14,-3.9, +-0.94,-3.96, +-0.53,-4.04, +0.18,-4.07, +0.89,-2.6, +0.74,-2.04, +1.11,-1.88, +1.1,-1.87, +1.97,-1.29, +-0.04,-1.82, +0.56,-1.71, +2.36,-1.84, +2.33,-1.96, +2.02,0.19, +2.77,-0.15, +3.08,-0.25, +3.28,-0.53, +3.87,-0.2, +4.19,-0.62, +2.71,-1.36, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.86,-1.75, +5.39,-1.37, +5.53,-0.65, +5.24,0.25, +4.59,0.41, +2.61,-0.33, +-0.07,-0.08, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +9.06,1.6, +17.92,3.16, +17.47,5.62, +17.56,6.39, +17.44,6.35, +17.23,7.1, +16.44,8.64, +15.53,8.97, +14.06,8.57, +12.21,10.25, +10.83,12.04, +9.96,13.62, +8.75,15.15, +8.21,15.69, +6.55,14.04, +4.72,11.31, +5.32,11.3, +6.19,10.71, +9.21,7.45, +10.23,5.9, +9.88,5.7, +10.11,4.45, +11.87,2.09, +12.35,2.18, +11.39,2.01, +11.27,1.99, +11.96,2.11, +12.16,2.41, +12.7,0, +9.11,3.09, +8.5,6.39, +7.6,8.59, +5.87,10.87, +4.07,10.06, +6.1,5.89, +11.67,4.25, +13.15,4.79, +13.58,3.65, +14.31,3.84, +15.11,5.5, +15.81,5.76, +15.93,5.8, +16.13,5.87, +15.13,5.51, +14.75,4.42, +15.06,2.66, +14.32,2.52, +14.01,1.65, +12,-1.73, +13.96,0.07, +16.87,2.71, +14.93,2.63, +15.12,2.67, +13.25,1.37, +11.66,0, +12.73,-1.48, +13.21,-2.71, +12.11,-4.12, +11.73,-4.27, +11.59,-4.22, +7.4,-4.86, +6.65,-4.91, +6.84,-3.95, +8.89,-3.24, +8.16,-3.65, +7.29,-5.46, +6.25,-6.51, +4.82,-7.31, +5.27,-7.48, +4.65,-6.13, +2.95,-3.84, +1.76,-5.25, +1.5,-6.59, +0.35,-7.2, +-1.75,-7.42, +-1.8,-6.73, +-2.12,-6.22, +-1.57,-5.92, +-1.92,-5.7, +-2.34,-7.02, +-0.15,-6.48, +-3.87,-6.27, +-6.71,-3.84, +-7.85,-1.38, +-6.63,-3.27, +-6.1,-3.92, +-5.45,-4.31, +-4.86,-6.24, +-3.75,-7.22, +-2.92,-7.53, +-3.68,-7.66, +-3.99,-6.9, +-4.24,-6.73, +-5.44,-6.48, +-5.12,-6.11, +-4.65,-5.55, +-3.97,-4.52, +-3.56,-3.56, +-2.94,-3.5, +-2.6,-4.22, +-2.08,-5.35, +-2.38,-5.16, +-2.53,-4.38, +-2.53,-4.38, +-1.73,-4.75, +-1.56,-4.29, +-1.28,-3.87, +-0.52,-4.04, +-0.35,-4.05, +-0.71,-4.02, +-0.71,-4.02, +-0,-4.06, +1.74,-2.78, +2.38,-2.57, +1.71,-2.04, +2.18,-2.11, +2,-1.49, +1.2,-2.24, +2.24,-1.58, +2.26,-1.9, +1.38,-1.37, +2.52,0.04, +2.6,-0.19, +3.76,-0.81, +4.03,-0.09, +4.04,-0.09, +3.42,-2.17, +2.81,-1.5, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.84,-1.43, +6.24,-1.06, +6.13,-0.44, +4.73,-0.06, +0.55,-0.73, +-0.07,-0.08, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.01,0, +16.07,2.48, +18.76,3.31, +18.36,6.06, +18.71,6.81, +18.71,6.81, +18.68,6.8, +18.05,6.57, +16.74,7.18, +14.6,8.05, +13.47,9.17, +12.63,11.24, +12.45,12.95, +11.59,14.27, +11.38,15.49, +10.24,14.75, +7.53,11.88, +7.79,11.3, +7.79,11.4, +7.85,9.4, +10.76,6.5, +11.04,5.5, +11.47,4.47, +12.35,2.18, +12.75,2.54, +12.19,2.15, +10.58,1.87, +11.15,2.29, +11.9,2.9, +10.51,1.69, +9.93,2.34, +9.52,5.81, +7.72,8.2, +6.38,10.58, +6.36,11.66, +6.72,10.94, +10.68,8.99, +13.2,6.98, +13.86,5.89, +14.52,5.29, +15.46,5.63, +16.33,5.23, +14.86,5.41, +14.98,5.45, +14.37,5.23, +14.52,4.65, +14.1,2.49, +13.46,2.37, +13.11,1.76, +12.18,-0.95, +12.31,-0.03, +12.92,1.16, +15.39,1.89, +13.23,1.68, +12.83,2.26, +11.9,1.74, +10.36,-1.34, +10.52,-2.29, +11.33,-4.12, +11.48,-4.18, +9.66,-3.88, +9.89,-4.9, +9.05,-4.49, +9.1,-2.46, +7.91,-2.08, +8.21,-4.09, +8.63,-5.28, +7.72,-5.69, +6.88,-6.27, +4.77,-7.49, +2.14,-6.98, +-0,-3.98, +-1.5,-5.47, +-2.66,-6.67, +-3.86,-7.09, +-5.1,-6.54, +-5.81,-5.64, +-5.07,-4.89, +-3.96,-5.3, +-2.22,-5.59, +-2.58,-6.38, +-3.78,-6.43, +-7.09,-4.72, +-8.49,-3.85, +-8.58,-2.8, +-9.24,-3.06, +-9.37,-3.18, +-9.2,-3.35, +-6.75,-4.72, +-5.32,-5.85, +-4.87,-6.88, +-4.77,-6.88, +-4.49,-6.55, +-4.26,-6.91, +-4.68,-6.61, +-4.11,-6.08, +-3.81,-5.66, +-3.21,-4.89, +-2.76,-4.89, +-2.61,-4.7, +-2.98,-4.06, +-2.41,-4.43, +-2.39,-4.44, +-2.47,-4.28, +-2.2,-3.82, +-2.31,-4.21, +-1.4,-3.84, +-1.17,-3.9, +-0.24,-4.06, +-0,-4.08, +-0.81,-3.99, +-0.79,-4, +-0.5,-4, +2.34,-2.77, +3,-2.65, +1.84,-2.43, +2.69,-2.33, +1.98,-2.62, +2.68,-2.06, +2.67,-1.77, +2.56,-0.91, +2.21,-1.24, +2.41,-0.71, +3.02,-0.36, +3.02,-0.58, +3.69,-0.06, +3.77,-0.24, +2.83,-2.01, +2.15,-1.65, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.09,-0.98, +6.63,-0.57, +5.45,-0.99, +1.3,-0.96, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +15.02,0.52, +19,3.35, +19.4,3.42, +18.61,6.77, +19.42,7.07, +20,5.09, +19.42,5.42, +18.92,6.04, +18.21,6.63, +16.76,6.55, +16.62,8.7, +16.38,10.26, +16.33,10.49, +15.42,12.19, +15.67,13.65, +15.56,14.62, +10.18,10.9, +10.32,10.57, +9.72,10, +9.06,10.8, +11.51,7.58, +13,4.73, +12.92,4.41, +12.89,2.25, +14.14,2.47, +12.03,2.12, +10.31,2.66, +10.47,3.81, +10.36,3.77, +10.05,3.66, +10.37,3.77, +11.04,4.47, +11.45,5.49, +9.76,8.16, +9.89,9.85, +10.03,10.6, +11.47,10.59, +13.54,9.48, +14.54,8.39, +14.18,6.14, +15.24,5.55, +15.28,5.56, +15.48,5.63, +15.08,5.49, +14.75,5.37, +14.07,5.12, +13.78,2.43, +13.57,2.39, +13.78,2.43, +11.93,1.4, +12.8,1.77, +15.19,4.15, +13.67,0.82, +14.68,1.82, +13.57,2.39, +12.16,2.14, +9.99,-1.01, +10.17,-1.47, +9.66,-3.51, +11.33,-4.33, +11.85,-4.31, +11.78,-4.29, +9.6,-5.07, +7.67,-2.96, +6.89,-1.86, +7.48,-3.58, +9.55,-5.51, +8.95,-5.37, +7.69,-6.6, +4.38,-7.55, +0.64,-6.84, +-3.56,-3.29, +-5.04,-3.55, +-6.1,-4.29, +-7.11,-4.59, +-8.06,-4.59, +-8.42,-3.89, +-7.5,-3.47, +-5.77,-3.05, +-4.17,-4.3, +-3.39,-5.07, +-2.83,-5.09, +-8.02,-3.83, +-9.03,-3.06, +-8.98,-2.17, +-9.72,-2.01, +-9.57,-2.54, +-9.11,-3.32, +-8.18,-4.35, +-6.85,-5.75, +-5.59,-6.66, +-5.36,-6.38, +-4.86,-5.79, +-4.3,-6.01, +-3.45,-5.97, +-3.15,-5.46, +-2.61,-4.52, +-1.89,-5.21, +-1.8,-4.95, +-2.24,-4.46, +-3.01,-4.04, +-2.53,-4.38, +-2.37,-4.45, +-2.13,-4.56, +-2.12,-3.68, +-2.04,-3.54, +-1.91,-3.6, +-1.17,-3.9, +-0.24,-4.06, +-0,-4.08, +-0.16,-4.07, +-0.59,-4.03, +-0.71,-4.02, +2.91,-2.77, +3.16,-3.54, +3.18,-2.51, +3.65,-2.64, +3.09,-2.41, +2.62,-2.24, +2.8,-1.32, +2.37,-1.17, +2.38,-0.86, +2.75,-0.72, +3.17,-0.43, +4.01,-1.51, +3.87,-0.42, +3.6,-0.94, +2.4,-1.26, +1.62,-1.62, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.32,-1.82, +1.45,-1.36, +-0.23,-0.29, +-0.04,-0.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.28,0.73, +17.19,2.14, +18.76,3.31, +19.72,3.48, +19.73,6.75, +19.82,6.83, +20.68,3.65, +20.2,3.56, +19.72,3.48, +19.32,4.96, +19.27,7.01, +19.73,7.18, +20.25,7.37, +20.09,8.18, +19.27,9.68, +18.74,10.82, +15.81,10.36, +11.48,8.29, +12.64,8.32, +13.27,8.73, +11.34,9.52, +11.44,8.65, +13.4,5.41, +13.59,4.21, +13.26,3.13, +14.45,2.25, +12.08,2.13, +12.41,2.57, +12.39,3.28, +13.06,3.89, +12.92,3.57, +12.93,3.99, +12.7,4.62, +13.04,4.75, +13.13,4.47, +12.89,3.93, +12.46,6.45, +12.47,8.82, +13.05,10.23, +13.12,9.43, +14.06,7.05, +14.84,5.75, +15.07,5.77, +15.63,5.69, +15.28,5.56, +14.2,5.17, +14.37,5.23, +14.22,4.56, +14.33,2.53, +13.51,2.38, +13.63,3.93, +16.06,5.54, +12.76,1.19, +12.97,0.65, +15,2.64, +14.38,2.54, +13.15,2.32, +10.95,0.07, +10.24,-0.52, +8.74,-2.74, +9.01,-3.28, +11.89,-3.87, +11.03,-4.45, +11.06,-6.06, +9.74,-3.85, +8.05,-2.52, +8.62,-3.06, +8.43,-5.02, +7.64,-6.41, +5.24,-6.56, +4.04,-8.42, +1.54,-8.74, +-4.22,-1.94, +-6.11,-2.63, +-6.99,-3.03, +-8.22,-3.17, +-9.35,-3.4, +-9.84,-2.93, +-9.03,-1.75, +-7.85,-1.38, +-6.43,-1.54, +-4.82,-3.89, +-6.73,-3.34, +-8.72,-2.33, +-9.52,-1.68, +-9.13,-1.61, +-9.68,-1.71, +-9.76,-1.72, +-9.54,-2.56, +-8.48,-3.46, +-6.44,-5.4, +-5.65,-5.77, +-5.12,-6.11, +-5.02,-5.98, +-4.46,-5.49, +-3.57,-5.84, +-3.01,-5.22, +-2.53,-4.38, +-2.29,-4.49, +-1.93,-4.66, +-2.38,-4.42, +-2.73,-4.43, +-2.58,-4.71, +-1.81,-4.98, +-1.73,-4.75, +-1.84,-4.7, +-2.1,-4.22, +-2.1,-3.64, +-1.38,-3.8, +-0,-4.08, +-0.09,-4.07, +0.53,-4.04, +0.47,-4.04, +1.25,-3.75, +3.43,-2.54, +3.65,-3.4, +3.67,-2.62, +3.31,-2.31, +3.08,-2.3, +3.17,-2.22, +3.24,-1.3, +2.52,-1.8, +2.21,-0.84, +2.47,-0.63, +2.92,-0.92, +4.06,-1.56, +4.25,-0.72, +3.32,-0.79, +1.95,-1.82, +1.17,-1.78, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.49,0.62, +14.14,2.49, +17.64,3.11, +19.19,3.38, +19.21,4.54, +18.81,6.85, +19.42,5.22, +19.91,3.51, +20.57,3.63, +20.2,3.56, +20.4,5.21, +21.05,7.66, +21.47,7.82, +20.07,7.3, +19.88,7.24, +19.84,7.22, +19.29,7.86, +12.01,8.37, +9.9,7.36, +11.43,6.2, +11.47,6.62, +11.54,7.82, +10.58,8.35, +10.94,6.72, +12.12,4.33, +12.76,3.63, +12.06,2.43, +12.31,3.11, +13.03,3.59, +13.06,3.07, +13.22,3.92, +13.36,4, +14.48,4, +13.56,4.93, +13.8,5.02, +13.56,4.5, +14.62,2.58, +14.32,1.9, +13.41,4.54, +13.56,6.54, +12.03,8.82, +12.06,8.77, +13.29,7.62, +15.89,5.78, +15.5,5.64, +15.59,5.67, +14.98,5.45, +14.98,5.45, +14.07,5.12, +13.88,3.33, +14.72,4.38, +15.71,5.72, +13.39,2.54, +11.17,0, +11.25,0.5, +13.15,2.32, +13.46,2.37, +13.31,1.62, +11.4,0, +10.54,-0.44, +9.17,-1.9, +7.31,-3.98, +9.91,-4.86, +7.06,-6.19, +8.29,-6.05, +7.03,-4.85, +7.77,-3.95, +4.74,-4.72, +2.25,-5.02, +3.71,-7.34, +0.99,-7.24, +-0.46,-8.37, +0.52,-9.06, +-7.48,-1.7, +-7.17,-2.21, +-8.06,-1.65, +-9.13,-1.61, +-10.26,-1.81, +-10.73,-2.09, +-9.95,-1.76, +-8.34,-1.27, +-6.35,1.75, +-7.61,-0.83, +-8.64,-0.04, +-9.01,-1.04, +-9.85,-0.74, +-9.86,-0.57, +-10.06,-0.57, +-9.81,-1.15, +-9.22,-1.63, +-8.14,-2.78, +-6.12,-4.54, +-5.12,-6.11, +-5.12,-6.11, +-4.91,-5.86, +-4.04,-5.09, +-3.01,-5.22, +-2.53,-4.38, +-2.37,-4.1, +-2.37,-4.1, +-2.53,-4.38, +-2.23,-4.52, +-2.16,-4.79, +-2.06,-5.66, +-2.06,-5.66, +-1.48,-4.93, +-1.52,-4.81, +-1.73,-4.75, +-1.83,-3.99, +-1.67,-3.68, +0.27,-4.06, +0.59,-4.03, +0.74,-4.18, +1.05,-3.92, +2.01,-2.62, +3.27,-1.19, +3.63,-2.64, +3.65,-3.17, +3.43,-2.64, +3.08,-2.24, +3.1,-1.89, +2.5,-2.34, +2.18,-2.38, +2.12,-1.11, +1.74,-0.64, +4.08,-1.13, +4.04,-0.94, +3.66,-0.94, +2.95,-1.42, +2.28,-2.09, +1.38,-2.13, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.66,0, +15.8,2.79, +17.16,3.03, +18.16,3.2, +18.76,3.31, +19.36,3.41, +19.4,3.81, +18.91,5.65, +19.56,3.45, +19.56,3.45, +20.25,3.57, +20.75,6.26, +22.1,8.04, +21.95,7.99, +19.73,7.18, +18.45,6.71, +18.16,6.61, +18.05,6.57, +7.84,7.12, +8.23,8.64, +8.43,5.91, +7.35,5.29, +7.01,7.81, +6.93,7.76, +7.37,6.64, +8.27,5.37, +10.87,3.96, +10.61,3.86, +10.92,3.97, +11.06,4.03, +11.46,4.17, +11.54,4.94, +12.04,5.57, +12.59,6.77, +14.39,6.71, +14.04,6.88, +13.92,5.31, +13.59,2.4, +14.97,0, +14.5,2.16, +14.26,3.96, +13.29,6.2, +12.99,7.03, +13.07,7.54, +14.78,5.65, +16.05,5.84, +15.13,5.51, +14.83,5.4, +15.4,5.61, +16.36,5.95, +15.13,5.51, +14.7,5.35, +12.21,3.63, +10.98,1.6, +10.2,-0.49, +9.59,0, +10.5,1.61, +12.61,2.22, +12.47,0.78, +11.47,0.73, +8.48,-1.54, +6.2,-5.06, +-0.03,-7.94, +0.49,-8.11, +1.55,-7.89, +1.52,-7.77, +-1.9,-6.11, +-2.55,-5.68, +-3.15,-5.42, +-4.15,-5.25, +-1.05,-7.04, +-3.35,-6.67, +-3.57,-7.1, +-4.86,-4.89, +-9,-0.78, +-8.35,-1.24, +-8.81,-1.55, +-9.8,-1.34, +-10.72,-1.89, +-11.71,-2.06, +-10.91,-1.6, +-9.8,-0.24, +-9.04,0.45, +-9.07,0.98, +-8.94,0, +-9.51,0.43, +-9.88,0.43, +-10.08,0.29, +-10.79,0, +-9.91,0, +-8.86,-0.97, +-7.61,-2.28, +-5.24,-4.39, +-4.97,-5.5, +-4.37,-5.84, +-3.67,-5.94, +-3.34,-5.38, +-3.01,-5.22, +-2.7,-4.68, +-2.37,-4.1, +-2.04,-3.54, +-2.34,-4.31, +-1.86,-4.58, +-1.63,-5.35, +-1.55,-5.8, +-1.77,-5.74, +-1.05,-5.27, +-0.88,-4.98, +-1.44,-4.83, +-1.59,-4.36, +-1.5,-3.89, +0.79,-3.97, +1.34,-4.02, +1.68,-4.62, +1.83,-3.83, +1.97,-2.54, +2.94,-0.75, +3.86,-1.65, +4.24,-3.7, +4.07,-2.69, +2.86,-2.21, +2.81,-2.38, +2.81,-2.95, +3.23,-2.1, +3.08,-2.01, +2.98,-1.52, +3.61,-0.74, +3.46,-1.6, +2.34,-1.53, +2.96,-2.53, +1.79,-1.9, +1.66,-1.42, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +13.55,2.1, +16.34,2.88, +17.91,3.16, +18.87,3.33, +19.08,3.36, +18.76,3.31, +18.92,3.34, +18.02,6.56, +18,6.16, +18.78,4.47, +20.19,6.08, +21.68,7.89, +20.82,9.51, +15.13,11.05, +11.64,11.43, +9.88,8.29, +6.68,6.34, +9.67,6.58, +1.52,6.94, +-0.39,8.62, +-0.07,6.55, +-2.45,5.45, +-0.9,7.33, +-0.78,8.87, +0.88,8.35, +2.84,7.87, +5.08,5.95, +6.69,4.83, +8.72,4.37, +8.26,5.17, +9,5.86, +8.44,6.04, +8.79,6.92, +11.39,9.24, +11.24,10.47, +11.08,10.3, +11.31,8.43, +10.68,4.42, +12.36,2.16, +13.69,2.08, +14.79,3.05, +14.07,5.12, +12.95,4.71, +14.03,5.45, +15.92,5.8, +15.66,5.7, +15.86,5.77, +15.08,5.49, +14.37,5.23, +14.25,5.19, +13.15,4.79, +12.75,4.64, +11.62,3.01, +10.79,0.67, +8.18,-1.63, +6.32,-1.75, +7.83,1.91, +10.07,2.07, +11.19,0.27, +9.84,-0.91, +3.75,-6.61, +-2.63,-7.98, +-3.42,-8.05, +-3.4,-8.24, +-4.15,-7.18, +-2.45,-8.23, +-3.75,-6.61, +-3.85,-6.7, +-4.45,-5.79, +-4.6,-5.48, +-3.9,-6.41, +-4.65,-5.59, +-5.27,-5.2, +-6.64,-3.47, +-9.59,0, +-9.46,-0.47, +-9.9,-0.38, +-10.75,-0.48, +-11.28,-0.87, +-11.46,-2.02, +-11.27,-1.53, +-10.75,0, +-10.28,0.29, +-9.73,1.28, +-9.63,1.02, +-9.65,1.48, +-9.6,1.69, +-9.79,1.43, +-10.33,0.54, +-9.54,0.56, +-8.6,-0.23, +-7.35,-1.88, +-4.62,-4.04, +-4.23,-5.25, +-3.3,-5.46, +-3.5,-6.06, +-3.02,-6.15, +-2.66,-5.39, +-2.24,-5.22, +-1.87,-4.55, +-1.64,-3.72, +-1.65,-4.52, +-1.48,-4.48, +-1.01,-5.19, +-1.11,-6.29, +-1.07,-6.05, +-0.79,-5.31, +-0.44,-5.02, +-0.98,-4.95, +-1.62,-4.78, +-1.67,-4.6, +0.65,-4.05, +2.14,-4.41, +2.42,-4.41, +2.8,-3.93, +3.22,-2.53, +3.36,-1.19, +3.99,-1.31, +3.92,-3.36, +3.69,-3.24, +4.08,-2.53, +2.58,-2.32, +3.11,-2.31, +3.27,-1.96, +2.07,-2.69, +3.05,-2.02, +4.35,-1.1, +1.5,-3.24, +1.79,-1.64, +2.11,-2.36, +2.73,-2.12, +1.34,-1.01, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.21,0.74, +15.82,2.79, +17.23,3.04, +18.87,3.33, +19.56,3.45, +19.58,3.45, +18.97,3.35, +18.76,3.31, +18.02,6.06, +17.9,6.52, +19.02,6.92, +21.05,7.66, +22.76,8.29, +18.1,12.68, +10.32,12.3, +8.49,11.86, +1.75,9.29, +-4.6,7.45, +-4.71,8.16, +-4.58,7.93, +-4.22,8.34, +-4.29,8.9, +-4.81,7, +-3.42,7.88, +-1.8,7.84, +-0.62,8.33, +0,8.46, +2.13,6.93, +3.92,6.79, +5.66,5.94, +5.72,6.42, +6.42,6.4, +5.46,7.97, +6.18,9.38, +8.87,11.15, +9.66,13.84, +9.56,14.11, +10.35,12.15, +9.79,8.06, +10.89,4.68, +11.75,3.61, +13.03,3.59, +13.49,4.91, +14.07,5.12, +13.61,4.95, +14.37,5.23, +16.58,6.04, +17.72,6.45, +17.72,6.45, +15.29,5.56, +14.9,5.42, +12.63,4.6, +11.61,4.66, +11.83,3.75, +7.64,-1.01, +0.99,-4.07, +-0.76,-0.44, +6.88,2.22, +11.14,1.96, +11.4,-0.24, +8.76,-4.33, +-0.68,-9.16, +-3.91,-8.41, +-3.54,-7.82, +-2.39,-7.91, +-2.28,-8.44, +-1.65,-8.34, +-2.07,-7.86, +-2.95,-8.1, +-3.99,-7.43, +-3.99,-6.56, +-4.91,-5.86, +-5.04,-4.82, +-6.4,-4.07, +-7.45,-2.91, +-9.91,0, +-10.06,0, +-11.05,-0.32, +-11.02,-0.64, +-11.63,-1.13, +-11.29,-1.99, +-11.55,-2.04, +-10.41,-0.96, +-9.94,0, +-9.81,0, +-10.16,1.01, +-9.79,1.73, +-9.76,1.72, +-9.76,1.72, +-9.95,1.76, +-8.81,1.55, +-8.25,0.52, +-6.16,-1.57, +-3.93,-3.33, +-3.28,-4.64, +-2.85,-5.29, +-2.39,-6.29, +-2.39,-6.58, +-2.17,-5.97, +-2.06,-5.66, +-1.73,-4.75, +-1.47,-4.04, +-1.48,-4.48, +-0.82,-4.66, +-0.2,-4.79, +-0.33,-6.39, +-0.55,-6.3, +-0.17,-5.36, +-0,-5.05, +-0.35,-5.02, +-1.3,-4.86, +-1.73,-4.75, +0.59,-4.68, +3.04,-4.12, +3.14,-3.75, +3.46,-3.44, +3.78,-2.92, +4.14,-2.49, +3.75,-1.49, +3.87,-3.25, +4.26,-3.46, +4.17,-3.65, +3.61,-2.49, +3.86,-2.7, +3.34,-2.19, +2.51,-2.95, +2.76,-2.1, +2.91,-0.76, +1.8,-3.33, +3.39,-2.59, +3.53,-2.68, +3.36,-2.75, +2.26,-1.74, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +9.91,2.67, +16.24,2.86, +17.96,3.17, +19.08,3.36, +19.72,3.48, +19.72,3.48, +19.58,3.45, +19,3.35, +19.09,6.58, +19.63,7.14, +20.79,7.57, +22.39,8.15, +24.48,8.91, +16.89,10.61, +9.34,11.67, +5.65,11.8, +-4.45,7.85, +-5.29,8.09, +-5.11,8.08, +-5.64,7.12, +-4.79,8.31, +-4.74,8.21, +-4.52,7.84, +-4.27,7.84, +-2.81,8.47, +-1.09,8.65, +0.12,8.13, +1.38,7.85, +2.23,6.79, +3.09,7.32, +3.69,7.39, +4.31,7.46, +4.88,8.45, +5.78,10.02, +6.84,11.84, +8.62,14.93, +9.53,16.5, +9.77,16.92, +8.81,13.08, +6.86,10.62, +9.14,7.79, +10.13,5.41, +10.56,5.32, +11.31,5.68, +13.04,6.55, +12.63,6.29, +13.44,5.32, +14.2,5.17, +15.65,5.7, +14.2,5.17, +13.53,4.3, +12.3,3.15, +11.04,4.37, +7.07,-0.66, +-1.77,-5.54, +-4.43,-3.53, +-3.94,2.38, +5.37,3.73, +10.31,1.82, +12.07,-0.47, +6.08,-6.61, +-1.33,-9.7, +-1.63,-8.99, +-1.78,-8.58, +-1.41,-7.99, +-1.32,-8.5, +-1.57,-8.93, +-2.15,-9.36, +-3.39,-9.32, +-4.53,-8.57, +-4.95,-7.41, +-5.84,-5.84, +-6.5,-4.55, +-7.05,-3.67, +-7.54,-3.02, +-10.25,0, +-10.69,0, +-10.89,-0.25, +-10.86,-1.05, +-11.23,-1.42, +-11.81,-1.17, +-11.32,-2, +-10.47,-1.02, +-9.78,0, +-9.91,0, +-10.36,1.54, +-9.99,1.76, +-9.89,1.74, +-10.15,1.79, +-9.6,1.69, +-8.69,1.98, +-7.81,1.53, +-6.05,-0.43, +-3.2,-2.72, +-2.59,-4.48, +-1.84,-5.39, +-1.48,-5.82, +-1.51,-6.81, +-1.81,-6.06, +-1.38,-5.84, +-1.61,-4.78, +-1.52,-4.18, +-1.02,-4.94, +-0.59,-4.68, +0.75,-4.85, +0.77,-6.38, +-0,-6.44, +-0,-5.3, +0.44,-5.02, +0.15,-5.04, +-0.98,-4.95, +-1.73,-4.75, +0.24,-4.57, +4.51,-3.75, +3.8,-3.68, +3.67,-3.12, +5.38,-3.08, +4.89,-3.27, +4.01,-2.45, +3.46,-2.94, +4.01,-3.52, +4.26,-3.72, +4.21,-2.77, +3.74,-2.83, +3.9,-3.17, +3.12,-3.31, +3.64,-2.1, +3.05,-0.32, +4.11,-2.63, +3.56,-2.72, +3.43,-3.08, +3.21,-2.86, +2.43,-2.14, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.6,2.08, +5.84,4.48, +11.95,7.35, +12.84,7.08, +15.21,5.24, +14.62,6.05, +15.42,5.39, +14.62,4.62, +15.67,4.04, +16.91,3.84, +15.95,6.39, +16.76,7.68, +21.54,8.1, +23.52,8.56, +22.53,8.2, +9.97,9.33, +4.09,10.31, +-1.89,9.62, +-5.27,7.69, +-5.64,6.73, +-4.65,8.06, +-4.42,7.65, +-4.86,7.34, +-4.68,7.59, +-4,7.96, +-3.22,8.33, +-2.89,8.45, +-1.55,8.81, +0,8.62, +0.62,8.02, +1.1,7.95, +1.89,8.05, +3.06,8.4, +3.94,8.15, +4.79,9.11, +5.76,10.44, +7.08,12.26, +8.39,14.53, +8.77,16.85, +7.63,17.62, +6.12,16.8, +5.62,15.43, +4.15,12.59, +4.69,10.88, +6.61,9.42, +9.92,8.78, +11.94,7.83, +11.83,7.79, +12.21,6.71, +13,5.1, +13.94,5.08, +13.94,3.97, +12.42,2.96, +10.26,2.26, +6.93,-1.22, +-2.53,-5.47, +-4.29,-4.2, +-4.92,-2.59, +-5.77,2.09, +-0.6,6.15, +2.19,3.69, +-2.21,-2.99, +-0.4,-6.64, +-0.41,-9.27, +-0,-9.08, +-0,-8.94, +-0,-8.94, +-0.52,-8.9, +-1.92,-9.21, +-3.6,-9.82, +-5.15,-9.54, +-5.91,-9.13, +-6.47,-7.27, +-7.33,-5.54, +-7.89,-4.16, +-8.14,-2.96, +-7.76,-2.33, +-11.08,0, +-10.94,0, +-10.76,0, +-11,-0.96, +-11.74,-1.74, +-11.39,-2.01, +-10.91,-1.92, +-10.13,-0.91, +-10.5,0, +-10.08,0, +-10.4,1.64, +-10.42,1.84, +-10.26,1.81, +-9.93,1.75, +-9.3,2.2, +-8.34,2.83, +-7.24,2.42, +-5.79,0.33, +-2.7,-1.97, +-1.71,-3.76, +-0.88,-4.98, +-0.52,-5.98, +-0.78,-5.96, +-1.05,-5.93, +-1.02,-5.77, +-0.88,-4.98, +-1.24,-5.21, +-0.88,-4.98, +-0.44,-5.02, +0.82,-5.63, +1.13,-6.41, +0.45,-6.28, +0.13,-5.89, +0.29,-5.19, +0,-5.05, +-0.68,-4.99, +-1.41,-4.83, +0.8,-4.23, +5.22,-3.01, +4.42,-3.74, +5.21,-2.06, +5.42,-4.29, +5.83,-3.83, +5.16,-4, +4.7,-3.72, +4.92,-3.44, +4.7,-3.75, +4.56,-3.66, +4.77,-2.87, +5.05,-2.92, +4.33,-3.78, +3.36,-2.21, +3.55,-1.37, +4.14,-2.34, +4.04,-3.01, +3.52,-3.52, +3.49,-3.36, +2.36,-2.09, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +7.02,5.26, +9.77,7.36, +10.41,7.75, +9.98,8.37, +10.35,8.68, +10.06,8.44, +9.98,8.37, +10.51,7.23, +10.59,5.71, +11.85,4.63, +12.49,5.7, +12.64,7.83, +12.12,8.88, +11.15,8.57, +6.67,7.91, +3.03,8.04, +-0.64,7.8, +-4.07,7.93, +-5.2,7.23, +-4.79,7.4, +-4.2,6.36, +-4.1,6.31, +-3.98,6.9, +-3.89,7.08, +-3.71,7.03, +-3.07,7.92, +-3.06,8.4, +-1.8,8.74, +-1.46,8.27, +-1.55,8.81, +-0.22,8.92, +0.69,7.91, +1.48,8.4, +1.69,8.67, +3.15,9.16, +3.77,10.36, +5.79,10.73, +5.91,12.51, +5.79,15.91, +6.07,16.67, +6.05,16.62, +3.43,16.87, +3.09,17.55, +2.83,16.04, +1.41,15.82, +0.43,15.25, +3.2,14.6, +7.12,12.54, +9.75,9.73, +11.56,8.08, +12.36,7.14, +11.61,5.03, +12.95,2.28, +11.52,2.03, +8.37,1.04, +3.08,-3.85, +-3.29,-4.95, +-4.19,-4.3, +-5.31,-1.9, +-6.72,2.26, +-6.61,4.69, +-7.06,4.22, +-6.6,3.32, +-6.53,1.15, +-4.55,-6.53, +-0.5,-9.29, +0,-8.94, +-0,-8.94, +-1.59,-8.89, +-3.38,-9.28, +-5.52,-9.55, +-6.86,-8.67, +-7.3,-8.7, +-8.47,-6.88, +-8.97,-5.43, +-9.32,-3.39, +-8.77,-2.92, +-8.82,-1.95, +-11.08,0, +-11.08,0, +-11.08,0, +-11.33,-0.3, +-11.87,-2.09, +-11.76,-1.77, +-11.8,-1.02, +-11.08,0, +-10.5,0, +-10.21,0, +-10.22,0.89, +-10.15,1.79, +-9.93,1.75, +-9.78,1.72, +-8.71,2.44, +-7.95,2.89, +-6.88,2.5, +-4.88,1.86, +-2.27,-0.89, +-0.56,-3.19, +0.44,-4.85, +0.48,-5.62, +-0,-6.15, +-0.5,-5.62, +-0.96,-5.46, +-0.97,-5.52, +-0.92,-5.19, +-0.88,-4.98, +-0.11,-5.65, +0.38,-6.16, +1.06,-6.8, +1.05,-5.93, +0.78,-5.96, +0,-5.7, +0,-5.05, +-0.11,-5.04, +-1.3,-4.86, +1.22,-4.31, +4.87,-3.5, +4.34,-4.15, +2.64,-3.29, +2.12,-0.76, +6.01,-3.23, +6.11,-4.91, +6.44,-4.4, +5.18,-3.63, +4.96,-3.94, +4.87,-3.79, +5.57,-3.02, +5.7,-3.06, +4.32,-4.34, +3.63,-2.93, +3.5,-1.27, +2.47,-1.74, +2.94,-3.27, +2.34,-3.51, +3.38,-3.18, +2.27,-2.85, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.92,1.6, +5.4,8.31, +7.35,8.76, +9.05,7.59, +10.26,8.61, +10.85,9.1, +10.31,8.65, +10.31,8.65, +10.72,9, +10.23,8.58, +9.34,7.84, +9.07,7.61, +10.07,6.93, +9.68,6.39, +8.49,5.83, +6.11,7.38, +3.72,8.41, +2.74,8.66, +-0.05,7.37, +-2.91,6.65, +-5.07,6.58, +-5.75,6.85, +-5.75,6.85, +-4.9,7.45, +-3.71,6.42, +-3.5,6.06, +-2.69,6.33, +-2.3,6.32, +-2.5,6.88, +-1.93,7.83, +-1.29,8.18, +-1.53,8.7, +-1.38,7.85, +-0.26,8.92, +0,8.83, +0.65,8.56, +1.34,8.7, +1.83,8.73, +3.45,9.47, +3.84,10.56, +4.47,11.06, +5.26,12.63, +4.75,15.83, +3.96,16.13, +2.92,16.55, +3.06,17.37, +2.64,18.01, +2.09,17.89, +0.35,17.92, +0.33,17.27, +2.24,17.38, +3.78,16.29, +6.45,12.98, +9.62,10.71, +12.09,8.1, +11.88,5.99, +12.51,2.21, +10.95,1.74, +7.69,-1.04, +1.18,-5.69, +-2.38,-5.51, +-4.28,-4.17, +-5.69,-1.63, +-6.66,2.3, +-6.5,4.55, +-6.73,5.35, +-4.39,6.39, +-3.08,6.54, +-3.05,5.12, +-2.22,-9.3, +-0.78,-8.87, +-0.75,-8.26, +-2.78,-8.29, +-5.11,-8.28, +-7.31,-8.67, +-9.18,-7.33, +-9.63,-7.17, +-10.44,-6.03, +-10.99,-4.56, +-10.46,-3.51, +-9.97,-1.95, +-10.28,-1.23, +-11.57,0, +-11.24,0, +-11.73,0, +-12.13,-0.21, +-11.93,-1.4, +-11.99,-0.7, +-11.65,-1.42, +-11.9,-0.45, +-11.16,-0.3, +-10.5,0, +-10.99,0.67, +-10.91,1.92, +-10.48,1.85, +-10.26,2.1, +-8.63,3.14, +-7.56,3.72, +-5.78,3.34, +-3.6,2.52, +-1.2,-0.2, +0.57,-1.91, +1.86,-4.03, +1.65,-5.46, +0.52,-5.98, +-0.52,-5.98, +-1.05,-5.93, +-1,-5.7, +-0.88,-4.98, +-0.93,-5.3, +-0,-6.03, +0.77,-6.67, +0.2,-6.98, +0.87,-5.95, +1.05,-5.93, +0.52,-5.98, +0.13,-5.32, +0.29,-5.03, +-0.62,-4.98, +1.73,-4.72, +4.34,-4.15, +4.19,-4.99, +3.21,-3.45, +1.26,-0, +1.54,-0.33, +3.71,-1.57, +5.87,-4.56, +5.69,-4.81, +5.11,-4.29, +5.49,-3.84, +6.49,-3.15, +5.8,-4.57, +4.65,-5.31, +3.79,-3.99, +3.29,-2.24, +2.48,-2.68, +1.96,-4.25, +3.08,-3.74, +3,-2.85, +2.77,-3.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.55,1.91, +7.7,7.85, +8.37,9.98, +10.09,10.09, +10.72,9, +10.8,9.07, +11,8.6, +10.72,9, +11.22,8.85, +11.75,7.72, +11.5,7.2, +9.63,7.39, +7.88,6.95, +6.26,6.53, +4.61,5.68, +3.03,5.68, +1.28,5.95, +-1.09,7.35, +-2.8,7.68, +-4.49,7.01, +-5.83,6.12, +-6.29,6.11, +-5.44,6.48, +-4.55,6.5, +-3.81,6.87, +-3.06,6.38, +-2.71,6.25, +-2.12,5.81, +-2.03,5.87, +-1.57,6.4, +-1.32,7.49, +-1.24,8.35, +0,8.46, +0,8.08, +0.33,8.27, +0.78,8.87, +1.29,8.83, +1.9,9.37, +3.12,10.32, +3.79,10.41, +3.79,10.41, +4.07,11.17, +4.2,11.53, +3.58,13.48, +2.67,15.17, +2.82,16.02, +1.74,17.59, +1.41,18.17, +0.74,18.45, +0.55,18.52, +1.63,18.58, +3.39,19.24, +3.59,18.22, +5.27,15.55, +9.17,13, +10.44,10.99, +10.65,8.43, +11.31,3.85, +9.99,1.76, +7.24,-1.62, +0.24,-6.28, +-2.94,-5.09, +-4.88,-3.42, +-6.56,-0.52, +-6.94,2.79, +-6.67,5.62, +-5.61,6.95, +-3.94,7.28, +-2.08,7.83, +-2.63,8.52, +-3.79,4.65, +-4.33,-7.55, +-3.65,-7.77, +-4.53,-7.07, +-6.98,-6.37, +-8.56,-6.12, +-10.39,-6, +-11.19,-5.18, +-11.93,-4.34, +-12.04,-4.38, +-11.61,-2.68, +-11.07,-1.67, +-11.21,0, +-11.93,0, +-11.4,0, +-12.05,0, +-11.61,-0.3, +-11.76,-1.77, +-11.4,-1.3, +-11.65,-1.81, +-11.02,-0.32, +-10.25,-0.57, +-10.93,0, +-10.55,0, +-10.36,1.54, +-9.76,1.72, +-9.41,1.92, +-8.06,2.93, +-7.22,3.53, +-5.1,3.75, +-3.21,3, +-0.14,-0.06, +2.34,-0.94, +3.94,-2.9, +2.92,-5.05, +1.22,-5.89, +-0.56,-5.87, +-1.15,-5.28, +-1.47,-5.07, +-1.08,-5.26, +-0.53,-5.15, +-0,-5.59, +0.46,-6.96, +0,-7, +0.7,-6.13, +1.05,-5.93, +0.92,-5.95, +0.3,-6, +1.36,-5.68, +1.23,-5.13, +2.45,-4.81, +4.29,-5.11, +4.5,-5.36, +4.62,-5.87, +1.61,-1.62, +1.97,-0.63, +2.43,1.55, +5.64,-3.6, +6.05,-5.46, +5.72,-4.76, +6.82,-3.4, +6.15,-4.32, +6.24,-6.02, +4.25,-7.01, +3.95,-4.54, +3.66,-3.23, +1.97,-3.2, +2.94,-4.19, +4.55,-3.6, +4.16,-3.87, +3.35,-3.27, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.05,0.38, +8.45,5.21, +9.33,7.82, +9.77,10.41, +11.22,9.41, +10.93,9.17, +10.72,9, +11.19,9.39, +12.31,8.79, +13.06,7.54, +12.48,7.21, +10.86,6.27, +8.66,5.69, +5.1,4.89, +2.94,5.22, +0.8,5.1, +-0.53,4.57, +-1.72,4.72, +-2.77,5.81, +-4.17,6.43, +-5.33,6.35, +-5.65,5.92, +-5.23,6, +-4.36,6.64, +-3.63,7.07, +-2.34,6.93, +-1.6,5.91, +-1.52,6.48, +-1.23,7, +-1.24,7.05, +-0.81,6.93, +0,7.75, +0.1,7.91, +0.17,8.32, +0,8.94, +1.28,8.41, +1.5,8.49, +1.59,9.02, +2.55,9.88, +3.79,10.41, +3.79,10.41, +3.79,10.41, +4.09,11.22, +4.07,11.17, +3.28,12.39, +2.39,13.54, +2.65,15.03, +2.84,16.1, +3.14,17.8, +3.28,18.6, +3.31,18.76, +3.4,19.29, +3.91,19.32, +6.52,17.9, +6.95,16.41, +8.26,14.31, +8.01,12.72, +8.7,11.72, +8.09,8.93, +8.27,3.87, +6.25,-1.85, +-1.32,-6.31, +-4.2,-3.97, +-5.9,-1.54, +-7.1,1.58, +-7.59,4.03, +-7.12,6.01, +-5.56,7.57, +-3.91,8.16, +-2.39,8.58, +-2.77,8.73, +-3.76,8.07, +-6.49,4.77, +-7.08,-1.66, +-7.33,-2.45, +-8.52,-3.07, +-9.82,-2.79, +-11.48,-4.18, +-12.3,-4.21, +-12.85,-3.82, +-13.15,-2.32, +-12.35,-2.18, +-11.74,-0.78, +-11.57,0, +-12.05,0, +-11.89,0, +-12.05,0, +-12.05,0, +-12.19,-0.63, +-11.6,-1.03, +-11.45,-1.34, +-11.19,-0.64, +-10.97,-1.28, +-10.77,-0.24, +-10.91,0, +-10.23,0.25, +-9.63,1.7, +-9.13,1.61, +-8.4,3.06, +-7.29,3.91, +-5.16,3.94, +-2.64,3.45, +0.61,1.49, +4.51,0.35, +5.98,-1.38, +4.44,-3.88, +2.15,-5.56, +-0.64,-5.65, +-1.99,-4.63, +-2.14,-4.71, +-1.67,-4.85, +-0.77,-4.99, +-0,-5.86, +0,-7, +0,-7.75, +0.48,-6.85, +1.05,-5.93, +1.05,-5.93, +0.39,-5.75, +2.73,-5.42, +4.02,-5.02, +4.08,-4.86, +4.5,-5.36, +4.68,-5.57, +4.97,-5.65, +2.96,-2.75, +3.01,-1.07, +3.65,-0.03, +3.97,1.15, +5.97,-5.41, +6.5,-4.28, +6.3,-3.37, +6.98,-4.4, +4.63,-6.92, +4.45,-7.25, +3.09,-5.36, +3.89,-3.49, +2.61,-3.53, +3.46,-4.12, +4.74,-3.21, +4.37,-4.12, +3.08,-2.53, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.32,0.23, +8.09,2.49, +10.23,4.9, +10.23,7.77, +10.35,9.82, +9.66,10.67, +10.72,9, +10.8,9.07, +11.68,9.8, +12.87,9.59, +12.59,7.02, +10.32,3.95, +8.16,2.2, +5.1,1.36, +2.59,2.1, +0.83,2.84, +-0.34,3.25, +-1.24,3.47, +-2.02,3.7, +-3.01,5.22, +-3.36,5.57, +-4.26,5.92, +-4.43,6.09, +-3.9,6.76, +-3.36,7.2, +-2.73,7.49, +-1.64,7.42, +-0.6,6.39, +-0.2,6.98, +-0.46,6.96, +-0.41,6.96, +0,7.43, +0.23,7.95, +1.13,7.71, +1.38,7.85, +1.49,8.42, +2.04,8.01, +2.41,8.58, +2.5,9.22, +3.47,10.1, +3.79,10.41, +4.02,10.41, +4.23,9.96, +3.84,10.56, +3.85,9.53, +2.51,10.82, +2.24,12.69, +2.63,14.9, +2.79,15.83, +3.12,17.69, +3.17,17.99, +4.22,18.52, +5.11,18.95, +6.43,18.93, +7.42,18.19, +8.85,16.94, +8.13,14.08, +6.37,14.73, +6.29,12.72, +5.21,10.82, +5.2,8.33, +1.65,2.41, +-4.22,-4.3, +-5.59,-0.98, +-6.52,0.97, +-7.84,3.13, +-8.12,5.35, +-7.38,7.41, +-6.95,8.58, +-5.37,9.29, +-4.84,9.37, +-5.25,9.09, +-5.11,8.86, +-5.09,8.1, +-6.75,6.19, +-8.73,3.76, +-9.93,1.46, +-10.65,-0.19, +-12.13,-1.94, +-13.15,-2.32, +-13.78,-2.43, +-13.15,-2.32, +-12.85,-2.01, +-12.81,0, +-12.05,0, +-12.47,0, +-12.05,0, +-12.48,0, +-12.75,0, +-11.73,0, +-12.05,0, +-12.03,-0.23, +-11.01,-0.86, +-10.91,-1.92, +-11.32,-0.99, +-10.69,0, +-10.13,0, +-9.69,1.32, +-9.13,1.61, +-8.25,3, +-7.02,4.05, +-5.51,4.46, +-2.74,3.99, +1.95,2.01, +5.97,1.55, +7.8,0.17, +6.65,-2.18, +2.64,-4.93, +-0.84,-5.76, +-2.58,-5.06, +-2.69,-4.66, +-2.17,-4.31, +-1.16,-4.9, +-0.15,-5.37, +0,-7, +0,-7.87, +-0,-7.41, +0.55,-6.14, +1.16,-6.57, +1.53,-5.98, +3.93,-5.9, +4.5,-5.36, +4.5,-5.36, +4.71,-5.61, +5.33,-6.35, +5.23,-6, +3.31,-3.35, +3.49,-2.04, +4.33,-0.94, +4.4,1.68, +4.98,-4.44, +6.47,-4.34, +5.71,-3.5, +5.25,-4.72, +3.19,-8.21, +2.73,-7.49, +3.52,-5.81, +3.35,-4.88, +4.22,-3.77, +4.24,-3.98, +5.17,-3.83, +4.42,-4.42, +3.59,-2.79, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +10.37,1.83, +12.16,2.88, +11.92,4.87, +11.45,7.21, +10.53,9.19, +10.56,9.39, +10.72,9, +11.14,9.34, +11.55,9.69, +11.52,8.61, +8.83,4.04, +5.94,-0.32, +3.18,-1.75, +1.95,-1.85, +1.07,-0.2, +0.08,0.61, +-0.44,1.71, +-1.1,2.1, +-1.72,2.97, +-2.3,4.37, +-2.35,5, +-2.81,5.47, +-3.07,5.73, +-2.92,6.55, +-2.73,7.49, +-2.05,7.67, +-0.79,7.48, +0.81,6.93, +0.54,6.95, +0.17,6.98, +0,7, +0,7.69, +0.46,7.93, +1.31,8.29, +1.38,7.85, +1.89,7.88, +2.75,8.2, +3.06,8.4, +3.48,9.55, +3.73,10.26, +3.79,10.41, +4.03,9.18, +4.48,8.95, +3.66,10.05, +3.34,9.16, +3.13,10.56, +2.2,12.47, +2.62,14.85, +2.77,15.7, +2.89,16.37, +3.14,17.8, +3.18,18.02, +5.43,18.89, +6.85,18.81, +6.63,18.21, +6.28,15.91, +8.46,14.65, +8.4,14.55, +4.83,13.28, +2.71,11.37, +1.98,9.76, +-0.78,7.8, +-4.72,2.19, +-5.76,2.44, +-7.34,2.67, +-8.51,4.14, +-8.85,6.12, +-8.43,7.63, +-8.49,8.49, +-7.42,9.39, +-6.19,10.12, +-6.41,9.96, +-5.99,9.78, +-7.12,8.49, +-7.91,7.43, +-9.08,5.77, +-10.48,3.81, +-11.24,2.29, +-12.14,-0.02, +-13.4,-2.03, +-13.78,-2.43, +-13.57,-2.39, +-12.86,-1.88, +-13.02,0, +-12.44,1.07, +-12.94,0, +-12.7,0, +-13.02,0, +-13.02,0, +-12.33,0, +-12.27,0, +-11.84,0, +-11.03,-0.55, +-10.91,-1.92, +-11.27,-1.99, +-10.58,-1.24, +-9.89,-0.29, +-9.84,0.86, +-8.49,1.5, +-7.94,0.49, +-7.01,1.51, +-5.95,1.98, +-2.79,3.09, +2.3,2.86, +6.2,2.89, +7.44,1.74, +5.59,-1.27, +1.91,-4.6, +-1.17,-5.88, +-2.43,-5.02, +-3.18,-4.51, +-2.65,-4.29, +-1.72,-4.71, +-0.39,-5.24, +0.15,-6.74, +0.41,-7.61, +-0,-7.24, +1.01,-6.87, +2.34,-8, +3.55,-7.74, +4.64,-6.74, +5.44,-6.48, +5.12,-6.11, +5.67,-6.76, +6.06,-7.22, +4.95,-5.36, +4.12,-3.68, +4.98,-4.03, +5.17,-4.06, +4.33,-1.43, +3.57,-0.21, +5.77,-5.12, +5.93,-5.92, +5.5,-6.86, +1.99,-8.75, +2.92,-8.01, +4.45,-6.55, +5.02,-6.52, +5.31,-5.92, +4.78,-3.36, +3.85,-5.08, +5.07,-5.86, +4.04,-4.22, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +9.81,2.37, +12.88,4.15, +13.15,4.79, +12.42,6.37, +11.82,8.27, +11.3,9.48, +11.55,9.31, +11.72,9.27, +11.32,9.11, +8.21,5.83, +4.54,0.44, +0.69,-3.63, +-0.74,-4.18, +-0.05,-2.93, +0.39,-1.88, +0.18,-0.62, +-0.36,0.31, +-0.61,0.95, +-1.36,3, +-1.47,4.04, +-1.73,4.75, +-1.99,5.46, +-2.06,5.66, +-2.28,6.27, +-2.02,7.18, +-1.38,7.85, +-0.81,7.64, +1.01,6.91, +1.22,6.89, +1.08,6.9, +0.83,7.14, +0.17,7.95, +1.41,8.01, +1.48,8.4, +2.09,8.22, +2.98,8.2, +3.06,8.4, +3.06,8.4, +2.83,9.47, +3.33,10.27, +3.75,10.3, +3.49,9.59, +3.39,9.32, +1.91,9.43, +1.77,10.02, +2.17,11.07, +2.29,12.96, +2.96,14.31, +2.98,14.78, +2.84,16.12, +2.98,16.91, +3.16,17.91, +3.36,19.08, +4.92,19.33, +5.91,18.04, +6.88,16.09, +6.29,14.59, +6.43,14.88, +4.64,12.76, +2.82,11.9, +1.47,10.54, +-1.43,8.41, +-4.26,6.12, +-6.11,4.48, +-7.88,4.39, +-9.32,5.38, +-9.37,6.99, +-9.73,8.16, +-10.07,8.45, +-9.17,9.33, +-8.45,10.07, +-8.48,10.1, +-8.06,9.6, +-7.98,9.51, +-8.27,8.1, +-9.59,6.52, +-10.88,5.07, +-11.6,3.11, +-12.32,1.07, +-13.37,-0.49, +-13.97,-0.3, +-13.8,-0.41, +-12.99,-0.38, +-13.02,0, +-12.74,1.99, +-12.38,0, +-13.02,0, +-13.15,0, +-13.13,0, +-12.54,0, +-12.7,0, +-12.05,0, +-11.64,0, +-11.8,-1.89, +-11.32,-2, +-10.72,-1.89, +-10.03,-0.86, +-9.64,0, +-8.19,1.2, +-8.02,-4.63, +-7.64,-2.78, +-6.48,-0.41, +-3.56,1.68, +0.82,3.44, +4.83,4.32, +5.36,2.87, +3.63,-1.4, +-0.19,-3.99, +-1.69,-5.46, +-2.46,-5.3, +-2.93,-4.49, +-3.01,-4.04, +-2.21,-4.53, +-1.22,-5.01, +-0,-6.51, +1.27,-7.21, +2.44,-7.85, +3.35,-9.2, +5.54,-9.6, +5.61,-9.07, +5.9,-7.92, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.79,-8.09, +7.12,-8.49, +6.84,-8.15, +7,-8.34, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +6.87,-8.19, +6.37,-7.59, +6.14,-7.76, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.75,-8.04, +4.75,-5.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.29,0.75, +10.16,4.86, +12.5,5.68, +13.35,4.86, +13.15,4.79, +12.96,7.48, +13.46,8.71, +13.22,7.63, +12.56,6.97, +9.98,5.87, +4.64,2.24, +-0.56,-4, +-1.71,-4.98, +-1.03,-4.59, +-0.18,-3.63, +0.11,-2.58, +0.07,-1.79, +-0.44,-0.66, +-0.82,0.95, +-1.04,2.48, +-1.45,3.99, +-1.78,4.9, +-1.88,5.16, +-2.13,5.87, +-2.28,6.27, +-2.39,7.24, +-2.22,8.13, +-1.94,8.03, +0.03,7.07, +-0.1,7.09, +-0.26,7.09, +0,7.32, +0.46,8.04, +1.38,7.85, +1.41,7.97, +1.41,8.01, +1.52,8.65, +2,8.35, +1.91,8.82, +1.72,9.76, +2.79,10.28, +3.52,9.68, +3.39,9.32, +3.21,9.37, +1.84,9.53, +1.78,10.09, +2.01,11.39, +2.17,12.29, +2.37,13.46, +2.56,14.5, +2.74,15.54, +2.94,16.65, +3.18,18.04, +3.33,18.87, +4.56,19.2, +3.31,18.76, +6.13,16.83, +4.66,15.29, +4.86,15.14, +3.71,13.82, +2.15,12.19, +1.02,11.12, +-1.99,9.15, +-4.49,7.17, +-6.93,5.79, +-9.26,5.35, +-10.54,6.09, +-10.78,7.41, +-11.09,8.28, +-10.72,9, +-10.72,9, +-10.43,9.28, +-9,10.72, +-8.72,10.39, +-8.51,10.14, +-9.32,8.24, +-9.83,6.89, +-10.83,5.69, +-11.85,3.82, +-12.55,2.21, +-13.35,0, +-13.83,0, +-13.11,0, +-13.02,0, +-13.02,0, +-12.73,1.48, +-12.54,0, +-12.59,0, +-12.83,0, +-12.75,0, +-12.54,0, +-12.7,0, +-11.62,0, +-11.51,0, +-11.87,-2.09, +-10.86,-1.91, +-10.42,-1.84, +-10.05,-1.38, +-9.31,0, +-8.74,0.49, +-8.46,-3.31, +-8.08,-3.41, +-6.79,-1.51, +-4.87,1.04, +-1.53,3.53, +1.63,4.2, +0.6,2.62, +-1.73,-0.97, +-3.11,-3.75, +-3.09,-5.36, +-2.85,-5.16, +-3.2,-4.79, +-3.05,-4.67, +-2.64,-4.86, +-1.51,-5.68, +-0,-6.67, +1.32,-7.49, +2.51,-8.42, +3.87,-9.83, +5.54,-9.6, +6.07,-9.23, +6.79,-8.09, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.78,-8.08, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +6.65,-7.93, +6.37,-7.59, +5.97,-7.88, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.87,-8.19, +7.12,-8.49, +5.34,-6.37, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.36,3.27, +9.32,5.38, +10.86,6.27, +13.15,4.79, +13.56,4.93, +13.64,6.54, +14.01,8.09, +13.38,7.73, +12.07,4.92, +9.39,0.35, +3.09,-1.06, +-1.04,-4.5, +-1.34,-5.86, +-0.38,-4.88, +0,-4.67, +0.14,-3.42, +0.02,-2.3, +-0.14,-0.8, +-1.09,0.92, +-1.52,2.68, +-2.06,4.12, +-2.34,4.94, +-2.54,5.44, +-2.39,6.05, +-2.39,6.58, +-2.73,7.49, +-3.02,8.3, +-3.06,8.4, +-2.95,8.1, +-2.73,7.49, +-2.73,7.49, +-1.91,7.44, +-0.66,8.29, +0.62,8.33, +1.45,8.21, +1.42,8.06, +1.5,8.49, +1.47,8.33, +1.58,8.97, +1.72,9.76, +2.5,10.49, +3.57,9.8, +3.39,9.32, +3.22,8.86, +2.08,9.09, +1.7,9.66, +1.94,11.01, +2.09,11.87, +2.32,13.15, +2.5,14.19, +2.64,14.95, +2.84,16.12, +3.02,17.15, +3.22,18.28, +3.2,18.12, +3.27,18.55, +3.11,17.65, +2.87,16.27, +2.97,16.82, +1.76,14.98, +0.77,13.6, +-0.99,12.18, +-3.59,10.5, +-6.79,8.58, +-8.8,6.96, +-10.68,7.37, +-11.51,7.31, +-11.83,9.1, +-11.79,9.89, +-11.47,9.62, +-11.3,9.48, +-11.05,9.27, +-10.27,10.04, +-9.31,11.09, +-9.1,10.85, +-9.77,8.88, +-10.33,7.86, +-11.28,6.51, +-12.24,4.45, +-12.73,2.63, +-13.56,0.87, +-13.67,0, +-13.3,0, +-13.02,0, +-13.02,0, +-12.87,0.5, +-12.48,0, +-12.05,0, +-12.54,0, +-12.42,0, +-12.27,0, +-12.05,0, +-12.05,0, +-11.5,-0.7, +-11.71,-2.06, +-10.55,-1.86, +-10.02,-1.77, +-9.28,-1.64, +-8.92,-0.26, +-8.62,0, +-8.05,-4.84, +-8.13,-4.71, +-7.44,-2.31, +-5.84,-0.03, +-3.56,2.19, +-2.4,2.61, +-3.14,0.75, +-4.64,-1.91, +-4.62,-4.1, +-3.98,-5.33, +-3.7,-5.49, +-3.52,-5.31, +-3.01,-5.22, +-2.7,-5.37, +-1.72,-5.75, +0.03,-6.79, +1.36,-7.71, +3.08,-9.23, +4.24,-9.77, +5.72,-9.47, +7.15,-8.86, +7.41,-8.84, +6.37,-7.59, +6.37,-7.59, +6.56,-7.82, +6.87,-8.19, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +6.91,-8.23, +6.37,-7.59, +5.84,-7.97, +6.5,-7.74, +6.62,-7.89, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +4.99,-5.94, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.4,0.51, +9.23,3.99, +10.06,5.81, +11.09,6.4, +12.35,5.53, +13.91,5.06, +13.65,6.01, +13.92,8.03, +12.89,7.13, +11.46,2.47, +6.91,-3.53, +2.08,-4.88, +-0.68,-5.97, +-0.5,-6.41, +-0,-5.92, +-0,-5.22, +-0,-3.92, +-0.05,-2.72, +-0.66,-0.78, +-1.33,1.09, +-2.12,2.53, +-2.72,3.65, +-3.03,4.42, +-3.01,5.22, +-3,5.46, +-2.95,6.32, +-2.73,7.49, +-3.08,7.46, +-3.48,8.09, +-3.48,8.09, +-2.73,7.49, +-2.73,7.49, +-2.73,7.49, +-0.91,7.38, +0,7.43, +0,8.09, +0.69,7.91, +1.48,8.4, +1.44,8.17, +1.55,8.81, +1.72,9.76, +1.82,10.34, +1.76,9.95, +2.28,9.61, +1.76,8.86, +1.64,9.28, +1.92,9.38, +1.95,11.07, +2.2,12.47, +2.28,12.92, +2.46,13.97, +2.66,15.06, +2.74,15.54, +2.83,16.06, +3,17.04, +3.14,17.8, +3.2,18.12, +3.07,17.42, +2.63,17.35, +1.02,17.84, +0,17.18, +-1.38,15.94, +-3.69,13.78, +-5.96,12.3, +-8.19,11.02, +-10.64,8.93, +-11.47,9.62, +-12.09,10.14, +-12.21,10.25, +-12.14,10.18, +-11.94,10.02, +-11.47,9.62, +-11.47,9.62, +-11.47,9.62, +-9.62,11.47, +-9.64,10.23, +-10.48,9.24, +-10.54,7.86, +-11.93,6.54, +-12.85,4.68, +-13.29,3.44, +-13.78,2.43, +-13.32,0.38, +-13.02,0, +-13.02,0, +-13.02,0, +-12.86,0, +-12.38,0, +-12.05,0, +-12.17,0, +-12.05,0, +-12.05,0, +-12.05,0, +-11.89,0, +-11.8,-1.55, +-11.02,-1.94, +-10.26,-1.81, +-9.64,-1.7, +-8.97,-1.58, +-8.82,-1.36, +-8.39,-0.17, +-8.27,-4.07, +-8.02,-5.77, +-8.1,-3.71, +-6.85,-1.74, +-6.05,-0.25, +-5.48,-0.38, +-5.47,-1.01, +-5.47,-2.81, +-5.73,-4.81, +-5.01,-5.31, +-4.81,-5.73, +-4.21,-5.42, +-3.01,-5.22, +-2.42,-5.5, +-1.4,-5.95, +-0,-6.97, +1.96,-8.27, +3.39,-9.32, +4.83,-9.76, +5.72,-9.47, +7.51,-8.95, +7.07,-8.92, +7,-8.34, +6.82,-8.13, +7.03,-8.38, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +6.5,-7.74, +6.65,-7.93, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.21,-8.59, +5.58,-6.65, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.14,3.03, +9.21,4.99, +10.44,6.03, +11.09,6.4, +11.28,6.51, +12.91,5.56, +12.76,6.94, +12.26,7.08, +11.82,6.5, +10.1,1.72, +5.98,-5.12, +1.35,-6.8, +-0,-6.19, +-0,-7, +-0,-5.81, +-0,-5.05, +-0,-4.08, +-0.28,-2.72, +-0.64,-0.64, +-1.96,0.71, +-2.89,2.31, +-3.56,3.83, +-3.61,4.3, +-3.23,4.67, +-3.01,5.22, +-3.44,5.97, +-3.74,6.78, +-3.98,6.9, +-4.07,7.04, +-3.74,7.32, +-3.4,6.82, +-2.73,7.49, +-2.73,7.49, +-1.31,7.13, +0,7.16, +0,7.97, +0,7.97, +0.81,8.12, +1.43,8.12, +0.89,8.86, +1.34,9.69, +1.72,9.76, +1.81,10.27, +1.67,9.44, +1.58,8.97, +1.65,9.35, +2.2,9.3, +1.9,10.78, +2.12,12.01, +2.26,12.83, +2.35,13.3, +2.35,13.3, +2.58,14.65, +2.72,15.42, +3.04,17.23, +3.04,17.23, +2.35,17.09, +0.33,16.88, +1.13,17.54, +0,18.56, +0,18.24, +-2.99,16.98, +-4.37,15.82, +-6.73,14.15, +-9.59,12.87, +-11.1,11.12, +-12.42,10.42, +-12.62,10.59, +-12.71,10.66, +-12.46,10.45, +-12.21,10.25, +-11.93,10.01, +-11.54,9.68, +-11.26,9.83, +-10.54,10.54, +-10.85,9.1, +-10.72,9, +-11.97,7.22, +-12.42,6.59, +-13.72,4.99, +-14.2,3.38, +-13.78,2.43, +-13.35,1.78, +-13,0.25, +-13.3,0, +-13.19,0, +-13.02,0, +-12.29,0, +-12.05,0, +-12.19,0, +-12.21,0, +-11.89,0, +-11.84,0, +-11.48,-0.98, +-10.86,-1.91, +-11.07,-1.95, +-9.6,-1.69, +-8.76,-2.33, +-8.36,-2.91, +-8.81,-1.55, +-8.35,-1.24, +-8.6,-4.85, +-8.19,-6.87, +-8.27,-5.27, +-8.49,-3.85, +-7.34,-2.67, +-7.03,-1.94, +-6.45,-3.05, +-6.89,-4.56, +-6.64,-5.57, +-6.19,-6.17, +-5.23,-6.23, +-4.86,-5.79, +-3.42,-5.47, +-2.63,-5.85, +-1.55,-6.63, +0.2,-7.14, +1.95,-8.57, +3.52,-9.68, +5.29,-9.71, +6.07,-9.23, +7.67,-9.14, +7.07,-8.92, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7,-8.34, +6.96,-8.29, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.19,-8.57, +7.67,-9.14, +5.16,-6.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.93,3.04, +7.44,5.47, +9.75,5.91, +10.02,5.78, +9.81,5.66, +10.16,5.86, +10.72,6.19, +11.37,6.57, +11.28,6.51, +9.43,1.57, +4.61,-4.99, +1.02,-7.45, +-0,-7, +-0,-6.63, +-0,-5.86, +-0,-5.05, +-0.37,-3.92, +-0.68,-1.91, +-1.19,-0.35, +-2.46,0.78, +-3.52,2.34, +-3.87,3.25, +-3.53,3.9, +-3.31,3.95, +-3.66,4.77, +-3.29,5.7, +-3.62,6.27, +-3.98,6.9, +-3.88,6.72, +-3.56,6.17, +-3.5,6.06, +-2.95,6.85, +-2.58,7.08, +-0.95,6.92, +-0.27,7.41, +-0.23,7.95, +0,7.81, +0,7.83, +0,8.19, +0,8.73, +0,9.19, +1.23,9.81, +1.84,10.42, +1.53,9.8, +1.67,9.44, +1.64,9.28, +1.58,8.97, +1.86,10.53, +1.73,11.17, +2.07,11.76, +1.67,12.12, +2.37,13.46, +2.46,13.94, +2.37,14.98, +2.33,16.61, +0.98,16.5, +0,16.59, +0,16.91, +0,17.69, +0,18.4, +-1.43,18.6, +-3.11,17.66, +-5.03,16.69, +-7.13,15.81, +-9.3,13.82, +-10.74,12.32, +-12.66,11.58, +-13.4,11.25, +-12.95,10.87, +-12.95,10.87, +-12.46,10.45, +-12.21,10.25, +-12.1,10.16, +-11.47,9.62, +-11.47,9.62, +-10.8,9.07, +-11.19,8.33, +-12.12,7, +-13.59,5.38, +-14.07,5.12, +-14.67,2.85, +-14.07,2.48, +-13.78,2.43, +-13.41,1.19, +-14,0, +-13.58,0, +-13.64,0.7, +-12.59,0, +-12.05,0, +-12.21,0, +-12.19,0, +-11.51,0, +-11.02,-0.72, +-11.63,-2.05, +-9.95,-1.76, +-9.88,-2.03, +-9.51,-2.85, +-7.79,-2.84, +-8.1,-2.95, +-8.5,-2.68, +-8.49,-1.5, +-7.98,-6.08, +-8.49,-7.12, +-7.76,-6.14, +-8.59,-4.96, +-7.74,-4.47, +-8.12,-4.24, +-7.91,-4.79, +-7.74,-6.5, +-7.72,-6.48, +-7.29,-6.45, +-5.44,-6.48, +-5.12,-6.11, +-3.99,-6.9, +-2.78,-6.55, +-0.81,-6.93, +0.59,-7.36, +1.99,-8.49, +4.03,-9.66, +5.54,-9.6, +6.7,-8.98, +7.75,-9.23, +7.81,-8.42, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.33,-8.74, +7.43,-8.86, +7.81,-9.31, +7.75,-9.23, +5.16,-6.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.55,1.54, +4.42,5.27, +6.09,5.65, +6.85,4.44, +7.28,4.2, +7.63,4.92, +9.87,6.36, +10.81,6.8, +10.81,6.24, +8.16,1.42, +3.89,-4.79, +0.87,-7.25, +0,-7.12, +-0,-6.35, +-0,-6.03, +-0.33,-5.03, +-0.62,-3.04, +-0.96,-1.67, +-1.6,0.02, +-2.68,0.81, +-3.49,2.11, +-3.29,2.63, +-3.62,3.04, +-3.59,3.34, +-3.66,3.91, +-3.55,4.45, +-3.42,5.67, +-3.5,6.06, +-3.5,6.06, +-3.62,5.58, +-3.62,5.58, +-3.2,5.85, +-3.5,6.06, +-1.22,6.89, +-1.31,6.6, +-0.78,7.08, +-0.81,7.04, +-0.46,7.32, +0,7.97, +0,8.78, +-0.17,9.03, +0.19,9.47, +0.38,10.01, +1.08,9.82, +1.72,9.76, +0.93,8.87, +1.38,8.82, +0.79,10.23, +0,11.08, +0,11.4, +0,12.05, +0,13.46, +0.27,13.86, +0.67,14.91, +0,15.94, +0,15.94, +0,16.06, +0,16.82, +0,17.43, +0,18.56, +-2.76,18.81, +-3.22,18.28, +-5.84,17.06, +-6.82,16.69, +-8.96,15.51, +-10.87,12.95, +-11.74,13.18, +-12.76,12.53, +-12.29,12.08, +-12.51,10.97, +-12.21,10.25, +-11.84,9.93, +-11.57,9.71, +-11.77,9.19, +-11.47,9.62, +-11.19,8.33, +-12.12,7, +-12.62,6.32, +-13.76,5.01, +-14.52,3.44, +-14.6,2.58, +-14.62,2.58, +-14.42,2.54, +-14.14,2.49, +-14,0, +-14,0, +-13.73,1.19, +-12.7,0, +-12.16,0, +-12.7,0, +-12.17,0, +-10.97,-1.28, +-10.91,-1.92, +-11.07,-1.95, +-9.89,-2.06, +-8.86,-3.02, +-8.55,-3.11, +-7.49,-2.73, +-7.64,-2.78, +-8.22,-2.99, +-7.93,-2.57, +-8.98,-7.16, +-8.49,-7.12, +-7.59,-6.37, +-8.46,-5.13, +-8.54,-4.93, +-8.32,-5.09, +-7.74,-6.5, +-8.49,-7.12, +-7.93,-6.65, +-7.3,-6.12, +-5.75,-6.85, +-4.93,-6.24, +-3.99,-6.9, +-2.57,-7, +-0.81,-6.93, +0.86,-7.77, +2.03,-8.51, +4.16,-9.33, +5.54,-9.6, +7.67,-9.14, +7.75,-9.23, +8.27,-8.2, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.23,-8.61, +7.75,-9.23, +7.96,-9.48, +8.37,-9.98, +8.06,-9.6, +5.58,-6.65, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.72,3.12, +5.07,4.79, +5.04,3.69, +4.95,3.21, +5.77,4.66, +7.11,5.97, +8.71,7.31, +9.09,6.94, +6.63,2.15, +2.58,-4.8, +0.95,-6.92, +0,-7.55, +-0,-7, +-0,-6.03, +-0.33,-4.7, +-0.78,-2.8, +-1.2,-1.49, +-1.74,-0.28, +-2.65,0.72, +-2.91,1.32, +-3.13,1.65, +-3.71,2.14, +-3.53,2.47, +-3.57,2.84, +-3.33,3.79, +-3.36,4.19, +-2.98,4.58, +-3.37,5.21, +-3.87,4.62, +-3.87,4.62, +-3.87,4.62, +-4.43,5.28, +-4.39,5.44, +-4.21,5.56, +-3.67,5.94, +-3.5,6.06, +-3.37,7.07, +-2.73,7.49, +-2.78,8.14, +-1.8,8.74, +-2.05,8.67, +-3.06,8.4, +-3.06,8.4, +-3.1,8.06, +-3.21,8.81, +-3.39,9.32, +-3.66,10.05, +-4.01,11.02, +-2.37,12.13, +-2.34,13.25, +-2.51,14.26, +-2.68,15.22, +-2.82,15.97, +-2.28,16.5, +-1.96,16.44, +-2.37,17.01, +-1.79,17.59, +0,17.3, +-1.57,17.94, +-3.16,17.91, +-3.25,18.44, +-6.52,17.9, +-7.19,16.7, +-8.94,15.49, +-10,13.44, +-11.14,13.28, +-11.41,13.59, +-11.56,12.26, +-12.01,11.36, +-12.7,8.91, +-11.47,9.62, +-12.03,8.82, +-12.96,7.48, +-12.63,7.72, +-12.12,7, +-12.24,6.75, +-13.14,5.07, +-14.25,3.8, +-14.74,2.6, +-14.26,2.51, +-14.14,2.49, +-14.42,2.54, +-14.74,2.6, +-14.08,0.84, +-14,0, +-13.87,1.39, +-12.7,0, +-12.05,0, +-12.78,0, +-12.18,-0.35, +-11.71,-2.06, +-11.02,-1.94, +-10.66,-1.88, +-9.63,-2.95, +-8.63,-3.14, +-8.4,-3.06, +-7.62,-2.77, +-7.05,-2.89, +-7.41,-2.91, +-7.3,-2.84, +-9.23,-7.75, +-8.31,-6.98, +-7.69,-6.46, +-8.46,-5.13, +-8.96,-5.18, +-7.74,-6.5, +-8.19,-6.87, +-8.49,-7.12, +-7.79,-6.54, +-7.22,-6.06, +-5.75,-6.85, +-4.55,-6.5, +-3.63,-7.07, +-2.44,-7.47, +-0.61,-7.19, +0.92,-7.89, +2.56,-8.54, +3.86,-8.92, +5.65,-8.79, +7.48,-8.91, +8.01,-8.05, +8.53,-7.99, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +6.67,-8.8, +6.9,-8.65, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.64,-9.11, +7.75,-9.23, +8.16,-9.73, +8.37,-9.98, +8.37,-9.98, +5.58,-6.65, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.67,0.39, +2.19,1.53, +3.92,2.7, +3.87,3.25, +4.57,4.77, +4.84,6.43, +6.04,7.37, +6.41,6.4, +4.24,1.16, +2.17,-4.72, +1.08,-7.01, +0.55,-7.73, +0.12,-6.99, +-0,-6.03, +-0.53,-4.68, +-0.86,-2.78, +-1.32,-1.54, +-2.16,-0.87, +-2.55,-0.05, +-3.03,0.32, +-3.46,0.44, +-3.44,0.96, +-3.84,1.32, +-3.82,1.91, +-3.65,2.63, +-3.04,2.71, +-2.71,3.04, +-3.92,3.89, +-4.53,3.96, +-4.37,4.12, +-4.52,4.14, +-5.36,4.5, +-4.93,4.93, +-4.59,5.26, +-4.5,5.36, +-3.89,5.92, +-3.98,6.9, +-3.36,7.2, +-2.87,7.88, +-3.06,8.4, +-3.37,8.26, +-4.12,7.91, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-3.94,8.1, +-3.82,9.17, +-3.94,10.84, +-4.23,11.63, +-2.68,13.05, +-2.71,13.82, +-2.62,14.85, +-3.06,15.49, +-3.55,15.15, +-2.77,15.7, +-2.91,16.52, +-2.58,16.32, +-2.02,16.54, +-2.72,17.69, +-3.14,17.8, +-3.14,17.8, +-6.35,17.44, +-6.57,17.04, +-8.52,14.76, +-10.47,13.24, +-10.87,12.95, +-10.87,12.95, +-10.81,12.89, +-11.69,11.07, +-13.56,7.83, +-11.72,9.27, +-12.96,7.48, +-12.96,7.48, +-12.96,7.48, +-12.31,7.11, +-12.64,5.89, +-13.3,4.84, +-14.74,2.6, +-14.32,2.52, +-13.89,2.45, +-13.92,2.45, +-14.1,2.49, +-14.05,2.17, +-14.34,1.6, +-13.67,0, +-13.51,0, +-12.7,0, +-12.05,0, +-12.33,0, +-11.91,-1.67, +-11.23,-1.98, +-10.91,-1.92, +-10.56,-2.48, +-9.32,-3.39, +-8.29,-3.02, +-7.99,-2.91, +-7.26,-2.64, +-6.76,-3.9, +-6.62,-3.82, +-6.48,-3.74, +-8.03,-6.43, +-8.19,-6.87, +-8.74,-5.42, +-8.59,-4.96, +-8.59,-5.29, +-8.31,-6.97, +-8.19,-6.87, +-8.49,-7.12, +-7.59,-6.37, +-7.22,-6.06, +-6.02,-6.57, +-4.36,-6.64, +-3.51,-7.12, +-2.05,-7.67, +-0.65,-7.67, +0.98,-8.21, +2.68,-8.5, +4.09,-8.99, +5.77,-8.55, +7.12,-8.49, +8.26,-7.35, +8.53,-7.99, +7.12,-8.49, +7.12,-8.49, +6.86,-8.67, +5.54,-9.6, +5.94,-9.32, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.57,-9.02, +7.75,-9.23, +7.75,-9.23, +8.28,-9.87, +8.37,-9.98, +8.37,-9.98, +6.09,-7.26, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.04,0.87, +1.9,3.04, +2.13,5.25, +2.06,6.89, +1.06,4.42, +0.55,-0.97, +0.62,-5.96, +1.34,-7.58, +1.38,-7.85, +0.76,-6.93, +0,-6.51, +-0.26,-5.23, +-0.97,-3.77, +-1.69,-2.56, +-2.38,-1.67, +-2.71,-1.37, +-3.09,-1.44, +-3.66,-0.96, +-4.15,-0.46, +-4.38,0.26, +-3.96,0.94, +-3.49,1.15, +-3.23,1.71, +-3.4,2.17, +-4.05,2.75, +-5.22,3.01, +-5.3,3.46, +-5.74,3.76, +-5.75,3.94, +-5.52,4.28, +-5.36,4.5, +-4.74,5.11, +-4.81,5.73, +-4.62,6.46, +-3.98,6.9, +-3.62,7.34, +-3.06,8.4, +-3.53,8.18, +-4.63,7.63, +-4.61,7.64, +-4.61,7.64, +-4.47,7.74, +-4.55,7.88, +-4.96,8.59, +-4.28,9.47, +-3.88,10.66, +-4.03,11.8, +-3.78,12.75, +-3.66,13.62, +-4.5,13.56, +-5.05,13.86, +-5.23,14.37, +-3.94,14.76, +-2.71,15.38, +-2.88,16.34, +-2.99,16.94, +-2.99,16.97, +-4.36,16.81, +-6.13,16.83, +-6.67,15.48, +-8.46,14.65, +-9.88,12.91, +-10.32,12.29, +-10.73,12.79, +-10.66,12.71, +-11.31,11.32, +-13.52,7.81, +-12.96,7.48, +-12.96,7.48, +-12.96,7.48, +-12.96,7.48, +-13.01,7.01, +-13.15,4.79, +-13.15,4.79, +-13.64,2.95, +-13.62,2.4, +-12.83,2.26, +-12.86,1.88, +-13.85,0.3, +-14.54,0, +-14,0, +-13.67,0, +-13.19,0, +-13.02,0, +-12.31,-0.7, +-11.99,-0.7, +-12.06,-1.33, +-11.46,-2.02, +-10.8,-0.93, +-9.87,-2.24, +-10.49,-1.13, +-10.41,-0.76, +-9.86,-0.65, +-9.28,-1.64, +-8.81,-1.55, +-7.97,-1.41, +-7.85,-1.38, +-7.94,-4.02, +-8.23,-5.25, +-8.42,-5.19, +-8.45,-4.88, +-8.59,-4.96, +-8.39,-6.76, +-7.82,-6.56, +-8.34,-7, +-7.59,-6.37, +-7.04,-5.9, +-5.88,-6.35, +-4.15,-6.79, +-2.73,-7.49, +-1.69,-8.27, +-0.4,-7.94, +1.13,-8.67, +2.72,-8.78, +4.43,-8.83, +5.68,-8.55, +7.12,-8.49, +8.15,-7.46, +8.03,-7.58, +7.12,-8.49, +7.12,-8.49, +5.8,-9.41, +5.54,-9.6, +6.69,-8.79, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.48,-8.91, +7.75,-9.23, +7.85,-9.36, +8.16,-9.73, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +6.28,-7.48, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.63,1.07, +-1.36,4.21, +-1.44,5.19, +-2.62,1.55, +-1.91,-4.58, +-0.42,-7.39, +1.42,-8.06, +1.5,-8.49, +1.29,-7.3, +1.1,-6.25, +0.15,-5.32, +-0.49,-4.47, +-1.75,-3.67, +-2.45,-2.65, +-3.23,-2.71, +-3.77,-3.16, +-4.2,-2.57, +-4.73,-1.72, +-4.1,-0.72, +-4.47,-0.29, +-4.07,0.12, +-4,0.71, +-3.95,0.97, +-4.76,1.63, +-6.22,2.65, +-6.35,2.88, +-6.32,2.95, +-6.06,3.5, +-6.06,3.5, +-5.83,3.83, +-5.77,4.85, +-5.42,4.95, +-5.12,6.11, +-4.55,6.5, +-4.23,7.32, +-4.47,7.74, +-4.43,7.67, +-5.57,6.98, +-5.75,6.85, +-5.75,6.85, +-5.74,7.15, +-5.85,7.83, +-5.27,8.37, +-5.12,8.87, +-4.66,10, +-4.04,11.1, +-4.27,11.73, +-4.49,12.34, +-4.75,13.05, +-4.79,13.15, +-4.9,13.46, +-4.84,13.3, +-4.77,13.94, +-2.79,15.8, +-2.83,16.06, +-2.91,16.49, +-5.12,16.22, +-5.9,16.2, +-7.36,15.44, +-8.35,14.46, +-9.34,13.01, +-10.35,12.33, +-10.45,12.46, +-10.25,12.21, +-11.03,11.42, +-12.83,7.97, +-12.96,7.48, +-12.96,7.48, +-12.96,7.48, +-13.24,6.89, +-13.84,5.35, +-13.15,4.79, +-13.26,4.39, +-13.78,2.43, +-13.15,2.32, +-12.83,2.26, +-12.89,1.51, +-13.58,0, +-14.16,0, +-14,0, +-14,0, +-13.02,0, +-12.59,0, +-11.24,0, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0, +-9.91,0, +-10.69,0, +-11.08,0, +-9.84,-0.86, +-9.24,-1.63, +-8.81,-1.55, +-7.99,-1.41, +-7.85,-1.38, +-7.78,-3.44, +-7.74,-4.47, +-8.31,-4.8, +-7.93,-4.58, +-8.59,-4.96, +-8.24,-5.79, +-7.59,-6.37, +-7.4,-6.21, +-7.18,-6.03, +-6.74,-5.66, +-5.29,-5.94, +-4.11,-6.78, +-2.45,-8.23, +-1.45,-8.21, +-0,-8.29, +1.36,-8.82, +2.97,-8.93, +4.43,-8.83, +5.68,-8.55, +7.12,-8.49, +7.29,-8.32, +7.8,-7.8, +7.12,-8.49, +6.53,-8.9, +5.54,-9.6, +6.24,-9.1, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.28,-8.67, +7.75,-9.23, +7.75,-9.23, +8.27,-9.85, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.27,-9.85, +6.12,-7.3, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.13,-0.17, +-4.67,0.93, +-4.65,1.22, +-4.67,-2.32, +-2.58,-6.27, +-0.52,-8.41, +1.04,-8.85, +1.55,-8.81, +1.42,-8.06, +1.12,-6.36, +0.7,-5.24, +-0.56,-4.84, +-1.61,-3.74, +-2.62,-3.52, +-3.61,-4.12, +-4.53,-3.96, +-4.66,-3.63, +-4.85,-2.49, +-4.9,-1.78, +-5.03,-1.52, +-4.31,-0.86, +-4.36,-0.53, +-4.9,-0.13, +-5.64,0.7, +-6.79,1.61, +-6.72,1.87, +-6.61,2.26, +-6.58,2.39, +-6.28,3.03, +-6.06,3.5, +-6.08,4.31, +-6.11,5.12, +-6,5.23, +-5.22,6.01, +-5.06,6.54, +-4.68,7.59, +-4.9,7.45, +-6.23,6.83, +-6.43,6.77, +-6.44,7.07, +-6.46,7.31, +-6.37,7.59, +-6.37,7.59, +-5.67,7.95, +-5.35,9.26, +-4.71,9.94, +-4.87,10.82, +-4.57,11.79, +-5.48,11.76, +-6.27,11.39, +-5.86,11.7, +-6.1,12.18, +-6.84,11.84, +-2.66,15.06, +-2.77,15.7, +-4.82,15.04, +-5.53,15.18, +-5.78,15.89, +-7.55,14.76, +-8.13,14.08, +-9.87,12.48, +-10.25,12.21, +-10.25,12.21, +-10.25,12.21, +-10.9,11.56, +-12.71,7.84, +-12.96,7.48, +-13.07,7.25, +-13.82,5.64, +-14.07,5.12, +-13.61,4.95, +-13.15,4.79, +-13.47,3.61, +-13.78,2.43, +-13.26,2.34, +-12.83,2.26, +-12.89,1.51, +-13.67,0, +-14,0, +-14,0, +-13.78,0, +-13.02,0, +-11.89,0, +-11.08,0, +-11.08,0, +-11.08,0, +-10.93,0, +-11.08,0, +-9.91,0, +-10.27,-0.32, +-10.52,-1.37, +-9.76,-1.72, +-9.76,-1.72, +-8.69,-1.53, +-8.01,-1.41, +-7.85,-1.38, +-7.91,-3.78, +-7.65,-4.42, +-7.56,-4.36, +-7.74,-4.47, +-8.31,-4.8, +-7.73,-5.06, +-7.26,-6.09, +-6.99,-5.86, +-6.85,-5.75, +-6.19,-5.5, +-5.12,-6.11, +-3.64,-7.3, +-2.29,-8.51, +-1.01,-8.16, +-0,-8.94, +1.55,-8.81, +2.95,-9.31, +4.57,-8.77, +5.94,-9, +7.12,-8.49, +7.12,-8.49, +7.58,-8.03, +6.86,-8.67, +5.54,-9.6, +6.42,-8.98, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +6.95,-8.61, +6.5,-8.69, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.3,-8.7, +7.75,-9.23, +7.93,-9.45, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.29,-9.88, +7.75,-9.23, +5.37,-6.4, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.92,-2.44, +-5.56,-3.54, +-4.33,-6.73, +-2.19,-8.64, +-0.19,-9.47, +0.86,-8.87, +1.55,-8.81, +1.44,-8.17, +1.19,-6.77, +0.23,-5.36, +-0.59,-5, +-1.91,-4.67, +-2.77,-4.21, +-3.87,-4.62, +-5.04,-4, +-5.81,-3.49, +-5.31,-3.07, +-4.79,-2.56, +-5.41,-2.61, +-5.08,-2.01, +-4.81,-1.49, +-4.9,-1.16, +-5.87,-0.27, +-7.26,0.68, +-7.37,1.3, +-7.37,1.3, +-7.2,2.2, +-7.18,2.62, +-7.01,3, +-6.81,3.93, +-6.9,3.98, +-6.24,4.93, +-5.94,5.29, +-6.17,6.28, +-5.75,6.85, +-6.7,6.74, +-7.59,6.37, +-7.59,6.37, +-7.59,6.37, +-7.69,6.46, +-7.32,6.64, +-6.92,7.05, +-5.96,7.1, +-6.03,7.64, +-4.96,8.59, +-5.39,9.34, +-5.54,9.6, +-5.89,10.21, +-6.52,10.09, +-7.15,10.24, +-7.44,10.63, +-7.06,11.3, +-4.13,13.88, +-5.16,14.17, +-5.23,14.37, +-5.29,14.52, +-5.45,14.98, +-7.92,13.71, +-7.97,13.8, +-10.11,12.05, +-10.25,12.21, +-10.25,12.21, +-10.25,12.21, +-10.72,11.22, +-12.96,7.48, +-12.96,7.48, +-13.38,6.6, +-14.07,5.12, +-14.07,5.12, +-13.42,4.24, +-13.57,3.22, +-13.78,2.43, +-13.78,2.43, +-13.15,2.32, +-12.9,1.41, +-13,0.28, +-13.46,0, +-14.12,0, +-14,0, +-13.51,0, +-13.02,0, +-11.95,0, +-11.08,0, +-11.08,0, +-11.08,0, +-10.3,0, +-10.41,0, +-9.82,-1.03, +-10.31,-1.43, +-10.42,-1.84, +-9.76,-1.72, +-9.76,-1.72, +-8.93,-1.57, +-8.7,-1.53, +-8.25,-1.98, +-7.4,-3.68, +-7.18,-4.15, +-7.38,-4.26, +-7.38,-4.26, +-7.56,-4.36, +-7.65,-4.42, +-6.48,-5.44, +-6.42,-5.39, +-6.59,-5.82, +-5.41,-6.05, +-4.81,-6.45, +-3.7,-7.57, +-1.78,-7.99, +-0.23,-8.76, +0.26,-8.92, +1.6,-9.08, +3.11,-9.39, +4.06,-9, +5.25,-9.09, +6.86,-8.67, +7.12,-8.49, +6.92,-8.63, +5.72,-9.47, +5.8,-9.41, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +6.28,-8.37, +5.42,-8.74, +7.12,-8.49, +7.23,-8.61, +7.54,-8.98, +7.66,-9.13, +7.75,-9.23, +7.75,-9.23, +8.16,-9.73, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +7.98,-9.51, +7.75,-9.23, +5.48,-6.53, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.19,-1.2, +-5.62,-6.85, +-5.12,-7.56, +-3.78,-9.56, +-1.72,-9.76, +-0,-9.55, +0.17,-8.93, +1.19,-8.73, +1.38,-7.85, +0.69,-6.8, +-0,-5.05, +-1.12,-4.91, +-1.73,-4.75, +-2.97,-4.72, +-4.46,-5.1, +-5.25,-4.61, +-5.32,-3.71, +-4.69,-3.48, +-4.98,-3.73, +-5.7,-3.45, +-5.59,-3.23, +-5.16,-2.37, +-5.66,-2.06, +-6.42,-1.24, +-7.97,0, +-7.96,0.15, +-7.86,1.23, +-7.85,1.38, +-7.61,2.28, +-7.49,2.73, +-7.41,2.91, +-7.16,3.43, +-6.9,3.98, +-6.65,4.73, +-6.85,5.75, +-6.67,5.93, +-6.88,6.86, +-7.59,6.37, +-8.24,6.92, +-8.49,7.12, +-8.04,6.75, +-7.59,6.37, +-7.59,6.37, +-6.61,5.99, +-6.32,6.73, +-6.37,7.59, +-6.37,7.59, +-7,8.34, +-6.86,8.67, +-7.4,8.82, +-8.02,8.96, +-8.55,8.74, +-7.87,9.65, +-4.79,13.15, +-5.01,13.76, +-4.95,13.61, +-5.12,14.07, +-6.67,13.94, +-7.76,13.44, +-7.62,13.2, +-9.41,11.79, +-9.98,11.89, +-9.97,11.88, +-9.9,11.8, +-10.93,10.17, +-12.96,7.48, +-12.96,7.48, +-13.51,6.3, +-14.31,4.2, +-14.35,3.32, +-13.69,2.77, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.3,0.52, +-13.02,0, +-13.02,0, +-13.83,0, +-14.16,0, +-14,0, +-13.35,0, +-13.13,0, +-11.75,-2.07, +-10.96,-1.37, +-10.91,-1.92, +-10.94,-1.6, +-10.27,-1.81, +-9.89,-1.74, +-9.76,-1.72, +-10.26,-1.81, +-9.95,-1.76, +-9.76,-1.72, +-9.76,-1.72, +-9.02,-1.59, +-8.81,-1.55, +-8.47,-2.81, +-7.4,-3.68, +-7.18,-4.15, +-6.71,-4.47, +-6.78,-4.58, +-6.77,-4.17, +-7.04,-4.07, +-6.23,-5.23, +-5.94,-5.29, +-5.12,-6.11, +-4.83,-6.71, +-3.69,-6.86, +-2.94,-7.39, +-1.62,-8.07, +-0,-8.94, +0.94,-9.4, +1.93,-9.71, +3.39,-9.32, +3.78,-9.13, +4.96,-8.59, +6.59,-8.86, +6.79,-8.09, +6.34,-8.75, +5.54,-9.6, +6.07,-9.23, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7,-8.34, +7,-8.34, +7.3,-8.7, +7.54,-8.98, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.85,-9.36, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +7.82,-9.33, +7.85,-9.36, +5.23,-6.24, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.83,-1.43, +-4.84,-9.53, +-4.17,-10.83, +-2.5,-10.74, +-1.92,-10.91, +-0.24,-10.09, +-0,-8.94, +0.84,-8.31, +0.89,-7.68, +0.27,-6.97, +-0.43,-5.66, +-1.3,-5.24, +-2.32,-5.36, +-3.01,-5.22, +-3.84,-4.78, +-4.66,-4.74, +-4.78,-4.32, +-4.44,-4.36, +-5.36,-4.5, +-5.61,-4.71, +-6.09,-4.43, +-6.25,-3.79, +-5.64,-3.12, +-6.1,-2.2, +-8.7,-1.34, +-8.92,-0.22, +-8.9,0.52, +-8.49,1.5, +-8.17,1.44, +-8.01,2.04, +-7.93,2.47, +-7.95,2.89, +-7.89,3.39, +-7.74,4.47, +-7.93,4.77, +-7.65,5.92, +-7.81,6.06, +-8.07,6.03, +-9.04,6.33, +-8.96,6.44, +-8.91,6.33, +-8.04,5.74, +-7.92,5.9, +-7,5.54, +-6.85,5.75, +-7.35,6.16, +-6.98,6.98, +-7.03,7.48, +-7.58,8.03, +-8.29,7.32, +-8.74,7.33, +-9.33,7.82, +-9.12,8.71, +-7.1,10.83, +-7.25,11.12, +-8.26,10.99, +-9,10.72, +-9,10.72, +-9.61,10.23, +-9.64,10.32, +-10.42,9.78, +-11.62,7.89, +-12.21,8.55, +-12.46,8.2, +-13.33,6.7, +-14.07,5.12, +-14.07,5.12, +-14.14,4.84, +-14.58,2.57, +-13.9,2.45, +-13.78,2.43, +-13.78,2.43, +-13.85,1.62, +-13.97,0.27, +-13.35,0, +-13.02,0, +-13.02,0, +-13.38,-2.09, +-13.22,-1.51, +-12.68,-1.43, +-11.87,-2.09, +-11.87,-2.09, +-11.87,-2.09, +-11.15,-1.97, +-10.91,-1.92, +-10.66,-1.88, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.61,-2.28, +-8.47,-2.81, +-8.47,-2.81, +-8.4,-3.06, +-7.06,-5.07, +-6.46,-4.99, +-6.11,-5.12, +-5.73,-4.81, +-6.11,-5.12, +-5.86,-4.91, +-5.44,-4.76, +-5.06,-5.56, +-4.41,-6.6, +-3.81,-6.99, +-2.94,-7.39, +-2.05,-7.67, +-0.91,-8.5, +0.14,-9.17, +1.72,-9.76, +2,-9.69, +3.39,-9.32, +4.1,-9.31, +3.64,-8.03, +5.27,-8.49, +4.72,-8, +5.21,-9.03, +5.54,-9.6, +5.54,-9.6, +6.33,-9.04, +6.98,-8.59, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.2,-8.58, +7.67,-9.14, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.3,-9.89, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.27,-9.85, +7.75,-9.23, +8.09,-9.64, +5.98,-7.13, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.26,-3.47, +-3.91,-10.75, +-2.63,-11.22, +-1.98,-11.23, +-1.92,-10.91, +-0.43,-10.39, +-0,-8.94, +0.17,-8.28, +-0,-7.69, +-0.61,-6.94, +-1.05,-5.93, +-1.95,-5.69, +-2.17,-5.61, +-3.07,-5.31, +-3.57,-5.84, +-4.33,-5.48, +-4.6,-5.48, +-4.5,-5.36, +-4.94,-5.68, +-5.67,-5.56, +-5.98,-5.25, +-6.11,-5.12, +-6.07,-3.77, +-7.01,-3.1, +-8.94,-2.31, +-9.16,-1.26, +-9.21,0, +-9.3,0.67, +-8.81,1.55, +-8.81,1.55, +-9.13,1.61, +-9.04,1.94, +-8.76,2.98, +-8.78,3.4, +-8.83,4.43, +-8.75,5.05, +-9.29,5.68, +-9.78,5.65, +-9.88,5.7, +-9.6,5.54, +-9.6,5.54, +-8.84,5.1, +-8.59,4.96, +-7.74,4.47, +-7.35,5.04, +-7.28,5.88, +-7.59,6.37, +-7.59,6.37, +-7.89,6.62, +-7.99,6.38, +-9.01,5.7, +-9.57,5.77, +-10.58,6.68, +-8.37,9.98, +-8.37,9.98, +-8.37,9.98, +-9.22,9.43, +-10.45,9.12, +-10.72,9, +-10.95,8.66, +-11.89,7.33, +-12.12,7, +-13.41,6.11, +-13.94,5.38, +-14.07,5.12, +-14.07,5.12, +-14.07,5.12, +-14.4,3.86, +-14.19,2.5, +-13.78,2.43, +-13.8,2.23, +-13.87,1.39, +-14,0, +-14,0, +-13.19,0, +-13.02,0, +-13,-0.25, +-13.32,-1.55, +-12.83,-2.26, +-12.19,-2.15, +-11.87,-2.09, +-11.87,-2.09, +-11.76,-2.07, +-11.44,-2.02, +-10.91,-1.92, +-10.72,-1.89, +-9.76,-1.72, +-9.7,-1.96, +-9.61,-2.28, +-9.61,-2.28, +-9.48,-2.76, +-9.16,-3.34, +-9.16,-3.34, +-8.4,-3.06, +-8.4,-3.06, +-8.34,-3.19, +-6.66,-5.3, +-6.11,-5.12, +-5.98,-5.02, +-5.07,-4.79, +-5.83,-5.26, +-5.26,-4.73, +-4.62,-5.24, +-3.52,-5.06, +-3.61,-6.72, +-2.88,-7.42, +-2.22,-7.62, +-1.38,-7.85, +-0.42,-8.77, +0.49,-9.73, +1.72,-9.76, +2,-9.69, +3.45,-9.47, +3.75,-10.29, +3.4,-8.2, +2.73,-7.49, +3.06,-8.4, +3.94,-8.95, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.44,-8.96, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.48,-8.91, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.96,-9.48, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.06,-9.6, +7.82,-9.32, +8.3,-9.89, +6.12,-6.12, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.39,-2.19, +-1.72,-8.5, +-2.22,-9.03, +-2.48,-8.85, +-1.32,-8.46, +-0,-9.59, +-0,-8.82, +-0,-7.97, +-0,-7.54, +-0.97,-6.91, +-1.22,-6.14, +-2.06,-5.66, +-2.06,-5.66, +-3.45,-5.97, +-3.55,-6.15, +-3.95,-6.53, +-4.41,-5.95, +-4.93,-6.24, +-4.93,-6.24, +-5.39,-6.42, +-5.99,-6.61, +-6.77,-5.68, +-6.7,-5.22, +-7.73,-4.21, +-9.32,-3.39, +-9.69,-2, +-9.87,-0.49, +-9.91,0, +-9.9,0.17, +-9.87,0.47, +-9.84,0.86, +-9.76,1.72, +-10.15,1.79, +-9.9,2.72, +-9.92,3.61, +-10.05,4.57, +-9.6,5.54, +-9.88,5.7, +-10.44,6.03, +-9.72,5.61, +-9.6,5.54, +-8.92,5.15, +-8.36,4.82, +-7.74,4.47, +-7.74,4.47, +-7.74,4.47, +-8.25,5.01, +-8.37,5.27, +-7.82,4.74, +-8.04,4.89, +-8.59,4.96, +-9.73,5.25, +-10.45,5.23, +-9.75,8.18, +-9.83,8.52, +-9.8,8.55, +-9.98,8.37, +-10.1,8.48, +-11.19,8.33, +-12.12,7, +-12.12,7, +-12.24,6.75, +-14.33,5.21, +-14.07,5.12, +-14.07,5.12, +-14.52,3.44, +-14.74,2.6, +-14.74,2.6, +-13.93,2.15, +-13.89,1.22, +-13.96,0.41, +-14,0, +-14,0, +-13.78,0, +-12.94,-1.01, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.25,-2.16, +-11.81,-2.32, +-11.45,-2.6, +-10.99,-2.26, +-11.37,-2.71, +-10.77,-2.46, +-10.58,-3.17, +-10.2,-3.08, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-8.4,-3.06, +-8.18,-3.53, +-7.74,-4.47, +-6.27,-5.21, +-5.12,-6.11, +-4.57,-5.44, +-4.5,-5.36, +-4.59,-5.47, +-4.5,-5.36, +-4.17,-5.59, +-3.42,-5.92, +-2.6,-6.63, +-2.31,-7.6, +-1.59,-8.13, +-0.46,-8.09, +-0,-8.94, +1.43,-9.79, +1.74,-9.89, +3.43,-9.97, +3.79,-10.41, +4.08,-10.28, +3.43,-8.02, +2.73,-7.49, +3.06,-8.4, +3.99,-8.85, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.8,-9.41, +6.92,-8.63, +7.12,-8.49, +7.12,-8.49, +7.48,-8.91, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.82,-9.32, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +7.82,-9.32, +8.16,-9.73, +9.37,-8.97, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.2,-2.31, +-1.3,-7.37, +-1.31,-7.42, +-2.06,-6.89, +-0.8,-8.69, +-0,-9.05, +-0,-8.53, +-0,-7.97, +-0.61,-7.27, +-1.16,-6.57, +-1.81,-6.4, +-2.28,-6.27, +-2.4,-6.25, +-2.81,-6.38, +-3.9,-6.76, +-4.05,-7.02, +-3.99,-6.9, +-4.09,-7.09, +-4.45,-7.36, +-5.43,-7.07, +-5.75,-6.85, +-6.25,-5.89, +-6.74,-5.66, +-7.95,-5.19, +-9.39,-4.76, +-9.78,-3.15, +-10.34,-1.82, +-11,-0.96, +-10.5,0, +-11.08,0, +-11.08,0, +-11.03,0.52, +-10.91,1.92, +-10.91,1.92, +-10.66,2.86, +-10.41,3.79, +-10.45,4.73, +-10.14,4.62, +-10.63,5.35, +-10.34,5.67, +-9.6,5.54, +-8.59,4.96, +-8.66,4.04, +-8.26,3.37, +-8.04,3.83, +-7.81,4.33, +-7.98,4.61, +-8.59,4.96, +-7.74,4.47, +-8.03,3.87, +-8.87,3.84, +-9.92,3.61, +-10.82,3.94, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-10.78,7.86, +-11.55,7.25, +-12.12,7, +-12.12,7, +-12.46,6.26, +-13.15,4.79, +-14.61,4.34, +-14.23,4.49, +-14.57,3.23, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-13.5,-1.97, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.72,-2.24, +-11.67,-2.85, +-11.42,-3.78, +-10.87,-3.96, +-10.41,-3.79, +-10.61,-3.86, +-10.41,-3.79, +-10.41,-3.79, +-10.1,-3.68, +-9.68,-3.52, +-9.32,-3.39, +-9.32,-3.39, +-9.23,-3.56, +-9.18,-3.68, +-9.07,-3.91, +-7.96,-4, +-7.74,-4.47, +-7.74,-4.47, +-5.61,-5.88, +-4.43,-6.16, +-3.5,-6.06, +-3.5,-6.06, +-3.81,-5.45, +-3.64,-5.96, +-3.25,-6.18, +-2.76,-6.4, +-2.18,-7.01, +-1.38,-7.85, +-1.08,-8.12, +-0,-8.94, +0.29,-9.24, +1.74,-9.89, +2.3,-10.81, +3.79,-10.41, +3.79,-10.41, +4.28,-9.97, +3.47,-7.85, +2.89,-7.95, +3.06,-8.4, +3.77,-8.74, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.86,-8.67, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.64,-9.11, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.06,-9.6, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +7.82,-9.33, +8.64,-9.71, +9.98,-8.37, +7.48,-6.28, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.14,-4.65, +-0.81,-6.93, +-1.22,-6.89, +-1.24,-7.05, +-0.52,-7.89, +-0,-7.97, +-0,-7.21, +-0.68,-6.94, +-1.22,-6.89, +-1.8,-6.73, +-2.39,-6.58, +-2.39,-6.58, +-2.39,-6.58, +-2.47,-6.78, +-3.36,-7.2, +-3.66,-7.65, +-3.83,-7.43, +-4.47,-7.74, +-4.54,-7.86, +-5.3,-7.42, +-5.75,-6.85, +-6.61,-5.99, +-6.85,-5.75, +-7.73,-5.73, +-9.6,-5.54, +-10.3,-4.04, +-10.58,-3.17, +-10.91,-1.92, +-10.93,-1.71, +-10.95,-1.44, +-11.31,-1.02, +-11.57,0, +-11.79,0.26, +-11.92,1.52, +-11.87,2.09, +-11.45,3.67, +-11.02,4.01, +-10.41,3.79, +-10.87,3.96, +-10.88,4.22, +-9.68,4.43, +-9.23,3.56, +-9.01,3.28, +-8.4,3.06, +-8.4,3.06, +-8.4,3.06, +-8.56,3.48, +-9.15,3.74, +-8.65,3.54, +-8.4,3.06, +-8.4,3.06, +-9.53,3.47, +-10.72,3.9, +-10.79,7.21, +-10.85,7.13, +-11.28,6.51, +-12.04,6.95, +-12.12,7, +-12.29,6.63, +-12.81,5.52, +-13.15,4.79, +-13.15,4.79, +-15.06,2.66, +-14.74,2.6, +-14.74,2.6, +-14.8,1.95, +-14.74,2.6, +-14.82,1.73, +-14,0, +-14,0, +-14,0, +-14,0, +-13.96,-0.41, +-13.46,-2.37, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.42,-2.54, +-11.33,-4.12, +-11.33,-4.12, +-10.92,-3.97, +-10.41,-3.79, +-10.41,-3.79, +-10.41,-3.79, +-10.41,-3.79, +-9.86,-3.59, +-8.91,-4.26, +-9.02,-4.33, +-8.77,-4.57, +-8.59,-4.96, +-8.45,-4.88, +-7.74,-4.47, +-7.74,-4.47, +-7.55,-4.75, +-6.95,-5.61, +-5.17,-5.93, +-3.5,-6.06, +-3.5,-6.06, +-3.5,-6.06, +-2.91,-5.73, +-2.76,-6.4, +-2.61,-6.47, +-2.45,-6.73, +-1.38,-7.85, +-1.15,-7.87, +-0,-7.97, +0,-8.07, +1.23,-8.94, +1.82,-10.34, +1.74,-9.89, +1.72,-7.76, +3.26,-8.95, +3.01,-8.27, +3.12,-8.02, +3.06,-8.4, +3.06,-8.4, +3.89,-8.8, +5.54,-9.6, +5.54,-9.6, +6.07,-9.23, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.64,-9.11, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.06,-9.6, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.16,-9.73, +7.9,-9.42, +9.44,-8.91, +9.98,-8.37, +6.98,-5.86, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,-4.67, +-0,-7, +-0.95,-6.92, +-1.22,-6.89, +-1.22,-6.89, +-0.63,-7.55, +-0,-7.81, +-0.81,-6.93, +-1.22,-6.89, +-1.48,-6.82, +-2.26,-6.61, +-2.39,-6.58, +-2.39,-6.58, +-2.39,-6.58, +-2.67,-7.34, +-2.91,-7.41, +-3.15,-7.29, +-3.56,-7.81, +-3.81,-7.84, +-4.48,-8.06, +-5.32,-7.15, +-5.75,-6.85, +-6.85,-5.75, +-7.13,-5.73, +-7.99,-5.55, +-8.98,-6.42, +-9.6,-5.54, +-10.28,-4.08, +-10.5,-3.48, +-10.77,-2.46, +-11.12,-2.39, +-11.87,-2.09, +-11.91,-1.63, +-12.03,-0.26, +-12.05,0, +-11.87,2.09, +-11.37,2.87, +-10.37,3.77, +-10.41,3.79, +-10.41,3.79, +-10.23,3.72, +-9.32,3.39, +-9.32,3.39, +-9.16,3.34, +-8.4,3.06, +-8.4,3.06, +-8.4,3.06, +-8.4,3.06, +-9.09,3.31, +-9.32,3.39, +-8.79,3.2, +-9.11,2.5, +-9.56,2.46, +-10.19,3.05, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.84,6.84, +-12.68,5.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.26,4.39, +-15.06,2.66, +-14.74,2.6, +-14.87,1.16, +-14.97,0, +-14.89,0.95, +-14.89,0.87, +-14.32,0, +-14,0, +-14,0, +-13.93,-0.81, +-13.81,-2.16, +-13.68,-2.41, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.32,-4.14, +-11.63,-4.23, +-11.33,-4.12, +-11.33,-4.12, +-10.54,-3.84, +-10.41,-3.79, +-10.28,-4.08, +-9.95,-4.79, +-9.87,-4.96, +-8.99,-5.19, +-8.59,-4.96, +-8.59,-4.96, +-8.59,-4.96, +-8.59,-4.96, +-7.98,-4.61, +-7.5,-4.82, +-7.15,-5.32, +-6.85,-5.75, +-6.48,-6.12, +-4.25,-6.32, +-3.18,-6.21, +-2.73,-6.42, +-2.64,-6.46, +-2.35,-6.46, +-2.2,-6.63, +-2.1,-6.8, +-1.91,-7.56, +-1.38,-7.85, +-0.15,-8.17, +-0,-8.62, +0.62,-8.02, +1.52,-8.65, +1.7,-9.64, +1.95,-9.43, +1.55,-7.8, +2.59,-7.53, +2.73,-7.49, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +4.71,-9.2, +5.54,-9.6, +5.54,-9.6, +6.9,-8.65, +7.12,-8.49, +7.12,-8.49, +7.03,-8.38, +7.12,-8.49, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.96,-9.48, +8.29,-9.88, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +7.85,-9.36, +7.82,-9.33, +9.17,-9.17, +9.98,-8.37, +7.83,-6.57, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.36,-2.77, +0,-7, +-0,-7, +-0.2,-6.98, +-1.22,-6.89, +-1.22,-6.89, +-1.42,-7.28, +-1.31,-7.42, +-1.18,-6.68, +-1.22,-6.89, +-1.8,-6.73, +-2.39,-6.58, +-2.39,-6.58, +-2.39,-6.58, +-3.36,-6.36, +-3.38,-6.12, +-3.39,-6.91, +-3.78,-7, +-3.86,-7.14, +-3.59,-7.55, +-4.31,-7.82, +-4.98,-7.39, +-6.41,-6.87, +-7.43,-6.23, +-7.92,-5.9, +-8.39,-5.91, +-8.49,-7.12, +-9.23,-6.07, +-9.6,-5.54, +-10.05,-4.57, +-10.41,-3.79, +-10.72,-3.9, +-11.51,-3.45, +-11.87,-2.09, +-11.4,-1.32, +-11.4,0, +-11.35,0.66, +-11.12,1.96, +-10.21,2.29, +-10.36,2.48, +-10.5,3.48, +-10.05,3.66, +-9.32,3.39, +-9.32,3.39, +-9.32,3.39, +-9.16,3.34, +-8.4,3.06, +-8.4,3.06, +-8.53,3.11, +-9.21,3.35, +-9.32,3.39, +-9.51,2.65, +-9.76,1.72, +-10.34,1.82, +-10.91,1.92, +-11.83,5.34, +-11.92,5.14, +-12.71,5.47, +-13.04,5.03, +-13.15,4.79, +-13.15,4.79, +-13.36,4, +-13.6,3.1, +-13.78,2.43, +-14.95,1.39, +-14.52,1.39, +-15.45,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.52,-1.41, +-13.81,-2.16, +-13.78,-2.43, +-13.78,-2.43, +-13.78,-2.43, +-13.15,-2.32, +-12.83,-2.26, +-12.45,-3.66, +-12.43,-3.72, +-11.78,-4.29, +-11.33,-4.12, +-11.33,-4.12, +-11.33,-4.12, +-10.22,-4.49, +-9.87,-4.96, +-9.69,-5.35, +-9.6,-5.54, +-9.6,-5.54, +-8.92,-5.15, +-8.84,-5.1, +-8.59,-4.96, +-8.48,-5.11, +-8.06,-5.52, +-7.07,-5.43, +-6.85,-5.75, +-6.85,-5.75, +-6.67,-5.93, +-5.75,-6.85, +-3.71,-6.93, +-2.62,-7.18, +-2.54,-7.43, +-2,-7.52, +-1.3,-7.37, +-1.36,-7.69, +-1.38,-7.85, +-1.38,-7.85, +-0.62,-7.92, +-0.69,-8.23, +-0.46,-8.42, +0.67,-8.47, +1.11,-8.57, +1.63,-9.22, +1.9,-8.6, +1.38,-7.85, +2.39,-7.58, +2.73,-7.49, +3,-8.25, +3.11,-8.55, +3.06,-8.4, +4.58,-8.97, +5.54,-9.6, +5.54,-9.6, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +6.71,-7.99, +6.42,-8.55, +6.68,-9.59, +7.75,-9.23, +7.75,-9.23, +7.85,-9.36, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.19,-9.76, +7.75,-9.23, +7.75,-9.23, +7.86,-9.37, +9.13,-8.05, +9.23,-7.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.2,-1.15, +1.06,-6.03, +0.41,-6.96, +-0,-7, +-0,-6.71, +-1.08,-6.9, +-1.58,-6.63, +-1.55,-6.63, +-1.22,-6.89, +-1.14,-6.47, +-1.4,-6.4, +-1.89,-6.27, +-2.36,-6.47, +-2.54,-6.97, +-2.76,-6.4, +-3.13,-6.23, +-2.52,-6.52, +-2.73,-7.49, +-2.73,-7.49, +-2.73,-7.49, +-2.85,-7.83, +-3.76,-8.07, +-4.52,-7.31, +-5.91,-6.69, +-7,-5.87, +-7.69,-5.56, +-8.56,-5.88, +-8.58,-7.2, +-8.49,-7.12, +-9.23,-6.07, +-9.6,-5.54, +-9.69,-5.35, +-10.05,-4.57, +-10.41,-3.79, +-10.63,-2.96, +-10.91,-1.92, +-11.02,-0.64, +-11.08,0, +-10.77,1.62, +-10.22,2.02, +-9.87,1.74, +-10.45,1.84, +-10.38,2.41, +-9.32,3.39, +-9.32,3.39, +-9.32,3.39, +-9.32,3.39, +-8.81,3.21, +-8.4,3.06, +-9.52,3.47, +-10.58,3.17, +-10.75,2.55, +-10.84,2.19, +-10.91,1.92, +-10.91,1.92, +-11.53,2.54, +-12.24,4.45, +-12.7,4.62, +-13.15,4.79, +-13.15,4.79, +-13.24,4.45, +-13.64,2.95, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.96,0.41, +-14,0, +-14.54,-1.16, +-14.94,-0.29, +-14.97,0, +-14.44,-1.89, +-13.78,-2.43, +-13.78,-2.43, +-13.78,-2.43, +-13.78,-2.43, +-13.15,-2.32, +-12.7,-2.75, +-12.24,-4.45, +-12.24,-4.45, +-11.33,-4.12, +-11.33,-4.12, +-11.33,-4.12, +-10.73,-5.39, +-9.69,-5.59, +-9.6,-5.54, +-9.6,-5.54, +-9.43,-5.44, +-9.43,-5.44, +-9.44,-5.77, +-8.9,-6.08, +-7.84,-6.02, +-7.59,-6.37, +-7.35,-6.16, +-6.85,-5.75, +-6.67,-5.93, +-6.02,-6.57, +-5.75,-6.85, +-5.61,-6.95, +-3.23,-7.43, +-1.89,-8.05, +-1.61,-7.79, +-1.38,-7.85, +-1.19,-7.87, +-0.92,-7.89, +-1.83,-7.7, +-1.95,-7.66, +-0.99,-7.88, +-1.38,-7.85, +-0.46,-7.93, +-0,-7.97, +-0,-7.97, +0.86,-7.89, +1.41,-8.01, +1.38,-7.85, +2.05,-7.67, +2.73,-7.49, +3.06,-8.4, +3.39,-9.32, +3.27,-8.98, +4.63,-9.17, +5.54,-9.6, +5.54,-9.6, +6.53,-8.9, +6.22,-9.12, +6.77,-8.73, +6.36,-8.67, +5.7,-8.95, +6.86,-9.26, +7.75,-9.23, +7.75,-9.23, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.27,-9.85, +7.85,-9.36, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.99,-8.98, +5.66,-5.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.26,-3.81, +1.6,-6.36, +1.16,-6.57, +0.2,-6.82, +-0,-6.03, +-1.05,-5.93, +-1.25,-6.38, +-1.8,-6.73, +-1.22,-6.89, +-1.22,-6.89, +-2,-6.68, +-2.39,-6.58, +-2.39,-6.58, +-2.39,-6.58, +-3.01,-6.29, +-2.64,-6.46, +-2.45,-6.73, +-2.73,-7.49, +-2.31,-6.93, +-1.89,-7.71, +-2.48,-8.11, +-3.01,-7.83, +-4.1,-7.8, +-5.21,-6.78, +-6.27,-5.87, +-6.85,-5.75, +-7,-5.54, +-7.66,-8.32, +-8.35,-7.26, +-8.49,-7.12, +-8.98,-6.42, +-9.41,-5.8, +-9.6,-5.54, +-10,-4.66, +-9.86,-3.59, +-9.61,-2.28, +-9.78,-1.53, +-9.9,-0.19, +-9.88,0.43, +-9.84,0.86, +-9.81,1.15, +-9.76,1.72, +-9.78,1.72, +-9.4,2.24, +-9.42,3.02, +-9.32,3.39, +-9.32,3.39, +-8.71,3.17, +-9.08,2.42, +-10.19,2.05, +-10.86,2.13, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.46,2.02, +-11.99,2.88, +-12.39,4.51, +-13.05,4.75, +-13.29,4.26, +-13.43,3.74, +-13.7,2.72, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.89,1.22, +-13.97,0.27, +-14,0, +-14,0, +-14.49,-1.73, +-14.85,-1.3, +-14.97,0, +-14.64,-2.2, +-14.13,-2.49, +-13.78,-2.43, +-13.71,-2.69, +-13.47,-3.61, +-13,-3.5, +-12.34,-4.09, +-12.24,-4.45, +-12.24,-4.45, +-11.33,-4.12, +-11.23,-4.33, +-10.82,-5.21, +-10.16,-5.86, +-9.6,-5.54, +-9.6,-5.54, +-9.46,-5.74, +-8.35,-5.97, +-8.74,-6.53, +-8.38,-7.03, +-7.59,-6.37, +-7.59,-6.37, +-7.38,-6.19, +-6.85,-5.75, +-5.99,-6.61, +-5.75,-6.85, +-5.75,-6.85, +-5.23,-6.77, +-4.17,-6.77, +-2.53,-7.79, +-1.55,-8.81, +-1.21,-8, +-0.69,-7.91, +-0.17,-7.95, +-0,-8.13, +-1.61,-7.79, +-1.38,-7.85, +-1.38,-7.85, +-1.38,-7.85, +-0.46,-7.93, +-0,-7.97, +-0,-7.97, +1.19,-7.87, +1.47,-8.33, +1.47,-8.33, +1.61,-7.79, +2.86,-7.87, +3.13,-8.61, +3.22,-8.86, +3.39,-9.32, +4.17,-8.95, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.59,-8.86, +5.97,-9.06, +5.99,-8.93, +7.43,-8.86, +7.75,-9.23, +7.82,-9.32, +8.12,-9.68, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.06,-9.6, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.16,-6.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.49,-4.1, +1.99,-5.46, +1.95,-5.69, +1.16,-5.9, +0.81,-5.95, +0.17,-6.01, +-0.87,-5.95, +-1.1,-6.25, +-1.53,-6.47, +-1.22,-6.89, +-1.22,-6.89, +-1.69,-6.77, +-1.72,-6.76, +-2.39,-6.58, +-2.39,-6.58, +-2.61,-6.74, +-2.72,-7.02, +-2.69,-7.39, +-2.65,-7.29, +-1.55,-7.14, +-1.38,-7.85, +-1.44,-8.17, +-2.5,-8.22, +-2.88,-7.42, +-3.83,-6.94, +-4.62,-6.46, +-5.62,-5.92, +-6.34,-5.95, +-7.64,-9.11, +-7.45,-8.38, +-8.49,-7.12, +-8.49,-7.12, +-8.61,-6.95, +-9.47,-5.72, +-9.26,-5.35, +-9.21,-3.61, +-9.32,-3.39, +-9.69,-2, +-9.79,-1.43, +-9.86,-0.57, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.1,1.36, +-8.94,1.58, +-9.15,1.86, +-8.92,1.94, +-9.35,2, +-9.76,1.72, +-9.76,1.72, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.19,1.97, +-11.87,2.09, +-12.3,2.17, +-13.71,2.69, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.83,1.89, +-13.9,1.08, +-13.97,0.27, +-14,0, +-14.64,0, +-14.28,-0.41, +-13.82,-2.03, +-14.74,-2.6, +-14.77,-2.31, +-14.87,-1.16, +-14.67,-2.88, +-14.31,-3.66, +-13.33,-4.11, +-13.15,-4.79, +-13.15,-4.79, +-12.81,-4.66, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-11.33,-4.68, +-10.44,-6.03, +-10.44,-6.03, +-10.44,-6.03, +-9.6,-5.54, +-9.1,-6.24, +-8.59,-6.98, +-8.04,-6.75, +-8.49,-7.12, +-7.89,-6.62, +-7.35,-6.16, +-7.01,-6.17, +-6.65,-6.86, +-5.87,-6.73, +-5.75,-6.85, +-5.62,-7.14, +-5.24,-7.99, +-4.26,-7.38, +-3.99,-6.9, +-1.53,-8.7, +-1.04,-8.85, +-0.39,-8.91, +-0,-8.62, +0,-8.09, +-0.69,-8.23, +-1.38,-7.85, +-1.38,-7.85, +-1.41,-8.02, +-1.23,-7.97, +-0.23,-8.76, +-0,-8.78, +0,-8.13, +1.04,-7.88, +1.5,-8.53, +1.53,-8.7, +1.42,-8.06, +2.66,-8.38, +3.22,-8.86, +3.28,-9.01, +3.39,-9.32, +4.17,-8.95, +6.07,-9.23, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.59,-8.86, +7.12,-8.49, +7.12,-8.49, +7.68,-9.15, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.16,-9.73, +8.37,-9.98, +8.37,-9.98, +7.93,-9.45, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.16,-6.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0.08,-0.45, +0.91,-2.79, +1.34,-3.69, +1.73,-4.75, +1.73,-4.75, +1.73,-4.75, +1.46,-4.93, +0.95,-5.4, +0.23,-5.9, +-0.47,-5.99, +-1.05,-5.93, +-1.29,-6.33, +-1.22,-6.89, +-1.22,-6.89, +-1.22,-6.89, +-1.33,-6.86, +-2.41,-6.97, +-2.69,-7.4, +-2.73,-7.49, +-2.73,-7.49, +-2.73,-7.49, +-2.2,-7.06, +-1.5,-7.6, +-1.35,-7.64, +-1.08,-7.88, +-1.29,-8.18, +-0.59,-7.92, +-0.69,-7.91, +-1.15,-7.87, +-1.38,-7.85, +-4.04,-7.58, +-7.54,-8.98, +-7.12,-8.49, +-7.58,-8.03, +-8.32,-7.29, +-8.49,-7.12, +-8.67,-6.86, +-9.31,-5.37, +-8.67,-4.78, +-9.23,-3.56, +-9.37,-3.21, +-9.71,-1.91, +-9.76,-1.72, +-9.52,-0.83, +-9.36,-0.78, +-9.91,0, +-9.91,0, +-9.91,0, +-9.66,0.91, +-9.57,1.49, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-10.27,1.81, +-10.91,1.92, +-10.91,1.92, +-11.31,1.49, +-12.02,0.35, +-12.25,0.95, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.81,2.16, +-13.88,1.35, +-14,0, +-14,0, +-14,0, +-14.16,0, +-14.03,-0.73, +-13.81,-2.16, +-13.89,-2.45, +-15.1,-2.66, +-14.74,-2.6, +-14.74,-2.6, +-14.29,-4.28, +-14.07,-5.12, +-13.24,-4.82, +-13.15,-4.79, +-13.15,-4.79, +-12.89,-4.69, +-12.08,-4.8, +-12.24,-4.45, +-11.63,-4.23, +-10.66,-5.55, +-10.44,-6.03, +-10.44,-6.03, +-10.44,-6.03, +-9.04,-6.33, +-8.49,-7.12, +-8.49,-7.12, +-7.82,-6.56, +-8.19,-7.19, +-7.05,-6.92, +-6.2,-6.55, +-5.75,-6.85, +-5.85,-6.97, +-5.75,-6.85, +-5.32,-7.15, +-5.09,-8.3, +-4.96,-8.59, +-4.42,-7.65, +-3.61,-8.06, +-1.29,-8.83, +-0,-8.94, +-0,-8.94, +0,-8.94, +0.19,-8.93, +0.25,-8.38, +-1.44,-8.17, +-1.09,-8.71, +-0.78,-8.87, +-0.39,-8.91, +-0,-8.94, +0.78,-8.87, +1.53,-8.67, +1.4,-7.94, +1.52,-8.59, +1.55,-8.81, +1.55,-8.81, +2.63,-8.52, +3.13,-9.24, +3.39,-9.32, +3.39,-9.32, +4.1,-8.98, +5.46,-9.45, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.59,-8.86, +7.12,-8.49, +7.12,-8.49, +7.56,-9.01, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.27,-9.85, +8.37,-9.98, +8.37,-9.98, +7.87,-9.38, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.16,-6.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.12,-3.39, +0.44,-4.04, +0.94,-3.96, +1.24,-4.1, +1.54,-4.24, +2.04,-4.25, +2.22,-3.85, +1.44,-4.83, +0.88,-4.98, +0.25,-5.03, +-0.33,-5.57, +-1.05,-5.93, +-0.83,-6.04, +-1.18,-6.68, +-1.22,-6.89, +-1.22,-6.89, +-1.22,-6.89, +-1.61,-7.79, +-1.98,-7.69, +-2.73,-7.49, +-2.83,-7.76, +-2.95,-8.1, +-2.05,-7.67, +-1.38,-7.85, +-1.41,-8.01, +-0.49,-8.25, +-0.17,-8.49, +-0,-7.97, +-0,-7.97, +-0,-7.97, +-0.69,-7.91, +-2.93,-8.71, +-6.89,-9.83, +-7.33,-8.74, +-7.12,-8.49, +-7.27,-8.34, +-8.34,-7.27, +-8.49,-7.12, +-8.98,-6.42, +-9.43,-5.44, +-9.47,-4.43, +-9.68,-3.52, +-9.32,-3.39, +-9.61,-2.28, +-9.76,-1.72, +-9.23,-1.63, +-9.18,-1.4, +-9.33,-1.06, +-9.36,-0.78, +-9.1,0, +-9.19,0, +-8.94,0, +-8.9,0.52, +-9.12,0.38, +-9.84,0.86, +-9.89,0.29, +-10.83,0.64, +-11.05,0.32, +-11.5,0, +-12.05,0, +-12.05,0, +-13.85,1.7, +-13.88,1.35, +-13.89,1.22, +-14,0, +-14,0, +-14,0, +-13.97,-0.27, +-14.12,-1.1, +-14.28,-1.66, +-13.81,-2.13, +-13.9,-2.45, +-14.58,-2.57, +-14.37,-5.23, +-14.55,-3.32, +-14.74,-2.6, +-14.22,-4.56, +-14.07,-5.12, +-13.15,-4.79, +-13.15,-4.79, +-13.15,-4.79, +-12.01,-6.47, +-11.83,-5.34, +-12.03,-4.91, +-10.83,-5.18, +-10.44,-6.03, +-10.44,-6.03, +-10.44,-6.03, +-9.43,-7.46, +-8.49,-7.12, +-8.49,-7.12, +-8.49,-7.12, +-7.7,-7.61, +-7.11,-8.14, +-6.37,-7.59, +-6.06,-7.22, +-5.75,-6.85, +-5.75,-6.85, +-4.95,-7.41, +-4.86,-8.42, +-4.96,-8.59, +-4.46,-8.66, +-3.29,-8.29, +-3.06,-8.4, +-1.15,-8.77, +-0,-8.94, +-0,-8.94, +0.17,-8.93, +1.09,-8.85, +0.06,-8.45, +-1.29,-8.83, +-0,-8.94, +-0,-8.94, +0,-8.94, +1.16,-8.84, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.8,-8.74, +3.24,-9.36, +3.39,-9.32, +3.39,-9.32, +4.43,-8.83, +5.47,-9.47, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.4,-8.99, +7.12,-8.49, +7.33,-8.74, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.3,-9.89, +8.37,-9.98, +8.37,-9.98, +7.9,-9.42, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.16,-6.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.15,-1.3, +-0.88,-2.43, +-0.59,-3.36, +-0.45,-3.71, +0.03,-3.09, +0.44,-3.46, +0.79,-3.25, +1.44,-3.1, +1.71,-3.55, +1.61,-3.97, +1.35,-4.85, +0.66,-5, +-0.15,-5.04, +-0.99,-5.62, +-0.76,-6.05, +-0.93,-6.05, +-1.22,-6.89, +-1.22,-6.89, +-1.22,-6.89, +-1.38,-7.85, +-1.38,-7.85, +-2.17,-7.97, +-3.06,-8.4, +-2.63,-8.18, +-1.78,-7.92, +-1.48,-8.38, +-1.31,-8.29, +-1.02,-8.75, +-0,-9.27, +-0,-8.29, +-0,-7.97, +-0,-7.97, +-0.33,-8.05, +-2.99,-9.03, +-6.03,-10.44, +-7.09,-9.17, +-7.12,-8.49, +-7.12,-8.49, +-7.8,-7.8, +-8.49,-7.12, +-8.6,-6.96, +-9.29,-5.97, +-9.6,-5.54, +-9.96,-4.76, +-9.63,-3.5, +-9.32,-3.39, +-9.54,-2.56, +-9.39,-3.11, +-9.12,-2.26, +-8.86,-1.76, +-8.81,-1.55, +-8.82,-1.38, +-8.87,-0.78, +-8.92,-0.26, +-8.94,0, +-8.94,0, +-9.5,0, +-9.91,0, +-10.3,0, +-11.4,0, +-11.57,0, +-11.73,0, +-12.05,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-13.89,-1.22, +-13.78,-2.43, +-13.78,-2.43, +-13.78,-2.43, +-13.78,-2.43, +-14.53,-2.56, +-14.25,-4.43, +-14.37,-5.23, +-14.15,-4.8, +-14.74,-2.6, +-14.07,-5.12, +-13.91,-5.06, +-13.15,-4.79, +-13.15,-4.79, +-12.5,-6.19, +-11.85,-6.55, +-11.76,-5.48, +-11.28,-6.51, +-10.44,-6.03, +-10.44,-6.03, +-10.32,-5.96, +-9.38,-7.53, +-8.98,-7.54, +-8.49,-7.12, +-8.49,-7.12, +-7.8,-7.8, +-7.12,-8.49, +-6.75,-8.04, +-6.37,-7.59, +-5.81,-7.31, +-5.39,-7.89, +-4.61,-7.64, +-4.74,-8.21, +-4.96,-8.59, +-3.91,-9.07, +-3.28,-9.01, +-3.06,-8.4, +-3.06,-8.4, +-0.64,-9, +-0,-9.37, +0.19,-9.53, +1.55,-8.81, +1.55,-8.81, +0.02,-8.69, +-0.26,-8.92, +-0,-8.94, +0,-8.94, +1.04,-8.85, +1.62,-9.19, +1.67,-9.44, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +2.17,-8.98, +2.95,-9.1, +3.39,-9.32, +3.39,-9.32, +3.65,-9.19, +4.79,-8.88, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.33,-9.04, +7.43,-8.86, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.02,-9.56, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.16,-6.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.26,-0.73, +-2.27,-1.81, +-1.74,-2.31, +-1.27,-2.6, +-1.04,-2.7, +-0.59,-2.5, +-0.11,-2.47, +0.17,-2.06, +0.67,-2.16, +1.38,-2.56, +1.35,-3.2, +0.82,-3.72, +0.51,-4.58, +-0.39,-5.02, +-1.18,-5.34, +-1.08,-6.47, +-1.22,-6.89, +-1.27,-7.21, +-1.22,-6.89, +-1.25,-7.1, +-1.38,-7.85, +-1.4,-7.96, +-1.53,-8.7, +-2.05,-8.67, +-1.43,-8.12, +-1.52,-8.65, +-1.55,-8.81, +-1.57,-8.91, +-0.88,-8.97, +-0.19,-9.9, +-0,-9.05, +-0,-8.73, +-0,-8.7, +-1.04,-8.85, +-3.04,-9.35, +-6.03,-10.44, +-6.59,-9.89, +-7.47,-8.9, +-7.19,-8.57, +-7.58,-8.03, +-8.49,-7.12, +-8.49,-7.12, +-8.49,-7.12, +-9.46,-5.74, +-9.6,-5.54, +-9.46,-5.22, +-9.23,-3.56, +-9.44,-3.44, +-9.68,-3.52, +-9.79,-3.56, +-9.41,-3.15, +-8.6,-2.31, +-8.74,-1.8, +-8.81,-1.55, +-8.81,-1.55, +-8.81,-1.55, +-8.86,-0.99, +-8.97,-0.88, +-9.81,-1.15, +-9.84,-0.86, +-11.05,-0.32, +-11.05,-0.32, +-11.45,-0.66, +-11.99,-0.7, +-13.67,0, +-13.67,0, +-13.58,0, +-13.92,-0.91, +-13.78,-2.43, +-13.78,-2.43, +-13.78,-2.43, +-13.78,-2.43, +-13.81,-2.73, +-14.3,-3.4, +-14.16,-4.76, +-14.07,-5.12, +-14.72,-5.36, +-14.37,-5.23, +-14.18,-4.7, +-13.79,-5.96, +-12.57,-6.3, +-12.24,-6.75, +-12.38,-6.45, +-12.12,-7, +-12.1,-4.75, +-10.85,-7.13, +-10.41,-7.75, +-9.01,-7.9, +-9.66,-5.82, +-8.98,-6.42, +-8.06,-7.68, +-7.12,-8.49, +-7.58,-8.03, +-8.26,-7.35, +-7.12,-8.49, +-6.87,-8.19, +-6.74,-8.33, +-5.73,-8.05, +-4.96,-8.59, +-4.96,-8.59, +-4.88,-8.45, +-4.96,-8.59, +-3.98,-9.04, +-3.39,-9.32, +-3.13,-8.61, +-2.72,-8.49, +-2.05,-8.67, +-0.52,-9.54, +0.22,-9.9, +1.38,-9.47, +1.55,-8.81, +1.55,-8.81, +-0.19,-8.86, +-0,-8.94, +-0,-9.16, +0.52,-9.06, +1.67,-9.44, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.96,-9.7, +2.31,-9.51, +2.89,-9.45, +3.34,-9.16, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.91,-9.07, +4.98,-9.12, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +6.88,-8.95, +6.7,-8.98, +6.44,-9.17, +7.46,-9.43, +7.6,-9.33, +7.75,-9.23, +8.16,-9.73, +8.37,-9.98, +8.37,-9.98, +8.27,-9.85, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.53,-6.59, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.09,0.69, +-2.5,-0.28, +-2.44,-1.56, +-2.23,-1.87, +-1.87,-2.23, +-1.45,-1.84, +-1.16,-1.52, +-0.55,-1.21, +-0.36,-1.33, +0.13,-1.5, +0.55,-1.62, +0.34,-2.46, +0.67,-2.83, +0.24,-4.06, +-0.66,-4.87, +-1.33,-5.52, +-0.84,-6.56, +-0.68,-6.94, +-0.31,-7.62, +-0.42,-7.5, +-0.23,-7.63, +-0.69,-7.91, +-0.52,-8.25, +-1.32,-8.46, +-1.38,-7.85, +-1.5,-8.49, +-1.55,-8.81, +-1.59,-9.02, +-1.7,-9.64, +-1.7,-9.65, +-0.74,-9.85, +-0,-9.27, +-0.19,-8.93, +-1.38,-8.82, +-1.55,-8.81, +-3.04,-9.35, +-6.03,-10.44, +-6.03,-10.44, +-7.75,-9.23, +-7.75,-9.23, +-7.33,-8.74, +-8.67,-7.28, +-9.32,-7.82, +-9.32,-7.82, +-9.19,-7.43, +-9.48,-5.7, +-9.6,-5.54, +-8.92,-5.15, +-9.42,-4.08, +-10.41,-3.79, +-10.72,-3.9, +-10.32,-3.98, +-9.82,-3.58, +-8.71,-3.17, +-8.55,-2.49, +-8.6,-2.31, +-8.67,-2.05, +-8.81,-1.55, +-8.81,-1.55, +-10.45,-1.21, +-9.76,-1.72, +-10.78,-1.9, +-10.91,-1.92, +-11.23,-1.98, +-11.87,-2.09, +-12.89,-1.51, +-12.93,-1.13, +-13.54,-2.06, +-13.78,-2.43, +-13.78,-2.43, +-13.78,-2.43, +-13.7,-2.72, +-13.82,-4.17, +-14.07,-5.12, +-14.07,-5.12, +-14.07,-5.12, +-14.07,-5.12, +-14.02,-5.64, +-13.8,-7.97, +-13.8,-7.97, +-13.24,-7.65, +-12.12,-7, +-12.12,-7, +-12.12,-7, +-12.64,-5.89, +-11.76,-5.48, +-10.27,-7.96, +-9.26,-9.09, +-7.96,-9.02, +-8.12,-8.63, +-8.61,-7.23, +-8.03,-7.58, +-7.12,-8.49, +-6.9,-8.65, +-7.27,-8.34, +-7.12,-8.49, +-6.87,-8.19, +-5.58,-9.09, +-5.03,-8.71, +-4.96,-8.59, +-4.96,-8.59, +-4.61,-8.57, +-3.59,-9.22, +-3.39,-9.32, +-3.36,-9.24, +-2.31,-8.6, +-1.55,-8.81, +-1.55,-8.81, +-0.52,-9.54, +1.51,-9.78, +1.67,-9.49, +1.61,-9.13, +1.61,-9.13, +0.45,-9.23, +-0,-8.94, +-0,-9.27, +0.52,-9.06, +1.67,-9.44, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +2.76,-9.48, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.61,-9.36, +4.98,-9.54, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.86,-10.16, +7.1,-9.68, +7.75,-9.23, +8.16,-9.73, +8.37,-9.98, +8.37,-9.98, +8.16,-9.73, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.81,-6.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.25,0.86, +-2.23,1.87, +-2.76,2.02, +-2.68,-0.12, +-2.72,-1.05, +-2.56,-1.35, +-2.33,-1.54, +-1.6,-1.07, +-0.96,-0.59, +-0.81,-0.5, +-0.79,-0.54, +-0.3,-0.67, +0.41,-0.77, +0.85,-1.62, +1.11,-2.45, +0.54,-3.76, +-0.5,-4.59, +-0.93,-5.3, +-0.52,-6.6, +-0.17,-6.98, +-0,-7.43, +-0,-7.65, +-0,-7.65, +-0,-7.97, +-0,-8.29, +-0.2,-7.95, +-0.69,-7.91, +-1.55,-8.81, +-1.55,-8.81, +-1.68,-9.55, +-1.72,-9.76, +-1.72,-9.76, +-1.31,-10.5, +-1.04,-9.96, +-1.7,-9.64, +-1.64,-9.28, +-1.61,-9.13, +-1.61,-9.13, +-2.09,-9.66, +-3.39,-9.32, +-3.39,-9.32, +-9,-10.72, +-9,-10.72, +-9.98,-9.74, +-10.72,-9, +-10.72,-9, +-10.47,-8.79, +-9.98,-8.37, +-10.37,-7.81, +-11.08,-6.64, +-10.62,-6.13, +-11.03,-4.76, +-11.33,-4.12, +-11.51,-3.45, +-11.87,-2.09, +-11.87,-2.09, +-12.01,-0.52, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.17,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-12.83,-2.26, +-12.83,-2.26, +-13.42,-2.37, +-13.78,-2.43, +-13.69,-2.77, +-13.46,-4.16, +-13.91,-5.06, +-14.07,-5.12, +-14.07,-5.12, +-14.07,-5.12, +-14.07,-5.12, +-13.87,-5.86, +-13.66,-7.89, +-13.8,-7.97, +-13.8,-7.97, +-12.98,-8.03, +-12.12,-7, +-12.12,-7, +-12.12,-7, +-12.35,-6.51, +-10.85,-7.13, +-9.17,-9.17, +-8.37,-9.98, +-7.75,-9.23, +-7.75,-9.23, +-7.2,-8.58, +-7.12,-8.49, +-7.12,-8.49, +-5.72,-9.47, +-7.12,-8.49, +-7.12,-8.49, +-5.8,-9.41, +-5.44,-9.43, +-5.29,-9.16, +-4.96,-8.59, +-4.43,-8.83, +-3.56,-9.23, +-3.39,-9.32, +-3.39,-9.32, +-3.04,-8.91, +-1.55,-8.81, +-1.55,-8.81, +-1.55,-8.81, +0.42,-9.57, +1.74,-9.87, +1.73,-9.78, +1.72,-9.76, +1.72,-9.76, +0.57,-9.22, +-0,-8.94, +0,-9.1, +1.66,-9.4, +1.68,-9.55, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +3.39,-9.32, +3.46,-9.5, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +4.89,-9.69, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.62,-9.74, +5.95,-10.3, +6.67,-9.99, +7.75,-9.23, +8.27,-9.85, +8.37,-9.98, +8.37,-9.98, +7.96,-9.48, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.81,-6.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.07,0.62, +-4.01,2.86, +-3.13,2.62, +-3.2,2.53, +-3.09,0.34, +-2.76,-0.93, +-2.78,-0.83, +-2.28,-0.61, +-1.91,-0.19, +-1.39,-0.13, +-0.96,-0.09, +-0.93,-0.22, +-0.94,0.19, +-0.1,0.12, +-0.04,-0.65, +0.33,-1.29, +0.42,-2.58, +-0.53,-4.36, +-0.9,-5.08, +-0.35,-6.64, +-0,-7, +0,-7.32, +0,-7.32, +0.23,-7.95, +0.52,-7.92, +0.31,-7.94, +-0,-8.08, +-0,-8.73, +-0.35,-8.59, +-1.05,-8.85, +-1.72,-9.76, +-1.72,-9.76, +-1.79,-10.15, +-1.76,-9.95, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-2.09,-9.66, +-2.56,-9.54, +-3.39,-9.32, +-3.39,-9.32, +-9,-10.72, +-9,-10.72, +-9.19,-10.53, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.08,-8.46, +-9.98,-8.37, +-9.98,-8.37, +-11.01,-6.68, +-10.86,-6.27, +-10.83,-5.18, +-11.33,-4.12, +-11.45,-3.67, +-11.81,-2.32, +-11.87,-2.09, +-12.02,-0.35, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.76,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-12.83,-2.26, +-12.83,-2.26, +-13.36,-2.77, +-13.31,-4.2, +-13.46,-4.9, +-14.07,-5.12, +-14.07,-5.12, +-14.07,-5.12, +-14.07,-5.12, +-14.07,-5.12, +-13.75,-6.94, +-13.8,-7.97, +-13.8,-7.97, +-13.8,-7.97, +-12.94,-9.21, +-12.21,-9.12, +-11.63,-7.94, +-12.12,-7, +-12.12,-7, +-11.89,-7.33, +-9.98,-8.37, +-9.09,-9.26, +-8.37,-9.98, +-7.75,-9.23, +-7.75,-9.23, +-7.48,-8.91, +-7.12,-8.49, +-6.33,-9.04, +-5.54,-9.6, +-7.12,-8.49, +-6.67,-8.8, +-5.54,-9.6, +-5.54,-9.6, +-5.35,-9.26, +-4.43,-8.83, +-3.39,-9.32, +-3.39,-9.32, +-3.39,-9.32, +-3.11,-9.39, +-2.14,-8.98, +-1.55,-8.81, +-1.55,-8.81, +-1.38,-8.93, +1.15,-9.81, +1.92,-10.91, +1.75,-9.91, +1.72,-9.76, +1.72,-9.76, +0.57,-9.22, +-0,-8.94, +0.39,-8.91, +1.6,-9.08, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +2.56,-9.54, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.66,-10.05, +3.46,-9.5, +3.79,-10.41, +4.86,-9.06, +4.96,-8.59, +5.48,-9.48, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.6,-9.7, +5.95,-10.3, +6.03,-10.44, +6.31,-10.24, +7.82,-9.33, +8.37,-9.98, +8.37,-9.98, +8.73,-9.62, +8.16,-9.73, +8.37,-9.98, +8.37,-9.98, +9.44,-8.91, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.81,-6.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.14,3.02, +-3.87,3.25, +-3.13,2.62, +-3.13,2.62, +-2.96,0.54, +-2.86,-0.57, +-2.88,-0.42, +-2.59,0.06, +-1.88,0.48, +-1.43,0.44, +-0.89,0.34, +-0.87,0.4, +-0.85,0.43, +-0.28,0.45, +-0.18,0.06, +0.22,-0.73, +0.51,-1.54, +-0.43,-4.02, +-0.88,-4.98, +0.5,-6.36, +0.81,-6.93, +1.14,-7.22, +1.1,-7.12, +1.38,-7.85, +1.38,-7.85, +1.41,-8.01, +0.49,-8.58, +-0,-7.97, +-0,-8.09, +-0.52,-8.79, +-1.42,-9.03, +-1.53,-9.78, +-1.74,-9.89, +-1.86,-10.53, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-1.93,-9.71, +-2.89,-9.45, +-3.39,-9.32, +-3.39,-9.32, +-5.26,-9.78, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.39,-8.72, +-9.98,-8.37, +-9.98,-8.37, +-10.12,-8.17, +-11.13,-6.72, +-10.92,-6.3, +-10.88,-5.07, +-11.33,-4.12, +-11.42,-3.78, +-11.81,-2.32, +-11.87,-2.09, +-11.87,-2.09, +-11.87,-2.09, +-12.04,-1.41, +-12.38,-1.83, +-12.8,-0.75, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-12.43,-3.72, +-12.3,-4.21, +-12.85,-4.68, +-13.15,-4.79, +-13.46,-4.9, +-14.07,-5.12, +-14.07,-5.12, +-14.07,-5.12, +-13.65,-6.01, +-13.27,-7.33, +-13.8,-7.97, +-13.8,-7.97, +-13.8,-7.97, +-13.54,-8.35, +-12.21,-10.25, +-11.79,-9.89, +-11.47,-9.62, +-11.93,-7.75, +-11.74,-7.73, +-10.49,-9.16, +-9.34,-9.92, +-8.57,-9.78, +-8.37,-9.98, +-7.75,-9.23, +-7.75,-9.23, +-7.75,-9.23, +-6.44,-9.17, +-5.54,-9.6, +-5.54,-9.6, +-6.83,-8.69, +-5.74,-9.46, +-5.54,-9.6, +-5.54,-9.6, +-4.53,-9.64, +-3.45,-9.47, +-3.39,-9.32, +-3.54,-9.73, +-3.25,-9.49, +-1.91,-9.71, +-1.72,-9.76, +-1.62,-9.17, +-1.55,-8.81, +-0.26,-8.92, +1.15,-9.65, +1.72,-9.76, +1.72,-9.76, +1.55,-9.68, +1.15,-9.49, +0.77,-9.31, +0,-8.94, +0.39,-8.91, +1.61,-9.13, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.66,-9.42, +2.83,-9.47, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.46,-9.5, +3.59,-9.86, +4.23,-10.21, +4.96,-9.01, +5.09,-8.81, +5.39,-9.34, +5.54,-9.6, +5.62,-9.74, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +7.63,-10.2, +8.37,-9.98, +8.37,-9.98, +9.78,-8.57, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +9.98,-8.37, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +4.43,-5.28, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.68,0.57, +0,0, +-2.5,0.91, +-2.95,1.52, +-5.78,3.34, +-4.37,3.67, +-3.87,3.25, +-3.21,2.69, +-3.13,2.62, +-2.98,0.87, +-2.88,-0.45, +-2.9,-0.17, +-2.54,0.47, +-1.94,0.71, +-1.67,0.96, +-1.76,0.82, +-1.21,0.79, +-0.57,0.78, +-0.33,0.9, +-0.15,0.5, +0.41,0.17, +0.43,-1.07, +-0.25,-3.18, +-0.62,-4.51, +0.17,-5.93, +1.22,-6.89, +1.64,-6.95, +2.13,-6.65, +2.28,-7.61, +2.32,-7.71, +2.13,-8.32, +1.5,-8.49, +0.79,-8.23, +-0,-8.8, +-0,-8.94, +-0,-8.94, +-0.57,-9.54, +-1.63,-10.5, +-1.92,-10.91, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-2.09,-9.66, +-3.11,-9.39, +-3.44,-9.45, +-3.52,-9.68, +-5.53,-10.52, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-10.15,-9.57, +-10.72,-9, +-10.72,-9, +-10.6,-8.89, +-9.98,-8.37, +-10.1,-8.48, +-9.98,-8.37, +-10.14,-8.14, +-11.16,-6.68, +-11.19,-6.46, +-11.22,-5.11, +-11.33,-4.12, +-11.42,-3.78, +-11.78,-2.43, +-11.87,-2.09, +-11.87,-2.09, +-12.51,-2.21, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.86,-1.88, +-12.95,-0.85, +-12.7,-2.24, +-12.24,-4.45, +-12.7,-4.62, +-13.15,-4.79, +-13.15,-4.79, +-13.15,-4.79, +-14.07,-5.12, +-13.96,-5.36, +-13.46,-6.41, +-12.96,-7.48, +-13.1,-7.57, +-13.8,-7.97, +-13.8,-7.97, +-13.54,-8.35, +-12.44,-9.92, +-12.21,-10.25, +-11.47,-9.62, +-11.47,-9.62, +-11.37,-9.54, +-10.72,-9, +-9,-10.72, +-8.79,-10.47, +-8.37,-9.98, +-8.37,-9.98, +-7.98,-9.51, +-7.75,-9.23, +-7.17,-9.63, +-5.97,-10.34, +-5.82,-10.08, +-5.62,-9.74, +-6.33,-9.04, +-5.54,-9.6, +-5.54,-9.6, +-5.54,-9.6, +-3.61,-9.92, +-3.39,-9.32, +-3.52,-9.68, +-3.68,-10.1, +-2.82,-10.47, +-1.72,-9.76, +-1.72,-9.76, +-1.7,-9.63, +-0.89,-8.86, +-0,-8.94, +0.38,-10.07, +0.57,-9.99, +1.15,-9.81, +1.29,-9.56, +-0,-8.94, +-0,-8.94, +0,-8.94, +1.33,-8.83, +1.58,-8.94, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.59,-9.02, +1.72,-9.76, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.68,-10.1, +3.79,-10.41, +4.37,-10.14, +5.52,-9.57, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.7,-9.88, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.6,-10.04, +6.97,-10.28, +8.37,-9.98, +8.37,-9.98, +8.64,-9.71, +9.98,-8.37, +9.05,-10.28, +8.68,-10.35, +8.92,-10.63, +9.55,-9.03, +8.86,-8.8, +8.86,-8.8, +8.86,-8.8, +9.11,-8.55, +5.91,-5.87, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.02,0.85, +-6.11,5.12, +-6.55,4.49, +-6.9,3.98, +-6.15,3.55, +-5.26,3.89, +-4.37,3.67, +-3.87,3.25, +-3.38,2.83, +-3.13,2.62, +-3.11,1.82, +-3.29,1.28, +-3.35,1.28, +-3.33,0.8, +-2.67,1.15, +-1.81,1.17, +-1.72,0.9, +-1.51,1.22, +-0.95,1.36, +-0.25,0.93, +-0.05,0.94, +-0.05,0.44, +0.04,-0.54, +-0.28,-2.57, +-0.53,-3.91, +0.57,-5.44, +2.16,-6.64, +2.58,-6.78, +3.13,-6.23, +3.36,-7.2, +2.84,-7.79, +3.06,-8.4, +2.04,-8.57, +1.55,-8.81, +0.69,-8.88, +-0,-8.94, +-0,-9.37, +-0,-9.59, +-0.82,-11.01, +-1.92,-10.91, +-1.79,-10.15, +-1.72,-9.76, +-1.75,-9.93, +-2,-10.22, +-3.06,-10.21, +-3.75,-10.29, +-3.79,-10.41, +-4.75,-10.92, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9.86,-9.86, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.47,-8.79, +-10.72,-9, +-10.64,-8.93, +-10.4,-8.73, +-10.19,-8.06, +-11.28,-6.51, +-11.28,-6.51, +-11.62,-4.63, +-11.33,-4.12, +-11.33,-4.12, +-11.45,-3.67, +-11.77,-3.28, +-12.7,-2.75, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.63,-2.99, +-12.85,-4.68, +-13.15,-4.79, +-13.15,-4.79, +-13.15,-4.79, +-12.64,-5.89, +-13.51,-6.3, +-12.96,-7.48, +-12.96,-7.48, +-13.08,-7.55, +-13.8,-7.97, +-13.8,-7.97, +-13.1,-8.98, +-12.39,-9.99, +-12.21,-10.25, +-12.58,-10.56, +-11.47,-9.62, +-10.72,-9, +-10.72,-9, +-9.57,-10.15, +-9,-10.72, +-8.79,-10.47, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-6.99,-9.96, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.02,-9.5, +-5.54,-9.6, +-5.54,-9.6, +-4.37,-10.14, +-3.46,-9.5, +-3.72,-10.23, +-3.79,-10.41, +-2.99,-10.63, +-1.92,-10.91, +-1.72,-9.76, +-1.72,-9.76, +-1.68,-9.55, +-0.78,-8.87, +-0,-8.94, +-0,-10.37, +0,-10.89, +1.15,-10.2, +1.29,-9.56, +0.29,-9.08, +1.15,-9.49, +0.62,-9.13, +1.55,-8.81, +1.19,-9, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.59,-9.05, +1.72,-9.76, +2.83,-9.47, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.74,-10.27, +3.79,-10.41, +4.04,-10.3, +5.48,-10.08, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.96,-10.32, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.56,-10.66, +7.13,-10.85, +8.37,-9.98, +8.37,-9.98, +8.64,-9.71, +9.17,-9.17, +9.03,-10.44, +8.65,-10.31, +8.64,-10.3, +9.58,-8.77, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.84,0.71, +-3.13,2.62, +-5.11,4.29, +-6.32,5.3, +-6.33,4.8, +-6.9,3.98, +-5.93,4.07, +-5.2,4.36, +-4.62,3.87, +-4.12,3.46, +-3.79,3.18, +-3.29,2.76, +-3.4,2.24, +-3.39,1.96, +-3.64,1.83, +-3.42,1.37, +-2.67,1.15, +-2.37,1.5, +-1.97,1.31, +-2.01,1.61, +-1.3,1.58, +-0.53,1.28, +-0.24,1.05, +-0.4,0.56, +-0.13,-0.01, +-0.5,-2.54, +-0.59,-4.03, +-0,-5.05, +-0,-5.38, +3.05,-5.56, +3.45,-5.98, +3.81,-6.6, +3.84,-7.57, +3.06,-8.4, +2.42,-8.91, +1.55,-8.81, +0.83,-9.52, +0,-8.94, +-0,-9.67, +-0,-9.91, +-0,-10.82, +-0.43,-11.04, +-1.19,-10.2, +-1.74,-9.89, +-1.9,-10.77, +-1.92,-10.91, +-2.86,-10.66, +-3.79,-10.41, +-3.79,-10.41, +-4.18,-10.77, +-8.09,-11.36, +-9,-10.72, +-9,-10.72, +-9.19,-10.53, +-10.51,-9.21, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.47,-8.79, +-10.41,-7.75, +-11.28,-6.51, +-11.48,-5.86, +-11.63,-4.23, +-12.24,-4.45, +-11.93,-4.34, +-12.09,-4.4, +-12.24,-4.45, +-12.31,-4.18, +-12.5,-3.48, +-12.76,-2.51, +-12.83,-2.26, +-12.83,-2.26, +-12.79,-2.85, +-13.15,-4.79, +-12.81,-5.52, +-12.29,-6.63, +-12.12,-7, +-12.12,-7, +-12.68,-7.32, +-12.96,-7.48, +-12.96,-7.48, +-13.45,-7.77, +-13.58,-8.3, +-12.48,-9.87, +-12.21,-10.25, +-12.21,-10.25, +-12.21,-10.25, +-12.83,-10.77, +-11.02,-9.73, +-10.72,-9, +-9.69,-10.03, +-9,-10.72, +-9,-10.72, +-8.58,-10.23, +-8.37,-9.98, +-8.37,-9.98, +-7.57,-10.54, +-6.51,-11.28, +-6.33,-10.96, +-6.24,-10.81, +-6.08,-10.53, +-4.76,-11.03, +-4.65,-10.68, +-4.79,-10.35, +-4.06,-10.22, +-2.86,-10.66, +-2.79,-10.48, +-3.79,-10.41, +-3.79,-10.41, +-2.08,-10.87, +-1.92,-10.91, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-0.19,-8.93, +-0,-8.94, +-0,-10.72, +0,-11.08, +0.29,-10.47, +1.27,-10.03, +1.38,-9.91, +1.53,-9.91, +1.72,-9.76, +0,-8.94, +0.19,-9.53, +1.43,-9.79, +1.72,-9.76, +1.69,-9.6, +1.58,-8.97, +1.72,-9.76, +2.83,-9.47, +3.39,-9.32, +3.39,-9.32, +3.44,-9.45, +3.72,-10.23, +3.79,-10.41, +3.79,-10.41, +4.83,-10.46, +5.54,-9.6, +5.59,-9.69, +5.88,-10.18, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.27,-10.86, +6.51,-11.28, +6.82,-11.06, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.65,-10.31, +8.79,-10.47, +8.48,-10.1, +8.37,-9.98, +9.37,-8.97, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.38,2.83, +-7.51,6.3, +-7.02,5.89, +-6.85,5.75, +-6.85,5.75, +-6.77,5.68, +-6.65,4.68, +-6,5.03, +-5.47,4.59, +-4.99,4.19, +-4.62,3.87, +-4.29,3.6, +-3.87,3.25, +-3.54,2.97, +-3.35,2.5, +-3.49,2.11, +-2.98,1.4, +-2.57,1.36, +-2.43,1.6, +-2.19,1.65, +-2.11,1.77, +-1.83,2.07, +-0.87,1.73, +-0.98,1.62, +-0.72,0.77, +-0.83,0.03, +-0.59,-1.95, +-0.71,-4.02, +-0,-4.57, +0.44,-5.02, +3.44,-4.92, +4.19,-4.99, +4.41,-5.62, +4.18,-7.24, +3.32,-8.38, +2.24,-9.51, +1.72,-9.76, +0.96,-9.83, +-0,-9.81, +-0,-9.91, +-0,-9.91, +-0.75,-11.27, +-1.13,-11.95, +-1.05,-11.09, +-1.8,-11.31, +-2.04,-11.55, +-2.24,-11.49, +-3.22,-10.9, +-2.75,-10.69, +-3.66,-10.67, +-4.12,-11.33, +-7.32,-12.68, +-9.31,-11.09, +-9,-10.72, +-9.09,-10.83, +-9.4,-10.78, +-11.01,-9.62, +-10.8,-9.07, +-10.72,-9, +-10.72,-9, +-10.51,-9.21, +-10.72,-9, +-10.72,-9, +-10.35,-8.68, +-10.63,-7.44, +-11.28,-6.51, +-11.45,-6.14, +-11.92,-5.14, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.53,-3.36, +-12.73,-2.63, +-12.87,-2.62, +-12.12,-7, +-12.12,-7, +-12.12,-7, +-12.12,-7, +-12.12,-7, +-12.96,-7.48, +-12.96,-7.48, +-13.1,-7.57, +-13.8,-7.97, +-12.81,-9.39, +-11.72,-10.74, +-11.93,-11.29, +-11.87,-11.19, +-12.26,-11.56, +-11.1,-12.72, +-10.79,-10.69, +-10.15,-9.57, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.58,-10.23, +-8.14,-10.14, +-6.93,-10.99, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.14,-11.92, +-4.18,-11.48, +-4.12,-11.33, +-4.08,-11.21, +-2.86,-10.66, +-1.92,-10.91, +-1.92,-10.91, +-3.38,-10.52, +-3.38,-10.52, +-1.92,-10.91, +-1.92,-10.91, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-0,-9.91, +-0,-8.94, +0,-11.08, +0,-11.08, +0,-10.95, +0,-10.69, +0,-10.79, +0.29,-10.86, +0.29,-10.47, +0,-8.94, +0.25,-9.75, +1.23,-9.81, +1.53,-9.78, +1.72,-9.76, +1.7,-9.66, +1.72,-9.76, +1.72,-9.76, +2.56,-9.54, +3.39,-9.32, +3.39,-9.32, +3.66,-10.05, +3.79,-10.41, +3.93,-10.8, +4.69,-10.53, +5.54,-9.6, +5.78,-10.02, +6.03,-10.44, +6.03,-10.44, +6.19,-10.72, +6.43,-11.14, +6.51,-11.28, +6.51,-11.28, +7.04,-10.91, +8.37,-9.98, +8.37,-9.98, +8.68,-10.35, +9,-10.72, +8.84,-10.54, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +9.17,-9.17, +8.91,-9.44, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.33,5.31, +-7.59,6.37, +-7.59,6.37, +-7.43,6.23, +-6.77,5.68, +-6.19,5.19, +-6.91,4.35, +-6.27,5.26, +-6.02,5.05, +-5.51,4.62, +-5.13,4.46, +-4.49,4, +-4.49,4, +-4,3.35, +-3.78,3.17, +-3.62,3.04, +-2.83,2.08, +-2.53,1.46, +-2.48,1.5, +-2.27,1.83, +-2.23,1.87, +-2.05,2.05, +-1.45,2.34, +-1.11,1.59, +-0.81,1.57, +-0.99,-0.06, +-0.68,-0.87, +-0.71,-3.37, +-0.06,-4.17, +0.78,-4.88, +1.8,-5.03, +2.68,-5.01, +3.01,-5.96, +4.16,-6.39, +3.37,-7.9, +1.98,-9.99, +0.86,-10.39, +0,-10.04, +-0,-9.91, +-0,-9.91, +-0,-9.91, +-1.7,-11.86, +-1.99,-12.63, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.38,-11.63, +-2.04,-11.6, +-3.5,-11.71, +-4.38,-12.04, +-7.48,-12.96, +-8.98,-11.92, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.93,-11.16, +-11.47,-9.62, +-11.22,-9.41, +-11.09,-9.31, +-9.76,-11.18, +-11.05,-9.27, +-10.72,-9, +-10.72,-9, +-10.06,-8.44, +-10.85,-7.13, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-12.08,-4.8, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.12,-7, +-12.12,-7, +-12.12,-7, +-12.12,-7, +-12.12,-7, +-12.96,-7.48, +-12.96,-7.48, +-13.24,-7.65, +-12.92,-9.23, +-10.25,-12.21, +-10.25,-12.21, +-10.77,-12.83, +-10.45,-12.46, +-10.87,-12.95, +-9.26,-14.08, +-8.92,-12.15, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.37,-10.37, +-7.04,-10.91, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.14,-11.92, +-4.45,-12.24, +-4.42,-12.14, +-4.12,-11.33, +-4.12,-11.33, +-3,-11.04, +-1.92,-10.91, +-1.92,-10.91, +-3.79,-10.41, +-2.23,-10.83, +-1.92,-10.91, +-1.92,-10.91, +-1.78,-10.09, +-1.72,-9.76, +-1.15,-9.81, +-0,-9.91, +-0,-9.62, +-0,-11.08, +0,-11.08, +0,-11.08, +0,-11.08, +0,-11.08, +0,-11.08, +0,-11.08, +0,-10.09, +0,-10.52, +0.43,-9.88, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +2.28,-9.61, +3.39,-9.32, +3.51,-9.64, +3.7,-10.17, +3.79,-10.41, +3.83,-10.53, +3.96,-10.87, +4.19,-10.58, +6.03,-10.44, +6.03,-10.44, +6.33,-10.96, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.98,-10.95, +8.37,-9.98, +8.45,-10.07, +9,-10.72, +9,-10.72, +8.84,-10.54, +8.58,-10.23, +8.58,-10.23, +8.6,-9.75, +8.66,-9.69, +9.8,-8.55, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.71,0.84, +-7.59,6.37, +-7.59,6.37, +-7.59,6.37, +-7.22,6.06, +-6.85,5.75, +-7.15,5.32, +-7.64,4.41, +-7.15,5.32, +-6.44,5.4, +-6.11,5.12, +-5.54,5.23, +-4.56,5.44, +-4.32,4.99, +-4.31,4.46, +-3.89,4.01, +-4,3.35, +-2.78,2.33, +-2.38,2, +-2.73,2.29, +-2.63,2.21, +-2.83,2.37, +-2.22,2.26, +-1.6,2.43, +-1.27,2.5, +-1.12,1.93, +-1.63,0.02, +-1.23,-0.27, +-1.14,-2.31, +-0.42,-2.86, +0.5,-4.2, +1.28,-4.49, +1.01,-5.16, +0.39,-7.4, +3.69,-5.66, +2.43,-7.68, +1.41,-10.44, +0.19,-11.55, +-0,-11.42, +-0,-9.91, +-0,-9.91, +-0,-11.86, +-1.05,-11.96, +-0.5,-12.22, +-0.47,-12.01, +-1.4,-11.93, +-1.86,-11.89, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.94,-12.31, +-4.45,-12.24, +-7.48,-12.96, +-7.48,-12.96, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-10.03,-11.06, +-11.26,-9.83, +-11.47,-9.62, +-9.62,-11.47, +-10.24,-10.85, +-10.43,-9.28, +-10.72,-9, +-10.6,-8.89, +-10.19,-8.55, +-10.7,-7.34, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.42,-6.22, +-11.76,-5.48, +-12.08,-4.8, +-12.24,-4.45, +-12.24,-4.45, +-12.24,-4.45, +-12.12,-7, +-12.12,-7, +-11.97,-7.22, +-11.65,-7.66, +-12.25,-7.38, +-12.4,-8.29, +-12.21,-8.55, +-11.71,-9.83, +-12.21,-10.25, +-10.25,-12.21, +-10.25,-12.21, +-10.25,-12.21, +-10.25,-12.21, +-10.73,-12.79, +-9.26,-14.08, +-9.23,-12.53, +-9.14,-10.89, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-7.01,-11.33, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.14,-11.92, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.23,-11.63, +-4.12,-11.33, +-3.07,-11.36, +-1.92,-10.91, +-1.92,-10.91, +-2.23,-10.83, +-1.92,-10.91, +-1.94,-11.02, +-1.92,-10.91, +-1.92,-10.91, +-0.29,-10.08, +-0,-9.91, +-0,-9.91, +-0,-9.78, +0,-10.91, +0,-11.08, +0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.69, +0,-12.05, +0,-12.05, +0.86,-9.84, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +2.69,-9.64, +3.69,-10.14, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.9,-10.72, +4,-10.98, +5.07,-10.88, +6.03,-10.44, +6.41,-11.11, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +7.44,-10.63, +8.48,-10.1, +9,-10.72, +9,-10.72, +8.89,-10.6, +8.55,-10.19, +8.51,-10.14, +8.51,-10.14, +8.37,-9.98, +8.77,-9.58, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.08,7.25, +-6.51,7.46, +-7.46,6.51, +-7.43,6.23, +-6.85,5.75, +-7.15,5.32, +-7.74,4.47, +-7.74,4.47, +-7.15,5.32, +-6.85,5.75, +-6.73,5.64, +-5.2,6.2, +-5.05,6.02, +-4.85,5.77, +-4.5,5.36, +-4.3,4.81, +-3.95,4.54, +-3.16,3.32, +-2.62,3.13, +-2.71,3.04, +-2.96,2.79, +-3.13,2.62, +-2.79,2.96, +-2.07,2.8, +-1.92,2.67, +-1.82,2.11, +-1.86,0.48, +-1.55,0.33, +-1.82,-1.8, +-0.96,-2.85, +0.54,-3.36, +1.43,-3.64, +1.28,-4.37, +0.98,-6.91, +2.91,-4.51, +1.86,-7.22, +0.36,-10.53, +-0,-11.19, +-0,-11.51, +-0,-11.61, +-0,-11.95, +-0,-12.05, +-0,-12.05, +-0,-11.89, +-0,-12.05, +-0,-12.05, +-0.7,-11.99, +-2.09,-11.87, +-2.09,-11.87, +-2.11,-11.98, +-2.41,-12.23, +-4.45,-12.24, +-7.48,-12.96, +-7.48,-12.96, +-8.67,-12.13, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-10.22,-9.65, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.6,-8.89, +-11.03,-7.71, +-11.7,-6.76, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.6,-5.83, +-12.03,-4.91, +-12.24,-4.45, +-12.12,-7, +-11.12,-8.43, +-10.72,-9, +-10.72,-9, +-10.97,-9.2, +-11.47,-9.62, +-11.47,-9.62, +-11.47,-9.62, +-12.21,-10.25, +-10.04,-11.96, +-10.25,-12.21, +-10.25,-12.21, +-10.45,-12.46, +-10.87,-12.95, +-8.46,-14.65, +-9.23,-12.53, +-9.2,-10.97, +-9,-10.72, +-9,-10.72, +-7.8,-11.56, +-6.67,-11.56, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-4.8,-12.08, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.34,-11.93, +-4.12,-11.33, +-3.45,-11.51, +-1.98,-11.23, +-1.92,-10.91, +-2,-11.34, +-2.09,-11.87, +-2.04,-11.55, +-1.92,-10.91, +-1.65,-10.94, +-0,-10.89, +-0,-9.91, +-0,-9.91, +-0,-9.5, +0,-10.87, +0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.44, +-0,-12.05, +0,-12.05, +0,-12.05, +1.24,-11, +1.99,-11.29, +2.08,-11.81, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.76,-9.95, +1.92,-10.91, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.96,-10.87, +4.12,-11.33, +4.12,-11.33, +5.6,-10.63, +6.27,-10.86, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +7.44,-10.63, +8.89,-10.6, +9,-10.72, +9,-10.72, +8.93,-10.64, +8.37,-9.98, +8.37,-9.98, +8.48,-10.1, +9,-10.72, +9.29,-9.75, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.19,1.41, +-7.12,8.49, +-6.75,8.04, +-7.29,6.68, +-7.59,6.37, +-7.43,6.23, +-7.62,5.02, +-7.74,4.47, +-7.74,4.47, +-7.15,5.32, +-6.85,5.75, +-6.85,5.75, +-5.91,6.69, +-5.54,6.6, +-5.12,6.11, +-4.91,5.86, +-4.65,5.55, +-4.5,5.36, +-3.76,4.48, +-3.06,3.65, +-2.55,3.31, +-2.43,3.26, +-2.62,3.12, +-2.48,3.07, +-1.91,3.02, +-1.46,2.53, +-1.67,2.38, +-1.94,1.15, +-1.81,0.6, +-1.68,-0.77, +-0.89,-2.17, +0.17,-2.87, +1.03,-3, +1.42,-3.68, +0.99,-5.17, +1.48,-3.19, +1.02,-6.78, +-0,-9.27, +-0,-11.08, +-0,-11.57, +-0,-11.89, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-11.84, +-0,-12.05, +-0,-11.73, +-0,-11.84, +-1.16,-11.84, +-2.09,-11.87, +-2.09,-11.87, +-2.18,-12.35, +-4.4,-12.09, +-7.48,-12.96, +-7.48,-12.96, +-7.48,-12.96, +-8.91,-11.96, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.7,-10.93, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.8,-7.88, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.97,-9.2, +-11.47,-9.62, +-11.47,-9.62, +-11.47,-9.62, +-12.21,-10.25, +-9.73,-11.59, +-10.25,-12.21, +-10.25,-12.21, +-10.56,-12.58, +-9.06,-14.22, +-8.46,-14.65, +-8.97,-13.23, +-9.2,-10.97, +-9,-10.72, +-8.5,-11.07, +-7,-12.12, +-6.67,-11.56, +-6.51,-11.28, +-6.51,-11.28, +-5.83,-11.6, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.16,-11.44, +-4.12,-11.33, +-2.07,-11.75, +-1.92,-10.91, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-1.95,-11.07, +-0.7,-11.02, +-0,-11.08, +-0,-10.56, +-0,-9.91, +-0,-9.91, +0.26,-11.37, +0,-11.89, +0,-11.73, +-0,-11.57, +-0,-11.89, +-0,-12.05, +-0,-12.05, +0,-12.05, +1.16,-11.95, +2.19,-12.4, +2.26,-12.83, +2.26,-12.83, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +2.07,-9.87, +2.86,-10.66, +3.17,-10.58, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.09,-11.22, +4.12,-11.33, +5.07,-10.88, +6.03,-10.44, +6.19,-10.72, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +7.75,-11, +8.84,-10.54, +8.93,-10.64, +9,-10.72, +8.89,-10.6, +8.48,-10.1, +8.37,-9.98, +8.89,-10.6, +9,-10.72, +9.49,-9.55, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.3,3.94, +-7.12,8.49, +-7.12,8.49, +-7.41,7.26, +-7.59,6.37, +-7.59,6.37, +-8.45,4.88, +-7.84,4.52, +-8.32,5.63, +-9.32,7.82, +-8.77,7.36, +-8.44,7.08, +-6.92,7.29, +-6.03,7.18, +-5.61,6.68, +-5.26,6.27, +-5.12,6.11, +-5.02,5.98, +-4.5,4.9, +-3.35,3.77, +-2.77,4.21, +-2.24,3.87, +-2.17,3.44, +-2.04,3.54, +-1.98,3.42, +-1.91,3.31, +-1.87,2.23, +-1.82,1.33, +-1.76,0.8, +-1.84,0.22, +-1.41,-1.26, +-0.66,-2.78, +0.88,-2.59, +1.38,-2.56, +1.11,-3.46, +0.81,-4.62, +0.25,-7.08, +-0,-9.27, +-0,-10.69, +0,-11.4, +0.23,-12.03, +0.45,-11.9, +0,-12.05, +0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0.23,-12.03, +-2.11,-11.98, +-2.13,-12.08, +-2.45,-12.44, +-4.45,-12.24, +-6.29,-13.05, +-7.48,-12.96, +-7.48,-12.96, +-7.84,-12.71, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.2,-10.97, +-9.19,-10.53, +-9.76,-9.95, +-10.48,-9.24, +-10.72,-9, +-10.72,-9, +-10.47,-8.79, +-10.54,-7.57, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-10.72,-9, +-10.72,-9, +-10.43,-9.28, +-9,-10.72, +-9,-10.72, +-8.33,-11.19, +-9.08,-11.45, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.97,-11.88, +-10.02,-12.37, +-8.62,-13.89, +-8.46,-14.65, +-8.46,-14.65, +-8.46,-14.65, +-9.2,-10.97, +-8.33,-11.19, +-7,-12.12, +-7,-12.12, +-6.65,-11.52, +-6.51,-11.28, +-6.51,-11.28, +-4.68,-12.13, +-4.45,-12.24, +-4.6,-12.64, +-4.5,-12.35, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.23,-11.63, +-4.12,-11.33, +-2.09,-11.87, +-1.95,-11.05, +-2.09,-11.87, +-2.09,-11.87, +-1.4,-11.93, +-1.34,-11.5, +-0.21,-11.06, +-0,-11.08, +-0,-10.89, +-0,-9.59, +0.38,-10.08, +2.16,-12.23, +1.05,-11.96, +0,-12.05, +0,-12.05, +-0,-12.05, +0,-12.05, +0,-12.05, +0.3,-12.03, +1.8,-12.22, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +2.13,-10.86, +2.55,-10.75, +3.82,-10.49, +3.79,-10.41, +4.01,-11.02, +4.12,-11.33, +4.12,-11.33, +5.39,-10.73, +6.03,-10.44, +6.27,-10.86, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +7.69,-10.57, +8.46,-10.08, +9,-10.72, +9,-10.72, +9,-10.72, +8.68,-10.35, +8.45,-10.07, +9,-10.72, +9.06,-10.21, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.72,2.05, +-7.75,9.23, +-7.47,8.9, +-7.12,8.49, +-7.28,7.78, +-7.59,6.37, +-7.59,6.37, +-7.84,4.52, +-7.74,4.47, +-8.49,5.77, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.4,7.89, +-7.73,7.82, +-6.39,7.61, +-5.82,6.93, +-5.61,6.68, +-5.4,6.44, +-4.85,5.77, +-3.65,4.51, +-2.62,4.31, +-2.53,4.38, +-2.02,3.68, +-2.04,3.54, +-1.61,3.74, +-2.04,3.54, +-1.88,2.69, +-2.3,1.78, +-1.9,1.4, +-1.61,1.02, +-1.27,-0.24, +-0.73,-1.86, +0.74,-1.97, +1.53,-2.35, +1.34,-3.2, +0.47,-5.34, +-0,-6.19, +-0,-8.29, +-0,-10.5, +0,-11.93, +0.93,-11.97, +1.34,-11.61, +0,-12.05, +0,-12.05, +0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-2.09,-11.87, +-2.18,-12.35, +-2.94,-12.48, +-4.45,-12.24, +-4.9,-13.46, +-6.96,-13.21, +-7.48,-12.96, +-7.48,-12.96, +-8.67,-12.13, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.31,-11.09, +-9,-10.72, +-9,-10.72, +-8.79,-10.47, +-8.65,-10.31, +-9.8,-9.61, +-10.72,-9, +-10.47,-8.79, +-9.98,-8.37, +-10.19,-8.06, +-10.3,-7.91, +-10.19,-8.06, +-11.06,-6.82, +-11.28,-6.51, +-11.28,-6.51, +-11.28,-6.51, +-9.98,-8.37, +-9.98,-8.37, +-8.94,-9.87, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-7.96,-11.58, +-9.29,-11.55, +-9.62,-11.47, +-9.62,-11.47, +-9.73,-11.59, +-9.49,-12.74, +-8.03,-13.91, +-8.46,-14.65, +-8.46,-14.65, +-8.46,-14.65, +-7.16,-12.4, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.87,-11.89, +-6.51,-11.28, +-6.17,-11.44, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.34,-11.93, +-4.12,-11.33, +-3.25,-10.98, +-2.67,-9.84, +-1.55,-8.81, +-1.72,-9.76, +-0.57,-10.25, +-0,-11.73, +0,-12.33, +0,-12.19, +1.05,-11.64, +0.96,-10.41, +1.91,-12.08, +2.26,-12.83, +2.26,-12.83, +1.43,-12.14, +1.49,-11.92, +0,-12.05, +0,-12.05, +1.05,-11.96, +2.23,-12.67, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +1.8,-10.19, +1.72,-9.76, +1.87,-9.72, +2.49,-10.04, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.04,-11.1, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +5.71,-10.59, +6.03,-10.44, +6.33,-10.96, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.98,-10.95, +8.37,-9.98, +9,-10.72, +9,-10.72, +8.89,-10.6, +8.68,-10.35, +8.53,-10.16, +8.58,-10.23, +8.37,-9.98, +9.83,-8.52, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.58,3.08, +-7.75,9.23, +-7.51,8.95, +-7.12,8.49, +-6.96,8.29, +-7.79,6.54, +-7.59,6.37, +-7.35,5.04, +-7.74,4.47, +-8.49,5.77, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.6,8.06, +-7.58,7.99, +-6.37,7.59, +-6.03,7.18, +-5.75,6.85, +-5.68,6.77, +-4.12,5.63, +-2.85,4.94, +-2.63,4.56, +-2.53,4.38, +-2.39,4.15, +-2.3,4.16, +-1.9,3.75, +-2.33,3.33, +-2.12,2.26, +-1.99,2.11, +-1.29,1.45, +-1.38,0.94, +-1.09,-0.38, +0.29,-0.78, +0.74,-1.54, +0.86,-2.81, +0.02,-4.73, +-0.89,-4.93, +-0.12,-6.99, +-0,-9.16, +0,-11.47, +1.74,-11.9, +2.09,-11.87, +1.4,-11.93, +1.07,-12.28, +0,-12.21, +0,-12.25, +-0,-12.73, +-0,-12.48, +-0.99,-12.51, +-2.19,-12.4, +-2.22,-12.61, +-3.61,-12.13, +-4.42,-12.14, +-4.9,-13.46, +-5.91,-13.7, +-7.48,-12.96, +-7.48,-12.96, +-7.48,-12.96, +-8.91,-11.96, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.07,-10.8, +-9,-10.72, +-9,-10.72, +-8.79,-10.47, +-8.37,-9.98, +-8.48,-10.1, +-9.75,-9.73, +-10.47,-8.79, +-9.98,-8.37, +-9.98,-8.37, +-9.98,-8.37, +-9.98,-8.37, +-9.98,-8.37, +-10.7,-7.34, +-11.13,-6.72, +-11.28,-6.51, +-9.98,-8.37, +-9.58,-8.77, +-8.37,-9.98, +-8.89,-10.6, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-7.77,-11.78, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.21,-12.41, +-7.97,-13.8, +-8.23,-14.26, +-8.46,-14.65, +-8.46,-14.65, +-7.16,-12.4, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.88,-11.91, +-6.51,-11.28, +-5.83,-11.6, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.34,-11.93, +-4.12,-11.33, +-3.36,-10.26, +-2.85,-8.96, +-1.55,-8.81, +-1.72,-9.76, +-1.15,-10.2, +-0,-11.44, +0,-12.16, +0.87,-12.3, +2.06,-11.66, +1.98,-11.23, +2.29,-12.96, +2.26,-12.83, +2.26,-12.83, +2.21,-12.51, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.14,-12.11, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +4.01,-13.06, +1.9,-10.75, +2.9,-9.65, +3.39,-9.32, +3.54,-9.73, +3.79,-10.41, +3.46,-9.5, +3.66,-10.05, +4.08,-11.21, +4.12,-11.33, +4.12,-11.33, +4.33,-11.23, +5.87,-10.51, +6.03,-10.44, +6.1,-10.56, +6.27,-10.86, +6.19,-10.72, +6.51,-11.28, +6.51,-11.28, +6.98,-10.95, +8.06,-10.19, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +9.44,-8.91, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +7.02,-5.89, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.46,3.29, +-6.79,9.9, +-7.08,9.36, +-7.12,8.49, +-7.12,8.49, +-7.59,6.37, +-7.59,6.37, +-7.15,5.32, +-7.64,4.61, +-9.65,7.42, +-10.23,8.58, +-10.14,8.51, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.25,8.04, +-6.87,8.19, +-6.37,7.59, +-6.27,7.47, +-5.75,6.85, +-4.5,5.36, +-3.26,5.24, +-3.01,5.22, +-2.74,4.75, +-2.8,4.84, +-2.49,4.51, +-2.39,4.15, +-2.45,3.25, +-2.61,2.6, +-2.11,1.99, +-1.6,1.9, +-1.35,1.39, +-1.11,0.93, +-0.33,0.06, +-0.06,-0.96, +0.56,-2.02, +-0.48,-3.33, +-2.06,-3.14, +-1.19,-5.41, +-0,-7.43, +0.21,-9.87, +2.06,-11.66, +2.09,-11.87, +2.09,-11.87, +2.22,-12.61, +1.94,-12.31, +0,-12.47, +-0,-13.02, +-0,-13.02, +-1.51,-12.89, +-2.26,-12.83, +-2.23,-12.67, +-3.89,-11.51, +-4.27,-11.73, +-4.75,-13.05, +-5.39,-13.23, +-7.16,-12.4, +-7.43,-12.87, +-7.48,-12.96, +-7.48,-12.96, +-8.82,-12.03, +-9.62,-11.47, +-9.62,-11.47, +-9.35,-11.15, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.93,-10.64, +-9,-10.72, +-10.34,-9.38, +-10.31,-8.65, +-10.06,-8.44, +-10.2,-8.56, +-10.08,-8.46, +-9.98,-8.37, +-9.98,-8.37, +-9.98,-8.37, +-10.41,-7.75, +-9.71,-8.64, +-8.37,-9.98, +-8.37,-9.98, +-8.58,-10.23, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.77,-10.88, +-7.96,-11.58, +-9.07,-11.35, +-9.62,-11.47, +-8.91,-11.96, +-7.89,-13.66, +-7.97,-13.8, +-8.37,-14.51, +-8.46,-14.65, +-7.16,-12.4, +-7,-12.12, +-7,-12.12, +-6.75,-12.24, +-6.19,-12.18, +-6.32,-11.37, +-5.83,-11.6, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.18,-11.48, +-4.12,-11.33, +-2.68,-11.19, +-3.01,-9.14, +-1.69,-9.57, +-1.83,-10.4, +-0.57,-10.38, +-0,-11.36, +0,-12.05, +1.05,-11.96, +2.01,-11.39, +2.18,-12.35, +2.43,-13.78, +2.35,-13.3, +2.26,-12.83, +2.26,-12.83, +2.21,-12.55, +2.11,-11.98, +2.09,-11.87, +2.18,-12.35, +2.26,-12.83, +2.26,-12.83, +2.63,-12.73, +4.45,-12.24, +4.75,-13.04, +3.48,-10.5, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.66,-10.05, +3.79,-10.41, +4.09,-11.25, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.43,-11.14, +6.51,-11.28, +6.51,-11.28, +7.44,-10.63, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +9.17,-9.17, +9.17,-9.17, +9.48,-7.96, +6.92,-5.81, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.51,2, +-2.28,1.57, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.68,4.64, +-6.03,10.44, +-6.96,9.39, +-7.12,8.49, +-6.87,8.19, +-7.47,7.05, +-7.59,6.37, +-7.22,6.06, +-7.72,4.79, +-9.73,7.49, +-10.72,9, +-10.72,9, +-10.64,8.93, +-10.06,8.44, +-9.98,8.37, +-9.89,8.3, +-8.29,8.08, +-6.79,8.09, +-6.37,7.59, +-6.03,7.18, +-4.71,5.61, +-3.78,5.73, +-3.26,5.64, +-3.01,5.22, +-3.01,5.22, +-2.69,4.66, +-2.53,4.38, +-2.26,3.38, +-2.41,3.28, +-2.22,2.44, +-1.69,2.36, +-1.21,1.9, +-0.84,1.75, +-0.37,0.53, +0.22,-0.6, +-0.11,-1.21, +-2,-1.3, +-2.67,-1.77, +-1.77,-3.84, +-0.29,-6.3, +-0,-8.46, +0.99,-10.96, +2.06,-11.71, +2.09,-11.87, +2.26,-12.83, +2.26,-12.83, +0,-13.02, +-0,-13.02, +-0,-13.02, +-1.26,-12.91, +-2.26,-12.83, +-2.13,-12.08, +-3.83,-11.4, +-4.23,-11.63, +-4.45,-12.24, +-5.14,-11.92, +-6.51,-11.28, +-7.24,-12.54, +-7.48,-12.96, +-7.48,-12.96, +-7.48,-12.96, +-8.91,-11.96, +-9.34,-11.14, +-9.05,-10.78, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9.86,-9.86, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.52,-8.83, +-9.98,-8.37, +-9.98,-8.37, +-9.98,-8.37, +-9.98,-8.37, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.79,-10.47, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8,-11.42, +-9.62,-11.47, +-8.91,-11.96, +-7.54,-13.06, +-7.93,-13.73, +-7.1,-12.29, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-5.89,-12.64, +-5.41,-12.5, +-5.48,-11.76, +-5.48,-11.76, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.39,-12.06, +-4.33,-11.91, +-4.2,-11.53, +-4.12,-11.33, +-3.67,-11.45, +-2.58,-11.45, +-3.17,-10.58, +-1.75,-9.91, +-1.86,-10.53, +-0.64,-11.02, +-0,-11.57, +0,-12.05, +1.57,-11.91, +2.09,-11.87, +2.24,-11.11, +3.63,-13.13, +4,-13.36, +2.91,-12.84, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.12,-12.03, +2.18,-12.35, +2.26,-12.83, +2.26,-12.83, +4.09,-12.34, +4.45,-12.24, +4.45,-12.24, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.07,-11.17, +4.12,-11.33, +4.12,-11.33, +5.07,-10.88, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.27,-10.86, +6.51,-11.28, +6.51,-11.28, +6.93,-10.99, +7.31,-10.72, +6.82,-11.06, +7.6,-10.37, +8.06,-9.6, +7.75,-9.23, +8.22,-9.79, +8.16,-9.73, +8.06,-9.6, +7.75,-9.23, +8.74,-8.24, +9.23,-7.75, +5.66,-5.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.5,6.06, +-3.63,5.51, +-2.74,4.04, +-2.17,4.2, +-3.17,6.99, +-4.07,6.62, +-2.11,3.65, +-2.55,4.43, +-3.07,5.31, +-3.46,3.21, +-3.32,1.46, +-3.13,1.2, +-2.31,1.64, +-0.78,0.51, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.38,5.85, +-6.03,10.44, +-6.98,9.77, +-7.33,8.74, +-7.12,8.49, +-7.93,7.49, +-7.79,6.54, +-7.59,6.37, +-7.7,6.22, +-8.97,6.78, +-10.72,9, +-10.72,9, +-10.72,9, +-10.6,8.89, +-9.98,8.37, +-9.98,8.37, +-8.91,8.65, +-7.19,8.57, +-6.87,8.19, +-6.22,7.7, +-4.85,6.56, +-3.77,6.26, +-3.44,5.97, +-3.12,5.41, +-3.01,5.22, +-2.64,5.27, +-2.25,4.75, +-2.28,3.96, +-2.04,3.54, +-2.04,3.54, +-1.46,2.53, +-1.07,2.7, +-0.93,2.06, +-0.61,1.05, +0.15,0.17, +-0.95,-0.74, +-3.66,-1.75, +-3.13,-2.62, +-2.37,-3.07, +-0.45,-4.4, +0.5,-6.95, +0.98,-9.78, +1.96,-11.1, +2.09,-11.87, +2.23,-12.67, +2.26,-12.83, +0.38,-12.99, +-0,-13.02, +-0,-13.02, +-0,-13.02, +-2.19,-12.4, +-2.09,-11.87, +-3.45,-11.51, +-4.12,-11.33, +-4.34,-11.93, +-4.68,-12.13, +-6.05,-11.49, +-6.62,-11.47, +-7.21,-12.5, +-7.41,-12.84, +-7.48,-12.96, +-7.24,-12.54, +-8,-11.42, +-9,-10.72, +-8.45,-10.07, +-8.48,-10.1, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9.82,-9.29, +-10.47,-8.79, +-10.72,-9, +-10.72,-9, +-10.08,-8.46, +-9.98,-8.37, +-9.98,-8.37, +-9.98,-8.37, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.44,-10.06, +-8.88,-10.59, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9.14,-10.89, +-9.38,-11.63, +-7.84,-12.71, +-7.48,-12.96, +-7.57,-13.1, +-7.24,-12.54, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.63,-12.29, +-5.01,-12.11, +-5.48,-11.76, +-5.14,-11.92, +-4.45,-12.24, +-4.45,-12.24, +-4.42,-12.14, +-4.25,-11.67, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-3.05,-11.44, +-3.22,-10.9, +-2.55,-10.75, +-1.9,-10.77, +-2.04,-11.55, +-1.31,-11.69, +-0,-12.05, +0,-12.05, +1.46,-11.92, +2.06,-11.69, +2.77,-11.44, +4.79,-13.15, +4.79,-13.15, +4.73,-13, +3.91,-12.39, +2.26,-12.83, +2.26,-12.83, +2.15,-12.19, +2.12,-12.01, +2.24,-12.69, +2.99,-12.63, +4.45,-12.24, +4.45,-12.24, +4.12,-11.33, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.9,-10.72, +3.9,-10.72, +3.96,-10.87, +3.96,-10.87, +3.96,-10.87, +4.12,-11.33, +4.12,-11.33, +4.6,-11.1, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.15,-10.65, +6.27,-10.86, +6.27,-10.86, +6.35,-11, +6.36,-10.5, +7.46,-9.43, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +6.34,-7.55, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.83,6.54, +-3.79,6.57, +-3.76,6.51, +-3.86,6.68, +-3.98,6.9, +-3.98,6.9, +-4.38,7.59, +-4.47,7.74, +-4.62,8, +-4.99,8.64, +-5.37,8.86, +-5.95,9.07, +-5.56,9.58, +-5.77,8.6, +-5.12,4.49, +-5,4.59, +-3.79,3.91, +-0.93,-0.41, +-0.17,-0.13, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.81,1.41, +-6.51,11.28, +-6.11,10.58, +-7.32,9.53, +-7.75,9.23, +-7.28,8.67, +-8.03,7.58, +-8.49,7.12, +-8.39,7.04, +-7.89,6.62, +-9.43,7.91, +-10.64,8.93, +-10.72,9, +-10.97,9.2, +-10.44,9.89, +-9.6,9.82, +-8.73,9.62, +-8.3,9.02, +-7.64,9.11, +-7.12,8.49, +-6.39,8.06, +-4.79,8.31, +-3.9,6.76, +-3.5,6.06, +-3.39,5.87, +-3.17,5.5, +-3.07,5.31, +-2.29,5.32, +-1.73,4.75, +-1.72,4.04, +-1.72,3.69, +-1.27,3.53, +-0.85,3.58, +-0.6,2.62, +-0.43,1.6, +0.08,0.63, +-2.49,-0.66, +-3.82,-1.39, +-3.38,-1.86, +-2.56,-1.1, +-0.76,-3.32, +0.5,-5.25, +1.33,-7.53, +1.67,-9.47, +2,-11.32, +2.19,-12.4, +2.19,-12.4, +0.74,-12.31, +0,-12.81, +-0,-12.7, +-0,-12.93, +-1.31,-12.18, +-2.09,-11.87, +-3.11,-11.6, +-4.12,-11.33, +-4.23,-11.63, +-4.34,-11.93, +-4.8,-12.08, +-6.67,-11.56, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7.44,-11.81, +-8.79,-10.47, +-8.37,-9.98, +-8.43,-10.05, +-9,-10.72, +-9.2,-10.97, +-9.41,-11.22, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9.28,-10.43, +-10.35,-8.68, +-10.72,-9, +-10.72,-9, +-10.16,-8.53, +-9.98,-8.37, +-9.98,-8.37, +-9.98,-8.37, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.68,-10.35, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-8.8,-11.84, +-7.48,-12.96, +-7.48,-12.96, +-7.48,-12.96, +-7.16,-12.4, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.02,-12.58, +-5.7,-11.81, +-5.48,-11.76, +-5.23,-11.88, +-4.45,-12.24, +-4.4,-12.09, +-4.12,-11.33, +-4.12,-11.33, +-5.07,-10.88, +-4.6,-11.1, +-4.12,-11.33, +-4.12,-11.33, +-3.26,-11, +-3.58,-10.47, +-1.96,-11.12, +-2.04,-11.55, +-2.09,-11.87, +-2.12,-12.01, +-0.35,-12.02, +0,-12.05, +0.7,-11.99, +1.95,-11.03, +3.17,-10.58, +4.45,-12.24, +4.73,-13, +4.79,-13.15, +4.54,-12.47, +2.99,-12.63, +2.26,-12.83, +2.17,-12.29, +2.09,-11.87, +2.09,-11.87, +3.24,-12.56, +4.45,-12.24, +4.45,-12.24, +4.12,-11.33, +3.79,-10.41, +3.79,-10.41, +3.96,-10.87, +4.07,-11.19, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.76,-11.03, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +7.4,-9.47, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.81,-6.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.8,6.36, +-4.09,6.84, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.04,7, +-4.5,7.8, +-4.73,8.19, +-4.7,8.14, +-4.96,8.59, +-4.99,8.64, +-5.38,9.32, +-5.54,9.6, +-5.54,9.6, +-5.72,9.9, +-5.83,10.1, +-5.72,9.74, +-6.03,7.89, +-6.01,7.45, +-4.49,3.8, +-1.56,0.69, +0,0, +0,0, +0,0, +-3.62,6.27, +-6.51,11.28, +-6.3,10.9, +-7.17,9.63, +-7.75,9.23, +-7.75,9.23, +-8.86,7.89, +-8.86,7.43, +-8.61,7.23, +-8.34,7, +-9.18,7.71, +-10.06,8.44, +-10.47,8.79, +-10.89,9.14, +-9.59,10.59, +-9.14,10.89, +-8.44,10.06, +-8.09,9.65, +-7.75,9.23, +-6.91,8.9, +-5.41,9.37, +-4.96,8.59, +-4.42,7.65, +-3.85,6.66, +-3.5,6.06, +-3.5,6.06, +-3.02,5.57, +-2.22,5.59, +-2.01,5.51, +-1.73,4.75, +-1.43,3.94, +-1.17,3.9, +-0.48,3.83, +-0.1,3.26, +-0.24,2.25, +0.13,1.28, +-3.96,0.62, +-3.91,-0.16, +-2.98,0.54, +-2.63,0.85, +-2.36,-1.13, +-0.71,-3.74, +0.7,-5.64, +1.36,-7.69, +1.67,-9.48, +2.01,-11.39, +2.09,-11.87, +0.7,-11.99, +0,-11.91, +-0,-11.94, +-0.23,-12.36, +-1.9,-12.08, +-2.11,-11.96, +-2.09,-11.87, +-3.45,-11.51, +-4.16,-11.43, +-4.27,-11.73, +-4.31,-11.83, +-5.88,-11.69, +-6.76,-11.7, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-9,-10.72, +-8.48,-10.1, +-8.61,-10.26, +-9.23,-11, +-9.62,-11.47, +-9.62,-11.47, +-9.41,-11.22, +-9.2,-10.97, +-10.53,-9.19, +-10.53,-9.19, +-10.15,-9.57, +-10.72,-9, +-10.72,-9, +-10.51,-8.82, +-9.98,-8.37, +-9.98,-8.37, +-9.44,-8.91, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.79,-10.47, +-9,-10.72, +-8.77,-10.88, +-7.62,-12.08, +-7.48,-12.96, +-7.48,-12.96, +-7.48,-12.96, +-7.14,-12.36, +-7,-12.12, +-7,-12.12, +-7,-12.12, +-6.33,-11.72, +-5.99,-11.88, +-4.8,-12.08, +-5.48,-11.76, +-4.71,-12.12, +-4.45,-12.24, +-4.23,-11.63, +-4.12,-11.33, +-4.12,-11.33, +-4.55,-11.13, +-4.3,-11.24, +-4.12,-11.33, +-3.78,-11.42, +-2.94,-11.14, +-3.79,-10.41, +-2.06,-11.71, +-2.09,-11.87, +-2.21,-12.55, +-2.21,-12.51, +-0,-12.21, +-0,-12.05, +0.35,-12.02, +1.92,-10.91, +3.32,-10.54, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +3.36,-12.53, +2.51,-12.76, +2.24,-12.69, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.12,-12.03, +4.4,-12.09, +4.45,-12.24, +4.12,-11.33, +3.79,-10.41, +3.84,-10.56, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.76,-11.03, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +5.86,-10.16, +5.76,-9.97, +6.38,-9.83, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5.81,-6.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.91,6.28, +-4.18,6.86, +-4.02,6.96, +-4.01,6.94, +-3.98,6.9, +-4.24,7.35, +-4.51,7.82, +-4.73,8.19, +-4.94,8.55, +-4.96,8.59, +-5.06,8.77, +-5.44,9.43, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.83,7.52, +-3.24,3.83, +-1.21,1.61, +0,0, +0,0, +-6.46,11.19, +-6.94,10.59, +-7.82,9.32, +-8.09,9.65, +-7.93,9.46, +-8.15,8.83, +-9.23,7.75, +-9.15,7.68, +-8.57,7.19, +-8.8,7.39, +-9.23,7.75, +-9.73,8.16, +-10.23,8.58, +-10.6,8.89, +-9.53,9.51, +-8.37,9.98, +-8.37,9.98, +-7.36,9.5, +-6.25,9.89, +-5.54,9.6, +-5.15,8.92, +-4.47,7.74, +-3.88,6.72, +-3.77,6.53, +-3.01,6.29, +-2.52,6.52, +-2.13,5.87, +-2.06,5.66, +-1.88,5.16, +-1.42,4.67, +-0.79,4.5, +-0.24,4.06, +0,4.08, +-0.08,3.3, +-1.46,2.74, +-3.68,2.12, +-3.53,1.45, +-3.1,2.22, +-2.52,2.12, +-2.81,0.4, +-2.63,-1.86, +-1.37,-4.11, +0.23,-5.52, +1.16,-7.22, +1.63,-9.25, +1.92,-10.89, +0.89,-11.43, +0,-11.2, +-0,-11.4, +-0.67,-11.51, +-2.06,-11.71, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-3.78,-11.42, +-4.4,-12.09, +-4.16,-11.43, +-5.45,-11.3, +-6.51,-11.28, +-6.57,-11.37, +-7,-12.12, +-7,-12.12, +-8.11,-11.34, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9.48,-11.3, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-10.97,-9.2, +-10.15,-9.57, +-9,-10.72, +-10.15,-9.57, +-10.72,-9, +-10.72,-9, +-10.16,-8.53, +-9.98,-8.37, +-9.44,-8.91, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.68,-10.35, +-9,-10.72, +-8.33,-11.19, +-7,-12.12, +-7.16,-12.4, +-6.51,-11.28, +-6.51,-11.28, +-6.59,-11.42, +-6.66,-11.53, +-6.62,-11.47, +-6.28,-11.39, +-5.83,-11.6, +-5.37,-11.81, +-5.1,-11.82, +-4.5,-11.62, +-4.45,-12.24, +-4.75,-12.1, +-4.45,-12.24, +-4.51,-12.39, +-4.57,-12.54, +-4.45,-12.24, +-4.23,-11.63, +-4.12,-11.33, +-3.9,-10.72, +-3.79,-10.41, +-2.97,-11.13, +-2.09,-11.87, +-2.12,-12.03, +-2.26,-12.83, +-2.22,-12.61, +-0.93,-11.97, +-0,-12.05, +0.66,-11.78, +1.92,-10.91, +2.99,-10.63, +2.45,-12.24, +2.99,-12.63, +2.63,-12.73, +2.26,-12.83, +2.26,-12.83, +2.21,-12.51, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +3.78,-11.42, +4.34,-11.93, +4.15,-11.41, +3.79,-10.41, +4.05,-11.12, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.29,-11.78, +4.12,-11.33, +4.12,-11.33, +4.44,-11.18, +5.25,-10.67, +5.39,-10.73, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +5.75,-10.56, +5.18,-10.83, +5.39,-10.73, +5.6,-10.63, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.23,-10.1, +7.32,-9.53, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +5,-5.96, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.32,6.45, +-5.17,7.08, +-4.47,7.63, +-4.28,7.41, +-4.03,6.99, +-4.32,7.48, +-4.47,7.74, +-4.5,7.8, +-4.92,8.53, +-4.96,8.59, +-4.96,8.59, +-5.03,8.72, +-5.05,8.75, +-5.45,9.45, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.23,9.06, +-5.24,7.96, +-2.95,0.41, +-0.95,-0.61, +-6.34,8.77, +-7.84,9.34, +-8.02,9.56, +-8.09,9.65, +-7.89,9.4, +-8.02,9.56, +-8.86,8.49, +-9.23,7.75, +-8.9,7.47, +-9.07,7.61, +-9.55,7.1, +-9.4,6.75, +-9.83,7.64, +-9.96,7.45, +-9.61,7.76, +-8.3,9.89, +-8.37,9.98, +-6.89,9.83, +-6.03,10.44, +-5.54,9.6, +-5.44,9.43, +-4.79,8.31, +-4.15,7.18, +-3.93,6.81, +-3.5,6.06, +-3.5,6.06, +-2.44,6.32, +-2.06,5.66, +-1.99,5.46, +-1.35,4.85, +-0.88,4.98, +-0.59,4.89, +-0.51,4.36, +-2.81,4.18, +-3.66,3.46, +-3.68,2.78, +-3.45,2.13, +-3.2,2.53, +-3.26,2.43, +-3.75,1.49, +-3.98,-0.47, +-3.42,-2.1, +-1.91,-3.53, +-0.03,-5.13, +0.95,-6.91, +0.33,-9.02, +0,-10.32, +0,-11.08, +0,-11.19, +-0.21,-11.39, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.54,-11.75, +-4.12,-11.33, +-4.23,-11.63, +-5.39,-10.73, +-6.03,-11.15, +-6.51,-11.28, +-7.09,-11.47, +-8,-11.42, +-9,-10.72, +-9.1,-10.85, +-9.41,-11.22, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.67,-10.66, +-9.95,-9.76, +-9.57,-10.15, +-10.53,-9.19, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.19,-8.55, +-9.44,-8.91, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.79,-10.47, +-9,-10.72, +-8.33,-11.19, +-7,-12.12, +-7,-12.12, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.83,-11.6, +-5.48,-11.76, +-4.34,-11.93, +-4.23,-11.63, +-4.45,-12.24, +-4.45,-12.24, +-4.68,-12.85, +-4.79,-13.15, +-4.79,-13.15, +-4.66,-12.81, +-4.45,-12.24, +-4.29,-11.78, +-4.01,-11.02, +-3.79,-10.41, +-1.92,-10.91, +-2.09,-11.87, +-2.15,-12.19, +-2.2,-12.47, +-2.12,-12.03, +-0.7,-11.99, +-0,-12.05, +0.32,-11.38, +1.92,-10.91, +2.07,-11.73, +2.26,-12.83, +2.26,-12.83, +2.15,-12.19, +2.09,-11.87, +2.16,-12.23, +2.26,-12.83, +2.19,-12.4, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +3.22,-11.57, +4.12,-11.33, +3.77,-10.35, +3.79,-10.41, +4.01,-11.02, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.34,-11.93, +4.45,-12.24, +4.23,-11.63, +4.12,-11.33, +4.12,-11.33, +4.09,-11.22, +4.12,-11.33, +4.12,-11.33, +4.28,-11.25, +4.33,-11.23, +4.6,-11.1, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +6.03,-10.44, +6.03,-10.44, +6.89,-9.83, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.06,-9.6, +4.19,-4.99, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.75,6.85, +-5.71,6.88, +-5.08,7.34, +-4.48,7.72, +-4.44,7.69, +-4.47,7.74, +-4.47,7.74, +-4.5,7.8, +-4.92,8.53, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-5.25,9.09, +-5.52,9.56, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.48,9.48, +-5.02,8.7, +-5.25,7.71, +-5.42,7.15, +-7.38,8.99, +-8.29,9.88, +-8.27,9.85, +-8.37,9.98, +-8.27,9.85, +-8.37,9.98, +-8.27,9.85, +-9.13,8.44, +-8.49,8.49, +-8.74,7.33, +-9.23,6.07, +-9.6,5.54, +-9.45,5.24, +-9.45,4.33, +-8.83,6.6, +-7.9,9.08, +-7.57,9.75, +-6.03,10.44, +-6.03,10.44, +-5.76,9.97, +-4.76,9.96, +-3.87,9.52, +-3.64,8.61, +-3.98,6.9, +-3.56,6.57, +-2.39,6.58, +-2.39,6.58, +-2.23,6.12, +-2.06,5.66, +-1.47,4.98, +-0.88,4.98, +-0.88,4.98, +-0.78,4.99, +-2.62,4.84, +-3.42,4.34, +-3.32,3.8, +-3.64,3.18, +-3.08,2.82, +-3.17,2.56, +-3.54,1.96, +-4.02,0.55, +-4.02,-0.39, +-3.63,-1.65, +-1.71,-3.5, +-0.75,-4.97, +-0.58,-7.27, +0,-8.94, +-0,-10.5, +-0,-10.89, +-1.57,-10.75, +-2.01,-11.39, +-2.06,-11.71, +-2.67,-11.71, +-4.12,-11.33, +-4.12,-11.33, +-4.2,-11.53, +-4.55,-11.13, +-6.6,-10.43, +-8.23,-10.2, +-9,-10.72, +-9.27,-11.05, +-9.62,-11.47, +-9.69,-11.55, +-9.97,-11.88, +-9.97,-11.88, +-9.7,-11.56, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.2,-10.97, +-9.28,-10.43, +-10.72,-9, +-10.72,-9, +-9.98,-8.37, +-10.51,-8.82, +-10.56,-8.86, +-10.66,-8.94, +-9.77,-9.18, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.45,-10.07, +-9,-10.72, +-9,-10.72, +-8,-11.42, +-7,-12.12, +-6.84,-11.84, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.45,-11.17, +-6.51,-11.28, +-6.51,-11.28, +-5.63,-11.69, +-5.1,-11.82, +-4.16,-11.43, +-4.23,-11.63, +-4.45,-12.24, +-4.72,-12.38, +-5.28,-12.92, +-4.82,-13.25, +-4.93,-13.56, +-4.93,-13.54, +-4.68,-12.85, +-4.41,-12.12, +-2.74,-11.53, +-1.92,-10.91, +-1.95,-11.07, +-2.09,-11.87, +-2.09,-11.87, +-2.16,-12.23, +-2.09,-11.87, +-1.4,-11.93, +1.22,-12.76, +1.19,-12.27, +2.09,-11.87, +2.19,-12.43, +2.17,-12.29, +2.22,-12.61, +2.09,-11.87, +2.09,-11.87, +2.15,-12.19, +2.26,-12.83, +2.21,-12.51, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.43,-11.78, +3.78,-11.42, +3.77,-10.35, +3.69,-10.14, +3.9,-10.72, +4.12,-11.33, +4.07,-11.17, +4.04,-11.1, +4.12,-11.33, +4.23,-11.63, +4.45,-12.24, +4.45,-12.24, +4.23,-11.63, +4.12,-11.33, +3.97,-10.92, +3.82,-10.49, +3.84,-10.56, +4.12,-11.33, +4.12,-11.33, +4.36,-11.21, +4.12,-11.33, +4.12,-11.33, +4.39,-10.66, +4.44,-11.18, +6.03,-10.44, +6.03,-10.44, +6.6,-10.04, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.85,-9.36, +8.91,-9.44, +6.12,-6.12, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.22,7.42, +-5.98,7.21, +-5.39,7.12, +-5.37,7.13, +-5.39,7.2, +-4.73,7.63, +-4.6,7.87, +-4.86,8.24, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-5.01,8.67, +-5.31,9.2, +-5.48,9.5, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.54,9.6, +-5.51,9.54, +-5.02,8.7, +-4.96,8.59, +-4.96,8.59, +-7.23,9.51, +-7.96,9.48, +-7.82,9.32, +-8.3,9.89, +-7.96,9.48, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.66,9.13, +-8.22,7.56, +-8.86,6.59, +-9.43,5.44, +-8.83,4.43, +-9.19,3.65, +-8.39,5.91, +-7.7,8.43, +-7.68,9.15, +-6.03,10.44, +-6.03,10.44, +-5.81,10.54, +-3.86,10.61, +-3.75,10.29, +-3.85,9.01, +-4.2,7.28, +-3.33,6.97, +-2.39,6.58, +-1.59,6.65, +-2.36,6.47, +-2.06,5.66, +-2.02,5.56, +-1.24,5.1, +-0.88,4.98, +-1.29,4.84, +-2.67,3.68, +-3.01,5.22, +-3.16,4.38, +-3.35,3.77, +-3.14,3.75, +-2.96,2.79, +-3.4,2.24, +-3.66,1.75, +-3.86,1.24, +-4.06,-0.24, +-2.68,-1.42, +-1.49,-3.69, +-1.1,-5.45, +-0.27,-7.84, +-0,-9.83, +-0.64,-10.89, +-1.92,-10.91, +-1.92,-10.91, +-2.83,-10.89, +-3.97,-10.92, +-3.91,-10.74, +-4.33,-10.87, +-5.63,-10.45, +-6.03,-10.44, +-7.59,-10.13, +-8.68,-10.35, +-9.1,-10.85, +-9.62,-11.47, +-9.83,-11.71, +-10.25,-12.21, +-10.25,-12.21, +-10.25,-12.21, +-10.04,-11.96, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.55,-11.38, +-9.34,-11.14, +-11.01,-9.62, +-10.32,-9.24, +-9.07,-7.91, +-9.98,-8.37, +-10.35,-8.68, +-10.72,-9, +-9.94,-9.32, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.68,-10.35, +-8.37,-9.98, +-8.79,-10.47, +-9,-10.72, +-8.75,-10.9, +-7.22,-11.97, +-7,-12.12, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.4,-11.09, +-6.51,-11.28, +-6.28,-11.39, +-5.44,-11.64, +-4.12,-11.33, +-4.12,-11.33, +-4.23,-11.63, +-4.8,-12.08, +-6.3,-12.05, +-5.58,-12.96, +-5.01,-13.76, +-5.12,-14.07, +-5.12,-14.07, +-4.9,-13.46, +-3.32,-12.29, +-2.01,-11.39, +-1.92,-10.91, +-1.92,-10.91, +-2.04,-11.55, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-1.4,-11.93, +1.62,-13.85, +2.3,-13.04, +2.22,-12.61, +2.16,-12.23, +2.09,-11.87, +2.23,-12.67, +2.21,-12.51, +2.23,-12.67, +2.26,-12.83, +2.26,-12.83, +2.21,-12.51, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.68,-10.24, +3.49,-9.59, +3.38,-10.52, +3.58,-10.47, +3.58,-10.47, +3.93,-10.79, +4.12,-11.33, +4.12,-11.33, +4.45,-12.24, +4.45,-12.24, +4.18,-11.48, +3.96,-10.87, +3.9,-10.72, +3.79,-10.41, +3.9,-10.72, +4.12,-11.33, +4.12,-11.33, +4.66,-11.21, +5.55,-11.01, +4.44,-11.18, +5.07,-10.35, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +7.17,-9.63, +7.82,-9.32, +7.75,-9.23, +7.75,-9.23, +7.91,-9.07, +8.68,-8.65, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.54,7.44, +-6.35,7.56, +-6.1,7.27, +-6.04,7.2, +-6.09,7.26, +-5.94,7.22, +-5.81,7.93, +-5.87,7.92, +-5.12,8.48, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-5.01,8.68, +-5.11,8.86, +-5.3,9.17, +-5.39,9.34, +-5.49,9.51, +-5.43,9.4, +-5.54,9.6, +-5.48,9.48, +-5.01,8.67, +-4.96,8.59, +-4.96,8.59, +-6.82,9.02, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.54,8.98, +-7.19,8.57, +-7.68,6.45, +-7.59,6.37, +-8.37,5.27, +-8.59,4.96, +-8.59,4.96, +-7.81,6.06, +-7.71,7.9, +-6.76,9.14, +-6.03,10.44, +-6.03,10.44, +-5.39,10.73, +-4.01,11.02, +-3.79,10.41, +-3.92,9.92, +-4.55,7.88, +-3.15,7.29, +-1.74,7.66, +0.27,7.23, +-0.22,6.18, +-2.06,6, +-2.1,5.76, +-1.74,5.41, +-0.97,4.95, +-1.85,5.37, +-3.39,3.4, +-2.94,4.47, +-2.96,5.13, +-3.03,4.13, +-3.3,3.66, +-3.03,2.87, +-3.26,2.43, +-3.74,1.61, +-3.88,1.16, +-4.06,0, +-3.03,-0.45, +-2.01,-2.32, +-1.63,-4.21, +-0.78,-6.93, +-0,-8.94, +-1.18,-10, +-1.82,-10.34, +-1.92,-10.91, +-3.79,-10.41, +-3.79,-10.41, +-4.76,-9.96, +-5.95,-10.3, +-5.97,-10.34, +-5.92,-10.25, +-9.62,-8.67, +-10.72,-9, +-11.22,-9.41, +-10.85,-10.24, +-10.95,-11.2, +-10.45,-11.55, +-10.04,-11.96, +-10.01,-11.93, +-9.73,-11.59, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-10.54,-10.54, +-7.12,-8.49, +-7.75,-9.23, +-8.95,-8.95, +-9.05,-9.47, +-9.8,-9.78, +-9.2,-10.27, +-8.37,-9.98, +-8.72,-10.39, +-8.37,-9.98, +-8.58,-10.23, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-9,-10.72, +-7.91,-11.48, +-7,-12.12, +-7,-12.12, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.43,-11.14, +-6.03,-10.44, +-6.15,-10.65, +-6.03,-10.44, +-4.18,-11.48, +-4.12,-11.33, +-4.12,-11.33, +-4.23,-11.63, +-5.14,-11.92, +-6.71,-12.06, +-5.03,-13.04, +-4.97,-13.66, +-5.12,-14.07, +-5.12,-14.07, +-4.71,-12.95, +-2.43,-12.34, +-1.97,-11.15, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-2.06,-11.71, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +0,-13.83, +0.38,-12.99, +0.35,-12.35, +1.05,-11.96, +0.7,-11.99, +1.42,-12.09, +2.15,-12.19, +0.73,-12.8, +0.85,-12.95, +1.51,-12.89, +1.22,-12.08, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +1.87,-10.59, +2.08,-10.17, +1.92,-10.91, +1.92,-10.91, +3.48,-10.5, +4,-10.98, +4.12,-11.33, +4.12,-11.33, +4.4,-12.09, +5.23,-11.88, +6.19,-10.72, +5.07,-10.88, +4.01,-11.02, +3.87,-10.64, +3.9,-10.72, +4.07,-11.17, +4.12,-11.33, +5.44,-11.64, +6.35,-11, +5.75,-10.56, +6.03,-10.44, +6.03,-10.44, +6.24,-10.81, +6.51,-11.28, +7.13,-10.85, +8.16,-9.73, +7.75,-9.23, +8.86,-8.12, +9.36,-7.85, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +6.78,-5.69, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.48,6.78, +-6.75,7.51, +-6.53,7.69, +-6.37,7.59, +-6.32,7.53, +-6.29,7.5, +-6.37,7.59, +-6.37,7.59, +-5.98,7.9, +-5.14,8.48, +-4.99,8.57, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-5.05,8.75, +-5.15,8.92, +-5.25,9.09, +-5.35,9.26, +-5.45,9.45, +-5.03,8.71, +-4.96,8.59, +-4.96,8.59, +-6.82,9.02, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.59,9.05, +-7.26,8.65, +-7.12,8.49, +-7.12,8.49, +-7.59,6.37, +-7.59,6.37, +-7.59,6.37, +-7.59,6.37, +-7.59,6.37, +-7.32,6.64, +-7.12,8.49, +-6.65,8.95, +-6.03,10.44, +-5.81,10.54, +-4.12,11.33, +-4.12,11.33, +-3.83,10.51, +-3.79,10.41, +-3.69,9.63, +-2.76,8.52, +-1.41,9.47, +0,8.13, +0,6.35, +-2.78,5.16, +-2.83,5.77, +-2.22,5.59, +-2.06,5.66, +-2.06,5.66, +-2.8,4.67, +-3.15,4.36, +-3.01,5.22, +-3.01,4.9, +-3.29,4.08, +-3.28,3.39, +-3.13,2.62, +-3.44,2.17, +-3.74,1.61, +-4.02,0.55, +-4.05,-0.39, +-2.47,-1.43, +-2.22,-3.43, +-1.48,-5.48, +-1.35,-7.64, +-1.4,-8.93, +-1.67,-9.44, +-2.6,-10.06, +-3.59,-9.86, +-4.96,-9.87, +-5.54,-9.6, +-5.66,-9.81, +-5.7,-9.88, +-6.81,-10.2, +-9.86,-9.1, +-10.72,-9, +-11.09,-9.31, +-11.47,-9.62, +-11.47,-9.62, +-11.47,-9.62, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-9.62,-11.47, +-8.07,-10.51, +-6.76,-9.24, +-7.25,-8.64, +-7.46,-8.89, +-7.46,-8.89, +-7.79,-9.28, +-7.12,-8.49, +-7.68,-9.15, +-8.37,-9.22, +-9.29,-9.06, +-8.48,-10.1, +-8.92,-10.63, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.58,-10.23, +-9,-10.72, +-8.66,-10.95, +-7.66,-11.65, +-7,-12.12, +-7,-12.12, +-6.84,-11.84, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.24,-10.81, +-6.03,-10.44, +-5.87,-10.51, +-5.39,-10.73, +-4.76,-11.03, +-4.12,-11.33, +-4.12,-11.33, +-4.23,-11.63, +-5.6,-11.71, +-5.96,-12.49, +-4.79,-13.15, +-4.84,-13.3, +-5.01,-13.76, +-4.95,-13.61, +-3.47,-12.84, +-2.15,-12.19, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-2.01,-11.39, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +0,-13.35, +0,-13.02, +0,-12.27, +0,-12.05, +0,-12.05, +0,-12.16, +0.23,-12.36, +0.23,-12.57, +0,-13.02, +0,-12.92, +0,-12.66, +0.35,-12.02, +1.02,-11.8, +1.99,-11.27, +2.09,-11.87, +2.09,-11.87, +1.98,-11.23, +1.92,-10.91, +1.92,-10.91, +2.86,-10.66, +4.01,-11.02, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +5.55,-11.01, +6.44,-11.16, +5.94,-10.94, +6.03,-10.44, +5.34,-10.58, +4.12,-11.33, +4.12,-11.33, +4.09,-11.22, +4.12,-11.33, +4.64,-11.17, +6.08,-10.53, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.35,-11, +6.82,-11.06, +8.37,-9.98, +8.37,-9.98, +9.05,-9.07, +9.23,-7.75, +9.6,-8.06, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +6.65,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.49,7.12, +-8.46,7.1, +-7.51,7.81, +-6.72,7.67, +-6.39,7.61, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.36,7.61, +-6.3,7.66, +-5.69,8.1, +-5.52,8.21, +-5.02,8.55, +-5.28,8.38, +-5.31,8.36, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-5.01,8.68, +-5.28,9.15, +-5.16,8.94, +-4.97,8.6, +-4.96,8.59, +-4.96,8.59, +-6.82,9.02, +-7.47,8.9, +-7.33,8.74, +-7.47,8.9, +-7.47,8.9, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7,8.34, +-7.59,6.37, +-7.59,6.37, +-7.59,6.37, +-7.59,6.37, +-7.39,6.58, +-6.37,7.59, +-7.12,8.49, +-6.42,8.98, +-5.54,9.6, +-4.52,10.43, +-3.77,10.97, +-3.86,10.61, +-3.79,10.41, +-3.9,10.72, +-3.83,10.51, +-3.38,10.52, +-1.81,10.27, +-1.48,8.38, +-1.57,6.57, +-3.76,4.48, +-4.53,3.96, +-2.99,5.18, +-2.06,5.66, +-2.06,5.66, +-2.17,5.61, +-2.33,5.54, +-3.01,5.22, +-3.01,5.22, +-3.04,4.61, +-3.35,3.77, +-3.62,3.04, +-3.4,2.24, +-3.74,1.61, +-3.97,0.58, +-4.03,-0.35, +-3.37,-1.48, +-3.08,-2.58, +-2.27,-4.63, +-1.76,-6.41, +-1.83,-7.84, +-2.05,-8.67, +-3.35,-9.21, +-4.13,-9.11, +-5.22,-9.03, +-5.54,-9.6, +-5.75,-9.57, +-7.39,-9.37, +-9.21,-9.24, +-10.72,-9, +-10.72,-9, +-10.72,-9, +-10.97,-9.2, +-11.01,-9.62, +-11.22,-9.41, +-10.11,-10.39, +-9.31,-11.09, +-9.41,-11.22, +-9.62,-11.47, +-8.94,-11.15, +-6.27,-9.56, +-4.96,-8.59, +-5.66,-8.09, +-6.37,-7.59, +-6.37,-7.59, +-7.12,-8.49, +-7.12,-8.49, +-7.64,-9.11, +-7.75,-9.23, +-8.27,-9.85, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.65,-10.31, +-9,-10.72, +-9,-10.72, +-7.72,-11.75, +-7.16,-12.4, +-7,-12.12, +-7,-12.12, +-6.84,-11.84, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.35,-11, +-6.03,-10.44, +-5.55,-10.66, +-4.76,-11.03, +-4.44,-11.18, +-4.12,-11.33, +-4.12,-11.33, +-4.23,-11.63, +-4.45,-12.24, +-4.55,-12.5, +-4.79,-13.15, +-4.79,-13.15, +-4.79,-13.15, +-3.61,-13.47, +-2.26,-12.83, +-2.09,-11.87, +-1.92,-10.91, +-1.71,-10.93, +-1.92,-10.91, +-1.96,-11.12, +-1.84,-11.78, +-2.09,-11.87, +-2.09,-11.87, +-2.02,-11.44, +0,-13.02, +0,-12.7, +-0,-12.05, +-0,-12.05, +0,-12.05, +0,-12.05, +0,-12.86, +0,-13.02, +0,-13.02, +0,-13.02, +0,-12.38, +0,-12.05, +0,-12.05, +2.09,-11.87, +2.15,-12.19, +2.21,-12.51, +2.12,-12.03, +2.37,-10.64, +2.86,-10.66, +4.07,-11.17, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.27,-11.73, +6.28,-11.39, +6.51,-11.28, +6.17,-11.44, +6.03,-10.44, +6.03,-10.44, +5.81,-10.54, +4.44,-11.18, +4.23,-11.63, +4.69,-11.77, +5.85,-10.65, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.6,-10.04, +7.85,-9.36, +8.37,-9.98, +8.14,-9.7, +9.17,-9.17, +9.85,-8.27, +9.79,-8.22, +9.98,-8.37, +9.98,-8.37, +10.14,-8.14, +10.63,-7.44, +7.09,-4.96, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-7.79,7.78, +-6.76,8.06, +-6.5,7.74, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-5.94,7.93, +-6.19,7.74, +-6.24,7.7, +-5.49,8.24, +-5.49,8.24, +-4.96,8.59, +-4.96,8.59, +-5.06,8.53, +-5.06,8.53, +-5.04,8.54, +-4.97,8.58, +-5.01,8.56, +-6.4,8.52, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.79,8.09, +-6.62,7.89, +-6.79,8.09, +-7.17,7.35, +-7.59,6.37, +-7.46,6.51, +-6.64,7.32, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.39,8.06, +-5.51,8.91, +-4.37,10.14, +-3.48,10.5, +-2.23,10.83, +-3.48,10.5, +-3.9,10.72, +-4.12,11.33, +-4.12,11.33, +-3.84,10.56, +-3.35,10.01, +-3.13,8.61, +-3.07,6.74, +-4.01,4.84, +-4.75,3.68, +-3.85,4.59, +-2.38,5.51, +-2.06,5.66, +-2.06,5.66, +-2.06,5.66, +-2.17,5.61, +-2.85,5.17, +-3.38,4.32, +-3.31,3.81, +-3.62,3.04, +-3.63,2.1, +-3.74,1.61, +-4.03,0.47, +-4.05,-0.32, +-3.95,-0.94, +-3.5,-2.02, +-2.85,-3.62, +-2.7,-5.37, +-3.01,-7, +-3.06,-8.4, +-3.64,-8.49, +-4.64,-8.56, +-5.15,-8.92, +-5.8,-9.41, +-6.86,-8.67, +-7.9,-8.49, +-11.29,-7.25, +-10.83,-8.56, +-9.98,-8.37, +-9.98,-8.37, +-10.35,-8.68, +-10.39,-8.72, +-10.72,-9, +-10.72,-9, +-10.34,-9.38, +-9.19,-10.53, +-9.14,-10.89, +-7.98,-10.51, +-5.54,-9.6, +-5.19,-8.99, +-4.96,-8.59, +-5.59,-8.15, +-6.05,-8.14, +-6.59,-8.86, +-7.12,-8.49, +-7.54,-8.98, +-7.75,-9.23, +-7.82,-9.32, +-8.27,-9.85, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.37,-9.98, +-8.68,-10.35, +-9,-10.72, +-9,-10.72, +-7.83,-11.94, +-7.48,-12.96, +-7,-12.12, +-6.76,-11.7, +-6.67,-11.56, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.63,-11.15, +-6.03,-10.44, +-5.39,-10.73, +-4.44,-11.18, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.23,-11.63, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-2.82,-12.9, +-2.24,-12.72, +-2.13,-12.08, +-2.09,-11.87, +-0.76,-11.26, +-0,-11.57, +-0.99,-11.64, +-1.05,-11.96, +-0,-12.21, +-0.64,-11.19, +-1.51,-11.31, +-1.31,-11.78, +-0,-13.02, +-0,-12.38, +-0,-12.05, +-0,-12.05, +0,-12.05, +0,-12.05, +0,-12.38, +-0,-13.02, +0,-13.02, +0,-13.02, +0,-12.7, +0,-12.54, +1.22,-12.27, +2.15,-12.19, +2.19,-12.4, +2.99,-12.63, +4.32,-12.61, +3.58,-10.47, +3.84,-10.56, +4.05,-11.12, +4.12,-11.33, +4.12,-11.33, +4.33,-11.9, +4.45,-12.24, +5.14,-11.92, +6.51,-11.28, +6,-11.52, +6.03,-10.44, +6.03,-10.44, +6.09,-10.54, +6.35,-11, +6.51,-11.28, +6.51,-11.28, +6.45,-11.17, +6.11,-10.58, +6.03,-10.44, +7.01,-9.75, +7.75,-9.23, +7.75,-9.23, +8.06,-9.6, +8.75,-8.74, +9.73,-8.16, +9.98,-8.37, +9.98,-8.37, +10.63,-7.44, +10.99,-6.93, +11.28,-6.51, +11.28,-6.51, +7.52,-4.34, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.13,7.66, +-8.88,7.46, +-8.6,7.22, +-8.39,7.23, +-8.02,7.63, +-6.96,8.29, +-6.57,7.82, +-6.46,7.69, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.33,7.63, +-5.72,8.07, +-5.15,8.47, +-5.01,8.56, +-5.66,8.14, +-5.89,7.96, +-5.82,8.01, +-5.18,8.45, +-5.61,8.16, +-6.47,8.48, +-7.04,8.39, +-7.12,8.49, +-7.12,8.49, +-6.62,7.89, +-6.23,7.72, +-6,7.86, +-6,7.87, +-5.56,8.12, +-6.35,7.57, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-5.81,7.99, +-4.96,8.59, +-4.08,8.96, +-2.83,10.14, +-2.34,10.8, +-1.92,10.91, +-2.13,10.86, +-3.86,10.61, +-4.12,11.33, +-4.12,11.33, +-4.01,11.02, +-3.78,10.39, +-3.96,8.52, +-3.9,6.76, +-4.2,5.1, +-4.62,3.87, +-4.12,4.37, +-3.44,4.92, +-2.22,5.59, +-2.06,5.66, +-2.06,5.66, +-2.06,5.66, +-2.38,5.51, +-3.87,4.62, +-3.4,3.89, +-3.87,3.25, +-4.04,2.45, +-4.03,1.47, +-4.03,0.55, +-4.06,-0.08, +-4.03,-0.39, +-3.94,-1.29, +-3.96,-2.94, +-3.67,-4.61, +-4.24,-6.2, +-4.31,-7.46, +-4.53,-7.84, +-5.09,-8.1, +-6.38,-7.9, +-7.12,-8.49, +-7.12,-8.49, +-7.12,-8.49, +-8.87,-7.75, +-11.13,-6.72, +-10.27,-7.96, +-9.89,-8.3, +-9.73,-8.16, +-9.98,-8.37, +-10.35,-8.68, +-10.72,-9, +-10.72,-9, +-10.15,-9.57, +-9,-10.72, +-7.84,-10.35, +-5.54,-9.6, +-5.54,-9.6, +-5.44,-9.43, +-4.96,-8.59, +-4.96,-8.59, +-5.35,-9.26, +-6.59,-8.86, +-7.43,-8.86, +-7.75,-9.23, +-7.75,-9.23, +-7.75,-9.23, +-8.28,-9.87, +-8.37,-9.98, +-8.37,-9.98, +-7.75,-10.41, +-7.82,-10.49, +-7.75,-10.8, +-7.17,-11.34, +-7,-12.12, +-7.05,-12.21, +-7,-12.12, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.83,-11.6, +-5.75,-10.92, +-4.76,-11.03, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.23,-11.63, +-4.45,-12.24, +-4.45,-12.24, +-4.18,-12.31, +-2.63,-12.73, +-2.26,-12.83, +-2.18,-12.35, +-1.74,-11.9, +-0.7,-11.5, +-0.26,-11.18, +-0,-12.05, +-0,-12.05, +-0,-12.21, +-0,-12.38, +-0.32,-11.05, +-0.96,-11, +-0.64,-12.32, +-0,-13.02, +-0,-12.59, +-0,-12.05, +-0,-12.05, +-0,-12.05, +0,-12.16, +-0,-12.81, +-0,-13.35, +0,-13.61, +0,-13.55, +0,-13.02, +0.75,-12.96, +2.26,-12.83, +2.26,-12.83, +3.05,-12.78, +4.79,-13.15, +4.84,-13.3, +3.84,-10.56, +4.04,-11.1, +4.01,-11.02, +4.07,-11.17, +4.29,-11.78, +4.37,-12.01, +6.51,-11.28, +6.43,-11.14, +6.35,-11, +6.51,-11.28, +6.27,-10.86, +6.43,-11.14, +6.35,-11, +6.45,-11.17, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.19,-10.72, +6.89,-9.83, +7.75,-9.23, +7.9,-9.08, +8.57,-8.41, +9.07,-7.91, +9.6,-8.06, +10.56,-7.55, +11.13,-6.72, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +7.52,-4.34, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.55,8.01, +-9.23,7.75, +-9.17,7.69, +-8.88,7.46, +-8.62,7.52, +-7.87,7.77, +-7.21,8.41, +-7,8.34, +-6.51,7.76, +-6.4,7.63, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.05,7.83, +-6.08,7.82, +-5.26,8.39, +-5.41,8.29, +-6.36,7.61, +-6.36,7.61, +-5.76,8.05, +-5.99,7.89, +-6.38,7.83, +-6.37,7.59, +-6.87,8.19, +-6.16,8.23, +-5.19,8.42, +-4.96,8.59, +-5.01,8.56, +-5.32,8.36, +-4.96,8.59, +-5.21,8.45, +-6.15,7.79, +-6.37,7.59, +-6.37,7.59, +-6.22,7.7, +-5.66,8.09, +-4.79,8.31, +-3.94,8.7, +-2.83,9.47, +-1.81,10.27, +-1.92,10.91, +-1.92,10.91, +-2.34,10.8, +-3.69,10.77, +-4.09,11.22, +-4.12,11.33, +-4.48,10.44, +-5.09,8.81, +-4.25,7.37, +-4.33,5.43, +-4.62,3.87, +-4.45,4.04, +-3.87,4.62, +-3.09,5.12, +-2.06,5.66, +-2.06,5.66, +-2.27,5.56, +-3.51,4.54, +-4.05,4.41, +-4.01,3.68, +-3.87,3.25, +-4.44,2.39, +-4.35,1.33, +-4.02,0.71, +-4.4,0.12, +-4.21,-0.35, +-4.51,-1.36, +-4.5,-2.26, +-4.69,-3.48, +-4.99,-5.32, +-5.04,-6.36, +-5.54,-7, +-5.96,-7.1, +-6.62,-7.89, +-7.12,-8.49, +-7.12,-8.49, +-7.12,-8.49, +-7.29,-8.32, +-11.28,-6.51, +-10.81,-6.24, +-9.58,-7.43, +-9.23,-7.75, +-9.4,-7.89, +-9.98,-8.37, +-6.82,-8.63, +-7.27,-9.4, +-6.81,-9.7, +-7.74,-10.29, +-7.67,-10.2, +-5.86,-10.16, +-5.58,-9.67, +-5.54,-9.6, +-5.09,-8.81, +-4.96,-8.59, +-5.15,-8.92, +-5.54,-9.6, +-6.25,-9.89, +-7.56,-9.37, +-7.75,-9.23, +-7.75,-9.23, +-7.98,-9.51, +-7.91,-10.3, +-6.82,-11.06, +-6.35,-11, +-6.35,-11, +-6.51,-11.28, +-6.51,-11.28, +-7,-12.12, +-7,-12.12, +-6.76,-11.7, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.83,-11.6, +-4.8,-12.08, +-4.69,-11.77, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.23,-11.63, +-4.45,-12.24, +-4.45,-12.24, +-3.54,-12.48, +-2.26,-12.83, +-2.26,-12.83, +-1.63,-11.69, +-0,-11.08, +0.62,-10.64, +0.14,-10.74, +-0.5,-12.55, +-0.66,-11.45, +-0.45,-11.47, +-0,-11.4, +-1.5,-10.95, +-1.6,-10.94, +-0.64,-12.32, +-0,-13.02, +-0,-12.86, +-0,-12.21, +-0,-12.05, +0,-12.29, +0,-12.86, +-0,-13.51, +-0,-14, +0,-14, +0,-13.63, +0.41,-13.47, +2.37,-13.46, +2.43,-13.78, +2.43,-13.78, +4.73,-13, +4.79,-13.15, +5.85,-13.55, +4.01,-11.02, +3.83,-10.53, +4.01,-11.02, +4.12,-11.33, +4.12,-11.33, +4.55,-11.66, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.67,-11.17, +7.63,-10.23, +7.75,-9.23, +7.75,-9.23, +8.64,-8.34, +9.11,-7.64, +9.36,-7.85, +10.7,-7.13, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +7.52,-4.34, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.56,8.02, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.13,7.66, +-8.61,7.25, +-8.05,7.59, +-7.21,8.41, +-7.04,8.39, +-6.82,8.12, +-6.58,7.84, +-6.57,7.83, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.25,7.69, +-6.22,7.71, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.13,7.78, +-6.05,7.84, +-6.05,7.84, +-6.02,7.86, +-6,7.87, +-5.2,8.43, +-4.96,8.59, +-5.35,8.5, +-6.37,7.59, +-6.37,7.59, +-5.9,7.92, +-4.79,8.31, +-4.23,7.32, +-4.47,7.74, +-3.76,8.07, +-2.78,9.31, +-1.79,10.15, +-1.89,10.72, +-1.92,10.91, +-2.23,10.83, +-3.84,10.56, +-4.12,11.33, +-5.7,9.88, +-5.15,8.92, +-4.47,7.74, +-4.33,5.87, +-4.62,3.87, +-4.62,3.87, +-4.63,3.48, +-4.12,3.38, +-3.63,3.89, +-4.14,3.9, +-4.69,3.49, +-5.27,3.31, +-4.6,4.24, +-3.75,4.29, +-3.88,3.35, +-4.38,2.53, +-4.71,1.82, +-4.93,1.07, +-5.01,0.49, +-5.05,0, +-4.98,-0.88, +-4.98,-1.96, +-5.22,-3.01, +-5.59,-4.17, +-5.45,-5.78, +-5.68,-6.77, +-6.63,-7.04, +-7.39,-6.95, +-8.03,-7.58, +-8.03,-7.58, +-8.34,-7.27, +-8.23,-7.11, +-8.61,-6.94, +-3.99,-6.9, +-7.88,-6.89, +-9.23,-7.75, +-9.23,-7.75, +-8.69,-7.95, +-4.96,-8.59, +-5.54,-9.6, +-5.62,-9.74, +-6.03,-10.44, +-6.03,-10.44, +-4.76,-11.03, +-5.78,-10.02, +-5.54,-9.6, +-5.15,-8.92, +-4.96,-8.59, +-5.15,-8.92, +-5.54,-9.6, +-5.95,-10.3, +-6.6,-10.04, +-7.75,-9.23, +-7.75,-9.23, +-7.96,-9.48, +-6.51,-11.28, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-6.46,-11.19, +-6.62,-11.47, +-7,-12.12, +-6.89,-11.93, +-6.57,-11.37, +-6.51,-11.28, +-6.28,-11.39, +-5.6,-11.71, +-5.6,-11.71, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.34,-11.93, +-4.23,-11.63, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.01,-11.69, +-4.37,-12.01, +-4.45,-12.24, +-2.54,-12.75, +-2.26,-12.83, +-2.18,-12.35, +-1.02,-11.31, +-0,-11.08, +1.89,-10.72, +-0.67,-11.85, +-2.38,-13.51, +-1.98,-11.23, +-1.92,-10.91, +-1.98,-11.23, +-1.92,-10.91, +-1.92,-10.91, +-0.64,-12.32, +-0,-13.02, +-0,-13.02, +-0,-13.02, +-0,-12.54, +-0,-12.9, +-0,-13.02, +-0,-13.83, +-0,-14, +0,-14.16, +0,-14.24, +2.03,-13.82, +2.43,-13.78, +2.43,-13.78, +3.61,-13.47, +4.88,-13.41, +6.1,-13.37, +7.7,-13.34, +3.97,-10.92, +3.76,-10.84, +3.9,-11.39, +4.12,-11.33, +4.12,-11.33, +5,-11.1, +6.3,-10.9, +6.51,-11.28, +6.84,-11.84, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +7.67,-10.47, +8.16,-9.73, +7.85,-9.36, +8.49,-8.49, +9.23,-7.75, +9.23,-7.75, +9.83,-6.89, +11.17,-6.45, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.14,-6.43, +7.1,-4.1, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.88,8.29, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-8.98,7.54, +-8.49,7.12, +-8.49,7.12, +-7.8,7.84, +-7.14,8.47, +-7.12,8.49, +-7.12,8.49, +-6.91,8.24, +-6.43,7.66, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.1,7.8, +-5.12,8.48, +-4.96,8.59, +-5.04,8.74, +-6.01,8.11, +-6.37,7.59, +-5.79,7.74, +-4.25,7.37, +-4.47,7.74, +-4.36,7.56, +-4.15,7.18, +-3.89,7.42, +-3.64,8.13, +-2.58,9.31, +-1.86,10.53, +-1.92,10.91, +-3.58,10.47, +-4.37,10.14, +-5.54,9.6, +-5.09,8.81, +-4.31,7.46, +-4.22,5.69, +-4.62,3.87, +-4.62,3.87, +-4.38,2.53, +-4.38,2.53, +-4.93,2.11, +-5.66,2.06, +-5.66,2.06, +-5.5,3.17, +-4.97,4.06, +-4.24,4.43, +-3.79,3.5, +-4.29,2.65, +-4.63,1.99, +-4.9,1.16, +-5.16,0.59, +-5.53,0.15, +-5.62,-0.99, +-5.64,-1.53, +-5.65,-2.46, +-6.21,-3.85, +-6.23,-5.23, +-6.85,-5.75, +-7.47,-6.27, +-8.26,-6.93, +-8.49,-7.12, +-8.49,-7.12, +-8.49,-7.12, +-8.49,-7.12, +-5.83,-7.13, +-4.49,-6.55, +-5.43,-6.33, +-5.64,-6.86, +-6.18,-7, +-8.05,-7.17, +-4.96,-8.59, +-5.54,-9.6, +-5.81,-10.06, +-6.03,-10.44, +-5.81,-10.54, +-5.18,-10.83, +-4.76,-11.03, +-4.45,-10.34, +-4.89,-9.76, +-5.09,-8.81, +-5.41,-9.37, +-5.54,-9.6, +-5.54,-9.6, +-6,-9.93, +-7.56,-9.37, +-7.56,-9.37, +-7.56,-9.76, +-6.47,-11.21, +-5.97,-10.33, +-5.62,-9.74, +-5.62,-9.74, +-6.89,-10.42, +-7.94,-10.68, +-7.33,-11.89, +-6.67,-11.56, +-6.51,-11.28, +-6.25,-11.4, +-6.17,-11.44, +-5.83,-11.6, +-4.45,-12.24, +-4.45,-12.24, +-4.34,-11.93, +-4.45,-12.24, +-4.45,-12.24, +-4.37,-12.01, +-4.12,-11.33, +-4.12,-11.33, +-2.77,-11.69, +-2.82,-11.84, +-4.12,-11.33, +-4.23,-11.63, +-2.26,-12.83, +-2.18,-12.35, +-2.06,-11.71, +-1.95,-11.07, +-0.32,-11.05, +-0,-11.08, +-1.63,-11.1, +-2.18,-12.35, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-0.68,-12.53, +-0,-13.02, +-0,-13.02, +-0,-13.02, +-0,-13.02, +-0,-13.35, +-0,-13.89, +-0,-14, +-0,-14.64, +0,-14.97, +1.68,-14.5, +2.51,-14.21, +2.54,-14.42, +3.91,-13.94, +4.9,-13.46, +5.37,-13.81, +7.48,-12.96, +7.97,-13.8, +4.12,-11.33, +2.83,-11.55, +2.04,-11.55, +4.12,-11.33, +4.12,-11.33, +4.56,-10.99, +6.03,-10.44, +6.43,-11.14, +6.67,-11.56, +6.57,-11.38, +6.43,-11.14, +6.51,-11.28, +6.51,-11.28, +7.44,-10.63, +7.44,-10.63, +7.75,-10.41, +8.37,-9.98, +7.96,-9.48, +7.75,-9.23, +9.23,-7.75, +9.23,-7.75, +9.43,-7.46, +10.44,-6.03, +10.96,-6.33, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +11.42,-5.36, +11.33,-4.12, +7.25,-3.38, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.93,8.33, +-9.73,8.16, +-9.35,7.84, +-9.25,7.76, +-9.15,7.68, +-8.7,7.3, +-8.72,7.32, +-8.3,7.36, +-7.16,8.46, +-7.12,8.49, +-7.01,8.36, +-6.58,7.84, +-6.43,7.67, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.31,7.65, +-5.63,8.14, +-5.54,8.21, +-5.73,8.16, +-5.04,8.74, +-4.96,8.59, +-5.34,8.87, +-6.01,7.74, +-4.79,7.52, +-4.47,7.74, +-4.47,7.74, +-4.28,7.41, +-3.98,6.9, +-4.24,6.73, +-4.36,6.64, +-3.47,7.74, +-2.06,9.56, +-1.86,10.53, +-3.66,10.05, +-4.52,9.5, +-5.02,8.7, +-4.79,8.31, +-4.15,7.18, +-4.39,5.44, +-4.66,4.44, +-4.82,3.59, +-4.38,2.53, +-4.38,2.53, +-4.99,2.48, +-5.66,2.06, +-5.47,2.48, +-5.22,3.01, +-5.97,3.44, +-6.06,3.5, +-5.76,3.76, +-4.66,2.85, +-4.71,1.82, +-4.98,0.88, +-5.67,0.35, +-6.03,0, +-5.95,-0.81, +-6.09,-1.31, +-6.51,-2.23, +-6.86,-3.31, +-6.63,-4.94, +-7.2,-5.81, +-7.59,-6.37, +-8.49,-7.12, +-8.49,-7.12, +-8.49,-7.12, +-8.49,-7.12, +-8.38,-7.03, +-6.82,-6.74, +-5.23,-6.23, +-4.74,-6.37, +-4.93,-6.24, +-5.33,-6.75, +-5.74,-7.25, +-5.9,-7.92, +-5.25,-9.09, +-5.86,-10.16, +-6.03,-10.44, +-5.39,-10.73, +-4.12,-11.33, +-4.12,-11.33, +-4.04,-11.1, +-3.79,-10.41, +-4.96,-9.87, +-5.54,-9.6, +-5.54,-9.6, +-5.54,-9.6, +-5.54,-9.6, +-5.62,-9.74, +-5.95,-10.3, +-6.11,-10.58, +-6.39,-11.07, +-5.7,-9.88, +-5.25,-9.09, +-5.54,-9.6, +-6.89,-9.83, +-8.37,-9.98, +-8.68,-10.35, +-6.51,-11.28, +-6.51,-11.28, +-6.34,-11.36, +-5.37,-11.81, +-4.68,-12.13, +-4.45,-12.24, +-4.45,-12.24, +-4.38,-12.04, +-4.45,-12.24, +-4.45,-12.24, +-4.15,-11.9, +-2.77,-11.69, +-3.22,-11.57, +-2.77,-11.69, +-2.73,-11.47, +-4.12,-11.33, +-2.99,-11.63, +-2.02,-11.47, +-2,-11.34, +-1.94,-11.02, +-1.92,-10.91, +-1.71,-10.93, +-1.5,-10.95, +-2.04,-11.55, +-2.04,-11.55, +-1.98,-11.23, +-1.92,-10.91, +-1.92,-10.91, +0,-11.4, +0,-11.08, +-0,-12.38, +-0,-13.02, +-0,-13.02, +-0,-13.02, +-0,-13.83, +-0,-14, +-0,-14.32, +-0,-14.81, +0,-14.97, +0,-14.97, +2.6,-14.74, +2.6,-14.74, +3.86,-14.4, +5.12,-14.07, +5.12,-14.07, +6.59,-14.05, +7.97,-13.8, +6.69,-12.98, +4.12,-11.33, +2.83,-11.55, +2.09,-11.87, +3.45,-11.51, +4.12,-11.33, +4.6,-11.1, +6.03,-10.44, +6.19,-10.72, +6.51,-11.28, +6.51,-11.28, +6.43,-11.14, +7.13,-10.85, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.06,-9.6, +8.49,-8.49, +9.23,-7.75, +9.23,-7.75, +10.24,-6.31, +10.44,-6.03, +11.17,-6.45, +11.28,-6.51, +11.28,-6.51, +11.28,-6.51, +10.9,-6.3, +11.1,-5.87, +11.32,-4.32, +11.33,-4.12, +7.55,-2.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.63,8.08, +-9.81,8.23, +-9.93,8.33, +-9.53,8, +-9.23,7.75, +-9.2,7.72, +-9.18,7.71, +-7.92,8.36, +-7.33,8.74, +-7.22,8.6, +-7.11,8.47, +-7.04,8.39, +-6.93,8.26, +-6.62,7.89, +-6.54,7.79, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.36,7.61, +-5.5,7.85, +-5.07,8.31, +-4.98,8.57, +-5.05,8.75, +-5.36,9.29, +-5.13,8.89, +-4.96,8.59, +-4.79,8.31, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.11,7.11, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.91,7.29, +-2.84,8.63, +-2.83,9.47, +-3.39,9.32, +-4.7,8.71, +-4.88,8.45, +-4.98,7.59, +-5.12,6.5, +-4.6,5.48, +-4.64,5.22, +-4.72,3.73, +-4.38,2.53, +-4.38,2.53, +-4.38,2.53, +-5.59,2.22, +-5.37,2.7, +-5.22,3.01, +-5.36,3.09, +-6.06,3.5, +-6.06,3.5, +-5.57,3.22, +-4.9,1.91, +-5.42,1.31, +-5.95,0.81, +-6,0.35, +-6.63,0.29, +-6.94,-0.68, +-6.95,-0.54, +-7.25,-1.45, +-7.66,-3.13, +-7.86,-5.42, +-7.69,-6.46, +-8.29,-6.96, +-9,-6.16, +-9.1,-6.24, +-8.49,-7.12, +-7.99,-6.71, +-6.52,-6.65, +-5.12,-6.11, +-5.12,-6.11, +-5.12,-6.11, +-5.54,-6.6, +-5.81,-7.2, +-5.9,-7.92, +-5.4,-8.59, +-5.59,-9.69, +-5.81,-10.06, +-4.97,-10.93, +-4.12,-11.33, +-4.12,-11.33, +-4.07,-11.17, +-3.79,-10.41, +-3.98,-10.32, +-5.54,-9.6, +-5.54,-9.6, +-5.54,-9.6, +-5.54,-9.6, +-5.54,-9.6, +-5.81,-10.06, +-6.03,-10.44, +-6.35,-11, +-5.59,-9.69, +-4.96,-8.59, +-5.54,-9.6, +-7.49,-9.28, +-8.37,-9.98, +-8.37,-9.98, +-6.51,-11.28, +-6.51,-11.28, +-6.51,-11.28, +-5.14,-11.92, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.09,-12.34, +-3.36,-12.53, +-4.45,-12.24, +-3.74,-11.93, +-3.78,-11.42, +-2.09,-11.87, +-2.06,-11.71, +-2.66,-11.05, +-3.11,-11.6, +-1.95,-11.07, +-1.92,-10.91, +-1.92,-10.91, +-2.23,-10.83, +-3.17,-10.58, +-2.23,-10.83, +-2.01,-11.39, +-2.04,-11.55, +-2.07,-11.75, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +0,-11.08, +0.64,-11.02, +-0,-12.29, +-0,-13.35, +-0,-13.35, +-0,-13.51, +-0,-14, +-0,-14.36, +-0,-14.97, +-0,-15.13, +0,-15.13, +1.38,-15.66, +2.66,-15.1, +3.02,-14.63, +5.17,-14.22, +5.12,-14.07, +5.51,-13.88, +4.45,-12.24, +4.29,-11.78, +4.12,-11.33, +4.12,-11.33, +3.87,-11.39, +4.12,-11.33, +4.01,-11.02, +4.12,-11.33, +5.18,-10.83, +6.03,-10.44, +6.13,-10.62, +6.51,-11.28, +6.51,-11.28, +7.13,-10.85, +8.37,-9.98, +8.23,-9.81, +8.16,-9.73, +8.37,-9.98, +8.37,-9.98, +8.32,-9.91, +8.23,-9.81, +9.32,-8.12, +9.48,-7.96, +10.07,-7.96, +11.09,-6.4, +11.09,-6.4, +11.28,-6.51, +11.39,-6.28, +11.92,-5.14, +11.42,-5.19, +10.59,-5.71, +11.18,-4.44, +11.33,-4.12, +11.33,-4.12, +7.09,-2.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.32,7.82, +-9.3,7.8, +-9.38,7.87, +-9.3,7.8, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-8.76,8.26, +-7.78,9.2, +-7.66,9.13, +-7.18,8.55, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.02,8.37, +-6.84,8.16, +-6.48,7.73, +-6.39,7.61, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.34,7.56, +-5.34,8.02, +-4.96,8.59, +-4.99,8.64, +-5.15,8.92, +-5.12,8.87, +-4.96,8.59, +-4.96,8.59, +-4.52,7.84, +-4.47,7.74, +-4.42,7.65, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.4,7.53, +-3.64,8.49, +-4.61,8.75, +-4.96,8.59, +-5.4,7.62, +-5.75,6.85, +-5.4,6.44, +-4.98,5.94, +-5.44,4.57, +-4.69,4.11, +-4.32,2.61, +-4.38,2.53, +-4.38,2.53, +-4.74,2.26, +-5.16,1.88, +-5.27,2.66, +-5.22,3.01, +-5.5,3.17, +-5.34,3.08, +-5.22,3.01, +-5.29,2.85, +-5.22,3.01, +-5.58,2.62, +-6.01,2.84, +-6.54,2.38, +-6.84,1.41, +-6.96,0.41, +-7.57,-0.92, +-7.91,-2.39, +-7.97,-5.27, +-8.56,-6.34, +-9.04,-6.33, +-9.6,-5.54, +-9.6,-5.54, +-8.86,-6.59, +-8.04,-6.75, +-6.33,-6.21, +-5.12,-6.11, +-5.12,-6.11, +-5.12,-6.11, +-5.64,-6.73, +-5.75,-6.85, +-5.93,-7.51, +-5.43,-8.26, +-5.54,-9.6, +-5.54,-9.6, +-4.62,-10.74, +-4.12,-11.33, +-4.12,-11.33, +-4.04,-11.1, +-3.79,-10.41, +-3.85,-9.74, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-5.05,-8.75, +-5.35,-9.26, +-5.54,-9.6, +-5.95,-10.3, +-6.27,-10.86, +-5.54,-9.6, +-5.35,-9.26, +-5.62,-9.74, +-7.17,-9.63, +-8.27,-9.85, +-8.06,-10.19, +-6.51,-11.28, +-6.51,-11.28, +-6,-11.52, +-4.45,-12.24, +-4.8,-12.08, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-4.45,-12.24, +-3.7,-11.69, +-2.77,-11.69, +-2.09,-11.87, +-2.04,-11.55, +-1.92,-10.91, +-1.96,-11.12, +-1.92,-10.91, +-1.92,-10.91, +-1.94,-11.02, +-2.56,-10.85, +-3.94,-10.82, +-4.09,-11.22, +-2.77,-11.69, +-1.96,-11.12, +-2.02,-11.47, +-1.98,-11.23, +-1.96,-11.12, +-1.92,-10.91, +0.64,-11.02, +0.43,-11.04, +-0,-12.05, +-0.25,-12.79, +-0,-12.92, +-0,-14, +-0,-14.32, +-0,-14.97, +-0,-15.62, +-0,-15.94, +0.92,-15.75, +2.77,-15.7, +2.87,-15, +4.99,-14.55, +5.45,-14.98, +5.34,-14.67, +6.93,-13.81, +4.45,-12.24, +4.2,-11.53, +4.16,-11.43, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.09,-11.22, +4.55,-11.13, +5.85,-10.52, +6.03,-10.44, +6.43,-11.14, +6.19,-10.72, +6.39,-11.07, +6.78,-10.3, +7.85,-9.36, +7.75,-9.23, +7.82,-9.33, +8.27,-9.85, +8.37,-9.98, +8.37,-9.98, +7.96,-9.48, +9.85,-8.27, +9.98,-8.37, +10.63,-7.44, +11.28,-6.51, +11.28,-6.51, +11.4,-6.25, +12.24,-4.45, +12.24,-4.45, +11.33,-4.12, +11.33,-4.12, +11.33,-4.12, +11.21,-4.08, +11.33,-4.12, +7.4,-2.69, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.88,8.29, +-9.4,7.89, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-8.64,8.38, +-7.78,9.2, +-7.75,9.23, +-7.39,8.8, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.11,8.47, +-7.08,8.44, +-6.68,7.96, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.15,7.33, +-6.12,7.3, +-6.23,7.43, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-5.8,8.02, +-5.01,8.56, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.79,8.31, +-4.47,7.74, +-4.25,7.37, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.24,6.73, +-3.55,6.15, +-4.86,6.81, +-5.25,7.72, +-5.02,8.01, +-5.75,6.85, +-5.87,6.73, +-5.75,6.85, +-5.68,6.77, +-5.22,5.56, +-4.62,3.87, +-4.4,3.06, +-4.38,2.53, +-4.38,2.53, +-4.5,2.26, +-4.75,1.73, +-4.85,1.77, +-4.94,2.6, +-5.13,2.96, +-4.98,2.99, +-4.47,3.49, +-4.86,4.08, +-5.36,4.5, +-5.36,4.5, +-5.75,3.94, +-6.12,3.38, +-6.68,2, +-6.96,0.41, +-7.55,-1.06, +-7.83,-2.69, +-8.59,-3.94, +-8.89,-4.91, +-9.17,-4.63, +-9.41,-4.11, +-9.33,-4.28, +-8.86,-4.67, +-8.21,-4.74, +-6.96,-5.11, +-6.11,-5.12, +-5.78,-5.45, +-5.12,-6.11, +-5.26,-6.27, +-5.75,-6.85, +-6.03,-7.18, +-5.27,-8.37, +-5.24,-8.7, +-5.48,-9.48, +-4.37,-10.14, +-3.86,-10.61, +-4.05,-11.12, +-3.9,-10.72, +-4.05,-9.44, +-4.33,-8.64, +-4.8,-8.31, +-4.8,-8.31, +-4.8,-8.31, +-4.8,-8.31, +-4.9,-8.49, +-5.15,-8.92, +-5.59,-9.69, +-6.15,-10.65, +-5.65,-9.78, +-5.59,-9.69, +-5.92,-10.25, +-7.36,-9.5, +-8.23,-9.81, +-8.37,-9.98, +-6.51,-11.28, +-5.96,-11.3, +-4.53,-11.78, +-4.36,-11.99, +-4.45,-12.24, +-4.45,-12.24, +-4.34,-11.93, +-4.34,-11.93, +-4.31,-11.83, +-4.2,-11.53, +-3.61,-11.46, +-2.09,-11.87, +-2.07,-11.73, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.98,-11.23, +-2.09,-11.87, +-2.43,-11.78, +-3.78,-11.42, +-2.77,-11.69, +-2.09,-11.87, +-2.09,-11.87, +-1.66,-11.26, +-1.28,-10.97, +-2.09,-11.87, +0,-11.08, +0.32,-11.05, +0,-11.2, +0,-12.05, +0,-12.05, +-0,-14, +-0,-14.81, +-0,-15.21, +-0,-15.94, +0,-16.26, +1.85,-15.78, +2.77,-15.7, +3.98,-14.75, +5.45,-14.98, +5.45,-14.98, +6.29,-14.59, +7.97,-13.8, +4.45,-12.24, +4.29,-11.78, +4.12,-11.33, +4.23,-11.63, +4.46,-11.43, +5.07,-10.88, +5.86,-10.16, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.11,-10.58, +6.87,-10.44, +6.64,-10.45, +7.75,-9.23, +8.16,-9.73, +7.75,-9.23, +7.75,-9.23, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.54,-9.59, +9.98,-8.37, +9.98,-8.37, +10.63,-7.44, +11.28,-6.51, +11.28,-6.51, +12.34,-5.09, +12.24,-4.45, +12.24,-4.45, +11.44,-4.16, +11.02,-4.01, +10.87,-3.96, +11.1,-4.04, +11.48,-4.18, +7.85,-2.86, +1.53,-0.56, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.98,8.37, +-9.88,8.29, +-9.75,8.18, +-9.75,8.18, +-9.75,8.18, +-9.68,8.27, +-8.96,8.62, +-8.44,8.58, +-7.77,9.21, +-7.75,9.23, +-7.58,9.03, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.64,7.92, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.16,7.35, +-5.77,6.87, +-5.75,6.85, +-5.83,6.95, +-6.31,7.52, +-6.37,7.59, +-6.37,7.59, +-5.7,8.09, +-4.98,8.57, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.55,7.88, +-4.15,7.18, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.36,6.64, +-4.21,5.96, +-5.41,5.37, +-5.6,6.31, +-5.44,6.48, +-5.64,6.73, +-5.93,6.67, +-5.75,6.85, +-5.54,7, +-5.02,5.98, +-4.72,4.23, +-3.96,3.13, +-4.29,2.65, +-4.38,2.53, +-4.38,2.53, +-4.44,2.39, +-4.63,1.99, +-4.63,1.99, +-4.29,2.65, +-3.96,3.13, +-3.48,3.81, +-4.39,5.24, +-4.79,5.07, +-5.36,4.5, +-5.59,4.17, +-6.23,3.13, +-6.68,2, +-6.96,0.41, +-7.53,-1.33, +-7.79,-2.84, +-8.71,-3.17, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-8.86,-3.22, +-8.4,-3.06, +-8.07,-3.76, +-7.18,-4.15, +-6.5,-4.55, +-5.82,-5.18, +-5.12,-6.11, +-5.75,-6.85, +-5.75,-6.85, +-5.96,-7.1, +-5.9,-7.92, +-5.19,-8.42, +-5.44,-9.43, +-4.37,-10.14, +-4.08,-10.28, +-4.27,-9.71, +-4.8,-8.31, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.8,-8.31, +-5.05,-8.75, +-5.98,-9.88, +-5.62,-9.74, +-5.78,-10.02, +-5.77,-9.99, +-6.81,-9.69, +-7.2,-10.21, +-6.89,-10.42, +-6.11,-10.58, +-6.03,-10.44, +-5.07,-10.88, +-4.6,-11.1, +-4.18,-11.48, +-4.45,-12.24, +-4.18,-11.48, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-3.11,-11.6, +-2.09,-11.87, +-2.01,-11.39, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.98,-11.23, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-2.09,-11.87, +-0,-11.08, +-0,-11.2, +-0.7,-11.67, +-0,-12.05, +-0,-11.89, +0,-11.57, +0.7,-11.5, +0.26,-11.91, +0,-12.05, +0,-12.05, +-0,-14.32, +-0,-14.97, +-0,-15.7, +0,-16.1, +0,-16.91, +2.8,-15.86, +2.77,-15.7, +5.12,-15.07, +5.45,-14.98, +5.45,-14.98, +7.55,-14, +6.8,-13.28, +4.45,-12.24, +4.45,-12.24, +4.74,-11.93, +5.75,-11.46, +6.51,-11.28, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.99,-8.98, +7.75,-9.23, +8.06,-9.6, +8.27,-9.85, +8.37,-9.98, +9.34,-8.78, +9.98,-8.37, +9.98,-8.37, +10.63,-7.44, +11.84,-6.84, +11.98,-6.92, +12.89,-5.34, +13,-4.73, +12.09,-4.4, +11.1,-4.04, +10.41,-3.79, +10.41,-3.79, +10.87,-3.96, +11.78,-4.29, +12.24,-4.45, +11.65,-5.42, +11.28,-6.51, +3.9,-2.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.94,8.34, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.69,8.69, +-8.52,9.76, +-7.96,9.33, +-7.75,9.23, +-7.75,9.23, +-7.72,9.2, +-7.33,8.74, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.04,8.39, +-6.47,7.71, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.33,7.54, +-5.93,7.06, +-5.87,7, +-6.07,7.23, +-6.36,7.58, +-6.37,7.59, +-6.04,7.79, +-5.02,8.5, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.79,8.31, +-4.09,7.09, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.36,6.64, +-5.16,5.91, +-5.89,5.34, +-6.11,5.12, +-5.78,5.45, +-5.12,6.11, +-5,6.19, +-4.34,7.18, +-4.25,7.37, +-4.91,5.86, +-4.58,4.37, +-3.93,3.17, +-4.1,2.93, +-4.1,2.93, +-4.17,2.63, +-4.28,2.47, +-4.38,2.53, +-4.32,2.61, +-3.9,2.99, +-3.1,3.42, +-4.13,4.93, +-4.5,5.36, +-4.79,5.07, +-5.36,4.5, +-5.83,3.83, +-6.29,3.01, +-6.79,1.61, +-6.97,0.27, +-7.13,-1.57, +-7.69,-2.8, +-8.4,-3.06, +-8.91,-3.24, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-8.4,-3.06, +-8.4,-3.06, +-8.17,-2.98, +-7.42,-2.87, +-7.1,-3.57, +-6.81,-4.11, +-6.28,-4.87, +-5.33,-6.35, +-5.61,-6.68, +-5.75,-6.85, +-6.23,-7.43, +-6.06,-7.81, +-5.43,-8.26, +-5.28,-9.15, +-5.15,-8.92, +-4.92,-8.52, +-4.53,-7.84, +-4.47,-7.74, +-4.47,-7.74, +-4.42,-7.65, +-4.15,-7.18, +-4.15,-7.18, +-4.15,-7.18, +-4.42,-7.65, +-4.8,-8.31, +-6.4,-8.99, +-5.64,-9.76, +-5.54,-9.6, +-5.65,-9.78, +-5.76,-9.97, +-6.03,-10.44, +-6.03,-10.44, +-6.03,-10.44, +-5.86,-10.16, +-5.81,-10.06, +-5.35,-10.66, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-2.6,-11.73, +-2.04,-11.55, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.94,-11, +-1.97,-11.17, +-2.01,-11.42, +-2.04,-11.55, +-1.4,-11.71, +-0,-11.08, +-0,-11.24, +0,-11.62, +0,-11.51, +0,-12.05, +0,-12.05, +0.7,-11.99, +0.35,-12.02, +0,-12.05, +0,-12.05, +-0,-13.56, +0.7,-13.93, +0.71,-14.5, +0.77,-15.66, +1.44,-15.71, +2.99,-15.41, +3.76,-14.99, +5.45,-14.98, +5.45,-14.98, +5.73,-14.85, +8.02,-13.9, +6.28,-12.93, +4.97,-12, +6.05,-11.49, +6.57,-11.37, +6.57,-11.37, +6.51,-11.28, +6.08,-10.53, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +7.1,-9.55, +7.61,-9.07, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.49,-8.95, +9.23,-7.97, +8.05,-9.23, +7.89,-9.4, +8.03,-9.29, +9.32,-8.12, +9.98,-8.37, +10.14,-8.51, +11.15,-7.12, +11.84,-6.84, +12.12,-7, +13.07,-4.97, +13.05,-4.75, +11.93,-4.34, +10.87,-3.96, +10.41,-3.79, +9.87,-4.96, +9.81,-5.66, +11.49,-6.05, +12.22,-5, +12.23,-6.38, +12.4,-7.16, +4.04,-2.33, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.5,7.97, +-9.75,8.18, +-9.93,8.33, +-9.75,8.18, +-9.56,8.06, +-8.76,9.22, +-8.07,9.61, +-7.82,9.32, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.54,8.98, +-7.15,8.52, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.97,8.31, +-6.82,8.12, +-6.76,8.06, +-6.46,7.69, +-6.41,7.64, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-5.97,7.79, +-4.75,8.18, +-4.81,8.32, +-4.92,8.53, +-4.92,8.53, +-4.92,8.53, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.95,8.57, +-4.31,7.46, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.36,6.64, +-5.45,5.78, +-6.11,5.12, +-6.11,5.12, +-6.11,5.12, +-5.45,5.78, +-4.36,6.64, +-3.98,6.9, +-4.11,6.81, +-4.57,5.44, +-4.37,4.12, +-4.05,3.53, +-3.97,3.08, +-3.87,3.25, +-3.12,2.93, +-3.07,2.42, +-4.38,2.53, +-2.9,2.76, +-3.1,3.69, +-4.08,4.86, +-4.5,5.36, +-4.5,5.36, +-4.71,5.15, +-5.36,4.5, +-5.83,3.83, +-6.4,2.76, +-6.79,1.61, +-6.96,-0.41, +-7.09,-1.72, +-7.49,-2.73, +-8.4,-3.06, +-8.4,-3.06, +-8.4,-3.06, +-8.86,-3.22, +-9.32,-3.39, +-8.55,-3.11, +-8.4,-3.06, +-8.29,-3.02, +-7.49,-2.73, +-7.49,-2.73, +-7,-3.78, +-6.64,-4.36, +-5.55,-5.9, +-5.75,-6.85, +-5.75,-6.85, +-6.16,-7.35, +-6.37,-7.59, +-6.37,-7.59, +-5.19,-8.42, +-4.96,-8.59, +-4.77,-8.27, +-4.9,-7.45, +-4.68,-7.59, +-4.47,-7.74, +-4.47,-7.74, +-4.07,-7.04, +-3.99,-6.9, +-3.99,-6.9, +-4.15,-7.18, +-4.06,-8.12, +-6.39,-9.59, +-5.7,-9.88, +-5.62,-9.74, +-5.54,-9.6, +-5.54,-9.6, +-5.86,-10.16, +-5.95,-10.3, +-5.95,-10.3, +-5.7,-9.88, +-5.54,-9.6, +-5.55,-10.13, +-4.12,-11.33, +-4.12,-11.33, +-3.96,-10.87, +-3.96,-10.87, +-4.01,-11.02, +-4.12,-11.33, +-2.6,-11.73, +-1.98,-11.23, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.89,-10.72, +-1.54,-10.56, +-1.92,-10.91, +-1.28,-10.97, +-0,-11.08, +-0,-11.4, +-0,-11.32, +0,-12.05, +0,-12.05, +0.35,-12.02, +0,-12.05, +0,-12.05, +0.26,-12.03, +0,-12.05, +0,-11.89, +0.35,-11.7, +2.09,-11.87, +2.2,-12.47, +2.26,-12.83, +2.4,-13.62, +3.61,-13.47, +4.79,-13.15, +2.43,-13.78, +4.79,-13.15, +4.79,-13.15, +5.25,-12.22, +6.92,-11.98, +6.51,-11.28, +6.51,-11.28, +6.76,-11.7, +6.51,-11.28, +6.45,-11.17, +6.27,-10.86, +6.11,-10.58, +6.03,-10.44, +6.16,-10.19, +7.64,-9.11, +7.43,-8.86, +7.23,-8.61, +7.59,-9.05, +7.75,-9.23, +7.75,-9.23, +8.1,-9.11, +8.65,-8.68, +8.12,-9.09, +7.54,-8.98, +8.68,-8.3, +9.23,-7.75, +9.48,-7.96, +9.98,-8.37, +10.95,-6.98, +11.84,-6.84, +12.46,-6.26, +13.15,-4.79, +12.24,-4.45, +11.78,-4.29, +10.5,-3.82, +10.28,-4.08, +9.43,-5.44, +10.02,-5.78, +11,-6.35, +12.12,-7, +7.58,-4.37, +6.48,-3.74, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.23,7.75, +-9.28,7.79, +-9.33,7.83, +-9.28,7.79, +-9,8.01, +-7.98,9.08, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.71,9.18, +-7.16,8.54, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.01,8.36, +-6.68,7.96, +-6.98,8.32, +-7.04,8.39, +-6.86,8.17, +-6.39,7.62, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.25,7.53, +-5.01,7.66, +-4.54,7.86, +-4.57,7.91, +-4.57,7.91, +-4.57,7.91, +-4.86,8.42, +-4.96,8.59, +-4.96,8.59, +-4.73,8.19, +-4.41,7.63, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.36,6.64, +-5.45,5.78, +-6.11,5.12, +-6.11,5.12, +-6.11,5.12, +-5.78,5.45, +-4.36,6.64, +-3.98,6.9, +-4.35,6.26, +-4.5,5.36, +-3.63,4.55, +-2.38,4.42, +-1.56,4.29, +-3.66,3.46, +-2.62,3.13, +-2.61,3.11, +-2.73,3.35, +-2.98,2.92, +-3.87,4.62, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.87,4.99, +-5.36,4.5, +-6.06,3.5, +-6.52,2.52, +-6.92,0.95, +-6.96,-0.41, +-7.09,-1.72, +-7.49,-2.73, +-8.1,-2.95, +-8.4,-3.06, +-8.4,-3.06, +-8.71,-3.17, +-9.32,-3.39, +-8.81,-3.21, +-8.4,-3.06, +-8.4,-3.06, +-8.2,-2.98, +-8.1,-2.95, +-7.65,-3.64, +-6.64,-4.36, +-6,-5.23, +-6.23,-5.91, +-6.36,-6.24, +-6.37,-7.29, +-6.37,-7.59, +-6.51,-7.46, +-6.37,-7.59, +-6.22,-7.7, +-5.8,-7.11, +-5.75,-6.85, +-5.46,-7.05, +-4.47,-7.74, +-4.58,-7.93, +-4.47,-7.74, +-4.62,-7.24, +-4.71,-7.05, +-4.89,-7.32, +-4.72,-8.01, +-6.14,-9.94, +-6.03,-10.44, +-5.59,-9.69, +-5.54,-9.6, +-5.54,-9.6, +-5.54,-9.6, +-5.59,-9.69, +-5.81,-10.06, +-5.65,-9.78, +-5.78,-10.02, +-6.03,-10.44, +-4.55,-11.13, +-4.09,-11.22, +-3.75,-10.29, +-3.52,-9.68, +-3.7,-10.17, +-3.97,-10.92, +-2.4,-11.62, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.5,-10.95, +-0.43,-11.04, +-0.64,-11.02, +-0.32,-11.21, +-0,-11.89, +-0,-11.89, +-0,-12.05, +0,-12.05, +0,-12.05, +0,-12.05, +0,-12.05, +1.4,-11.93, +0.52,-12.01, +0,-12.05, +0,-12.05, +0,-11.57, +1.74,-11.9, +2.16,-12.23, +2.26,-12.83, +2.29,-12.99, +3.22,-13.57, +4.79,-13.15, +2.43,-13.78, +4.79,-13.15, +4.79,-13.15, +6.33,-11.72, +7,-12.12, +6.82,-11.81, +6.76,-11.7, +6.76,-11.7, +6.51,-11.28, +6.51,-11.28, +6.27,-10.86, +6.03,-10.44, +6.03,-10.44, +7.53,-9.38, +7.64,-9.11, +7.23,-8.61, +7.23,-8.61, +7.12,-8.49, +7.54,-8.98, +7.75,-9.23, +8.06,-9.6, +7.98,-9.51, +7.43,-8.86, +7.12,-8.49, +7.72,-7.41, +8.84,-7.41, +9.23,-7.75, +9.48,-7.96, +10.93,-6.59, +11.7,-6.75, +12.46,-6.26, +12.47,-4.54, +12.63,-2.99, +11.94,-2.46, +10.4,-3.27, +10.14,-4.37, +9.6,-5.54, +8.71,-5.03, +1.88,-1.09, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.21,7.77, +-8.69,8.34, +-8.06,8.95, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.73,9.21, +-7.35,8.76, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.54,7.79, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-5.43,8.15, +-4.72,8.17, +-4.52,7.84, +-4.47,7.74, +-4.47,7.74, +-4.71,8.16, +-4.96,8.59, +-4.96,8.59, +-4.7,8.14, +-4.47,7.74, +-4.14,7.17, +-3.98,6.9, +-4.62,6.46, +-5,6.19, +-5.12,6.11, +-5.12,6.11, +-6.11,5.12, +-6.11,5.12, +-6.11,5.12, +-5.89,5.34, +-5,6.19, +-4.49,6.55, +-4.78,5.69, +-4.33,5.34, +-2.33,4.82, +-1.65,4.55, +-1.43,3.94, +-1.87,3.62, +-1.47,3.8, +-1.4,3.84, +-1.4,3.84, +-2.96,3.62, +-3.94,4.7, +-4.43,5.28, +-4.5,5.36, +-4.5,5.36, +-4.79,5.07, +-5.36,4.5, +-6.06,3.5, +-6.37,1.91, +-6.94,0.68, +-6.94,-0.68, +-7.09,-1.72, +-7.49,-2.73, +-8.1,-2.95, +-8.4,-3.06, +-8.4,-3.06, +-8.71,-3.17, +-9.32,-3.39, +-9.21,-3.35, +-8.61,-3.13, +-8.4,-3.06, +-8.4,-3.06, +-8.29,-3.29, +-7.29,-3.91, +-6.77,-4.17, +-6.11,-5.12, +-6.6,-5.54, +-6.97,-5.85, +-7.59,-6.37, +-7.54,-6.7, +-7.81,-6.97, +-6.98,-6.98, +-6.06,-7.22, +-5.75,-6.85, +-5.75,-6.85, +-5.32,-7.15, +-4.71,-8.16, +-4.96,-8.59, +-4.88,-8.45, +-5.74,-7.64, +-6.39,-7.62, +-6.37,-7.59, +-6.87,-8.19, +-6.51,-9.36, +-6.03,-10.44, +-5.7,-9.88, +-5.54,-9.6, +-5.44,-9.43, +-5.15,-8.92, +-5.35,-9.26, +-5.7,-9.88, +-5.95,-10.3, +-6.03,-10.44, +-6.03,-10.44, +-4.12,-11.33, +-4.12,-11.33, +-3.84,-10.56, +-3.46,-9.5, +-3.46,-9.5, +-3.17,-10.58, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.28,-10.97, +-0,-11.08, +-0.43,-11.15, +-0,-11.94, +-0,-12.05, +-0,-12.05, +0,-12.05, +0,-12.05, +0,-12.05, +0.25,-12.25, +1.45,-12.25, +1.4,-11.93, +0.7,-11.91, +0,-11.4, +0,-11.08, +0,-11.3, +1.16,-11.73, +2.09,-11.87, +2.15,-12.19, +2.26,-12.83, +3.67,-13.12, +3.96,-13.26, +2.63,-13.73, +4.79,-13.15, +5.22,-12.83, +6.89,-11.93, +6.78,-11.75, +6.8,-11.77, +6.94,-12.03, +6.51,-11.28, +6.51,-11.28, +6.47,-11.21, +6.16,-10.68, +6.31,-10.24, +7.46,-9.43, +7.43,-8.86, +7.54,-8.98, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.23,-8.61, +7.23,-8.61, +7.75,-9.23, +7.82,-9.33, +7.54,-8.98, +7.1,-8.46, +6.82,-7.76, +8.74,-7.33, +9.23,-7.75, +8.74,-7.33, +10.57,-6.68, +11.28,-6.51, +12.06,-5.22, +12.24,-4.45, +12.19,-2.15, +11.23,-1.98, +10.4,-3.27, +9.59,-4.17, +9.26,-5.35, +4.8,-2.77, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.22,7.76, +-8.56,8.46, +-7.8,9.19, +-7.72,9.2, +-7.66,9.13, +-7.72,9.2, +-7.75,9.23, +-7.43,8.85, +-7.66,9.13, +-7.66,9.13, +-7.29,8.69, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.87,8.19, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.07,7.83, +-5.04,8.54, +-4.85,8.4, +-4.51,7.82, +-4.47,7.74, +-4.48,7.76, +-4.57,7.91, +-4.57,7.91, +-4.51,7.82, +-4.45,7.71, +-4.05,7.02, +-3.98,6.9, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.78,5.45, +-6.11,5.12, +-6,5.23, +-4.92,5.36, +-4.51,6.01, +-4.58,6.09, +-3.93,5.87, +-2.7,5.37, +-2,4.98, +-1.81,4.53, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-2.61,3.93, +-3.87,4.62, +-3.95,4.71, +-4.5,5.36, +-4.5,5.36, +-4.79,5.07, +-5.24,4.39, +-5.43,2.94, +-5.75,1.72, +-6.16,0.35, +-6.93,-0.81, +-6.99,-2.11, +-7.49,-2.73, +-8.25,-3, +-8.4,-3.06, +-8.4,-3.06, +-9.01,-3.28, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-9.01,-3.28, +-8.4,-3.06, +-8.04,-3.84, +-7.18,-4.15, +-6.9,-3.99, +-6.62,-3.82, +-6.06,-3.5, +-6.32,-4.25, +-7.59,-6.37, +-7.69,-6.46, +-7.79,-6.54, +-7.59,-6.37, +-6.65,-6.25, +-5.7,-6.79, +-5.47,-6.52, +-5.61,-6.68, +-4.8,-8.31, +-5.35,-9.26, +-5.35,-9.26, +-5.35,-7.26, +-6.09,-7.26, +-6.3,-7.51, +-6.54,-7.79, +-6.58,-9.16, +-5.86,-10.16, +-5.54,-9.6, +-5.54,-9.6, +-5.22,-9.03, +-4.9,-8.49, +-5.23,-9.05, +-5.58,-9.67, +-5.97,-10.34, +-6.03,-10.44, +-4.97,-10.93, +-4.12,-11.33, +-4.12,-11.33, +-4.07,-11.17, +-3.72,-10.23, +-3.72,-10.23, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.28,-10.97, +-0,-11.24, +-0,-11.89, +-0,-12.05, +-0,-12.05, +-0,-12.05, +0,-12.05, +0,-12.05, +0,-12.21, +1.74,-11.9, +2.09,-11.87, +1.57,-11.91, +0.7,-11.83, +0,-11.57, +-0,-11.08, +0,-11.08, +0.96,-11, +2.07,-11.75, +2.09,-11.87, +2.21,-12.51, +2.66,-12.89, +2.43,-13.78, +2.72,-13.7, +4.79,-13.15, +4.79,-13.15, +6.04,-12.03, +6.84,-11.84, +6.57,-11.38, +6.59,-11.42, +6.51,-11.28, +6.51,-11.28, +6.98,-10.51, +7.75,-9.23, +7.75,-9.23, +7.68,-9.15, +7.49,-8.92, +7.12,-8.49, +7.19,-8.57, +7.12,-8.49, +7.24,-8.37, +7.12,-8.49, +7.33,-8.74, +7.26,-8.65, +7.54,-8.98, +7.75,-9.23, +7.18,-8.55, +7.28,-7.92, +9.15,-7.68, +9.48,-7.96, +8.19,-6.87, +9.32,-6.84, +9.75,-6.33, +10.76,-5.34, +10.86,-4.65, +10.29,-2.63, +9.66,-2.49, +9.33,-3.14, +8.58,-4.29, +8.58,-4.66, +4.67,-2.7, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.09,7.9, +-8.08,8.93, +-7.52,8.97, +-7.16,8.53, +-7.5,8.93, +-7.75,9.23, +-7.34,8.75, +-7.14,8.5, +-7.48,8.92, +-7.69,9.17, +-7.39,8.8, +-7.18,8.55, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.11,8.47, +-6.9,8.22, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.25,7.69, +-5.36,8.33, +-5.14,8.47, +-4.73,8.19, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.45,7.7, +-4.02,6.96, +-3.98,6.9, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.02,5.98, +-3.83,5.83, +-3.5,6.06, +-2.6,5.81, +-2.48,5.47, +-2,4.86, +-1.73,4.75, +-1.47,4.04, +-1.47,3.8, +-1.4,3.84, +-1.4,3.84, +-1.35,3.71, +-2.1,4.1, +-3.46,4.12, +-3.87,4.62, +-3.94,4.7, +-4.08,4.86, +-4.18,4.77, +-4.62,3.87, +-5.22,3.01, +-5.75,1.72, +-6,0.35, +-6.79,-1.2, +-6.99,-2.11, +-7.49,-2.73, +-8.4,-3.06, +-8.4,-3.06, +-8.5,-3.1, +-9.21,-3.35, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-8.91,-3.24, +-8.18,-3.53, +-7.18,-4.15, +-6.9,-3.99, +-6.62,-3.82, +-6.06,-3.5, +-6.06,-3.5, +-7.13,-4.78, +-7.76,-6.14, +-7.59,-6.37, +-7.59,-6.37, +-6.81,-5.96, +-5.23,-6.23, +-5.12,-6.11, +-5.23,-6.23, +-4.85,-7.88, +-5.35,-9.26, +-5.54,-9.6, +-5.07,-6.93, +-5.64,-6.73, +-6.27,-7.47, +-6.37,-7.59, +-6.75,-8.04, +-5.54,-9.6, +-5.54,-9.6, +-5.54,-9.6, +-5.25,-9.09, +-5.25,-9.09, +-5.62,-9.74, +-5.54,-9.6, +-5.86,-10.16, +-5.31,-10.77, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-3.79,-10.41, +-2.34,-10.8, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.32,-11.4, +-0,-11.73, +-0,-12.05, +-0,-12.05, +-0,-12.05, +0,-12.05, +0,-12.05, +0.99,-12.29, +0.7,-12.1, +2.21,-12.51, +2.11,-11.98, +2.11,-11.99, +1.05,-11.96, +0,-11.73, +-0,-11.08, +0.29,-10.86, +1.92,-10.91, +2.01,-11.39, +2.09,-11.87, +2.18,-12.35, +2.99,-12.63, +2.77,-13.53, +2.75,-13.82, +4.79,-13.15, +4.79,-13.15, +4.51,-12.39, +4.45,-12.24, +4.51,-12.39, +6.51,-11.28, +6.51,-11.28, +6.19,-10.72, +7.75,-9.23, +7.75,-9.23, +7.43,-8.86, +7.12,-8.49, +7.67,-9.14, +7.43,-8.86, +7.12,-8.49, +7.12,-8.49, +7.29,-8.32, +7.23,-8.61, +7.96,-9.48, +7.54,-8.98, +7.59,-9.05, +7.12,-8.49, +7.12,-8.49, +7.86,-7.54, +8.61,-7.23, +9.11,-7.64, +8.49,-7.12, +9.03,-7.58, +8.23,-5.91, +8.7,-5.02, +8.77,-5.06, +8.02,-3.63, +7.63,-3.45, +7.96,-4, +7.82,-4.31, +8.12,-4.69, +2.86,-1.65, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.43,7.46, +-8.9,7.47, +-8.74,7.33, +-9.18,7.71, +-8.9,8.11, +-7.75,9.23, +-7.52,8.97, +-7.13,8.5, +-7.27,8.67, +-7.72,9.2, +-7.64,9.1, +-7.16,8.54, +-7.15,8.52, +-7.47,8.9, +-7.66,9.13, +-7.59,9.05, +-7.16,8.54, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.08,8.44, +-6.52,7.77, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.33,7.59, +-5.36,7.46, +-4.76,8.15, +-4.86,8.42, +-4.51,7.82, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.32,7.48, +-4,6.92, +-3.98,6.9, +-4.87,6.28, +-5.12,6.11, +-5.12,6.11, +-4.98,5.94, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-4.91,5.86, +-4.23,5.68, +-3.5,6.06, +-3,5.94, +-2.7,5.37, +-2.31,4.59, +-2.28,4.26, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.67,3.83, +-2.87,4, +-3.46,4.12, +-3.87,4.62, +-3.87,4.62, +-3.87,4.62, +-4.49,4, +-5.02,3.3, +-5.75,1.72, +-6,0.35, +-6.57,-1.16, +-6.88,-2.5, +-7.64,-2.78, +-8.4,-3.06, +-8.4,-3.06, +-8.86,-3.22, +-9.32,-3.39, +-9.32,-3.39, +-9.32,-3.39, +-9.59,-3.49, +-9.32,-3.39, +-9.16,-3.34, +-8.4,-3.06, +-7.4,-3.68, +-6.9,-3.99, +-6.62,-3.82, +-6.06,-3.5, +-6.4,-2.76, +-5.76,-2.1, +-6.85,-3.51, +-8.04,-5.74, +-7.97,-5.46, +-6.73,-5.64, +-5.78,-5.45, +-5.12,-6.11, +-5.12,-6.11, +-5.24,-7.08, +-5.02,-8.7, +-5.15,-8.92, +-5.14,-7.01, +-5.33,-6.35, +-6.23,-7.43, +-6.62,-7.89, +-6.44,-7.9, +-5.28,-9.15, +-5.22,-9.03, +-5.09,-8.81, +-5.48,-9.48, +-5.41,-9.37, +-5.48,-9.48, +-5.44,-9.43, +-5.7,-9.88, +-4.84,-10.99, +-4.12,-11.33, +-4.12,-11.33, +-4.12,-11.33, +-3.96,-10.87, +-3.48,-10.5, +-2.23,-10.83, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.28,-11.29, +0,-12.05, +0,-12.05, +0,-12.05, +0,-12.05, +0,-12.05, +0.3,-12.03, +2.09,-11.87, +2.09,-11.87, +2.15,-12.19, +2.26,-12.83, +2.19,-12.43, +1.86,-11.89, +0.93,-11.65, +0.43,-11.04, +0.86,-11.01, +1.96,-11.12, +2.08,-11.79, +2.11,-11.98, +2.94,-12.31, +3.97,-12.37, +4.53,-12.44, +2.43,-13.78, +4.79,-13.15, +4.71,-12.95, +4.45,-12.24, +4.42,-12.16, +4.45,-12.24, +6.09,-11.3, +6.35,-11, +6.89,-9.83, +7.75,-9.23, +7.64,-9.11, +7.23,-8.61, +7.12,-8.49, +7.28,-8.67, +7.33,-8.74, +7.23,-8.61, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +8.06,-9.6, +7.85,-9.36, +7.43,-8.86, +7.12,-8.49, +6.87,-8.19, +8.32,-7.29, +8.61,-7.23, +8.61,-7.23, +8.49,-7.12, +8.66,-7.26, +7.92,-5.9, +8.59,-4.96, +7.85,-4.53, +7.18,-4.15, +7.6,-4.39, +7.74,-4.47, +7.74,-4.47, +8.31,-4.8, +2.27,-1.31, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.5,6.65, +-8.52,7.12, +-8.74,7.33, +-9.02,7.57, +-8.9,8.11, +-7.75,9.23, +-7.69,9.16, +-7.2,8.58, +-7.27,8.66, +-7.73,9.21, +-7.75,9.23, +-7.37,8.79, +-7.12,8.49, +-7.12,8.49, +-7.27,8.66, +-7.68,9.16, +-7.54,8.98, +-7.23,8.61, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.11,8.47, +-6.84,8.16, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.33,7.54, +-5.46,7.15, +-4.96,8.33, +-4.96,8.59, +-4.54,7.86, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.41,7.63, +-4,6.92, +-3.98,6.9, +-3.98,6.9, +-4.11,6.81, +-5.05,6.02, +-4.98,5.94, +-4.64,5.53, +-4.98,5.94, +-5.05,6.02, +-4.89,5.83, +-4.5,5.36, +-4.33,5.48, +-3.42,5.92, +-3.17,5.5, +-3.01,5.22, +-2.47,4.58, +-2.39,4.44, +-1.45,3.99, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.33,3.65, +-2.64,3.7, +-3.25,3.87, +-3.35,4, +-3.67,4.37, +-3.67,4.37, +-3.83,3.9, +-4.38,2.89, +-5.14,1.52, +-6.01,-0.23, +-6.57,-1.16, +-6.88,-2.5, +-7.79,-2.84, +-8.4,-3.06, +-8.71,-3.17, +-9.32,-3.39, +-9.32,-3.39, +-10.17,-3.7, +-10.41,-3.79, +-10.41,-3.79, +-9.32,-3.39, +-9.11,-3.32, +-8.11,-3.69, +-7.37,-4.26, +-6.9,-3.99, +-6.62,-3.82, +-6.23,-3.13, +-6.47,-2.36, +-5.66,-2.06, +-5.66,-2.06, +-7.61,-3.99, +-8.16,-4.71, +-6.57,-5.51, +-6.11,-5.12, +-5.12,-6.11, +-5.12,-6.11, +-5.64,-6.73, +-4.71,-8.16, +-4.96,-8.59, +-5.28,-7.18, +-6.06,-7.22, +-6.37,-7.59, +-7,-8.34, +-6.21,-8.07, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-5.15,-8.92, +-5.25,-9.09, +-5.28,-9.15, +-5.21,-9.87, +-3.79,-10.41, +-3.86,-10.61, +-4.05,-11.12, +-3.94,-10.82, +-3.79,-10.41, +-2.13,-10.86, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.76,-10.93, +-0.32,-11.54, +0,-12.05, +0,-12.05, +0,-12.05, +0,-12.05, +0.26,-12.03, +1.74,-11.9, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.23,-12.67, +2.24,-12.71, +2.09,-11.87, +2.09,-11.87, +2.04,-11.55, +2.01,-11.39, +2.37,-11.46, +3.7,-11.69, +4.4,-12.09, +4.45,-12.24, +4.45,-12.24, +4.57,-12.54, +2.43,-13.78, +4,-13.36, +4.79,-13.15, +4.62,-12.7, +4.49,-12.34, +5.09,-11.83, +6.35,-11, +6.03,-10.44, +7.56,-9.37, +7.54,-8.98, +7.19,-8.57, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.19,-8.57, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.85,-9.36, +7.75,-9.23, +7.12,-8.49, +6.62,-7.89, +7.28,-7.23, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.61,-7.23, +7.93,-6.65, +7.59,-6.37, +7.74,-4.76, +7.53,-4.35, +6.9,-3.98, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +8.59,-4.96, +1.2,-0.69, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.62,5.63, +-9.1,6.79, +-9.18,7.71, +-9.23,7.75, +-9.14,7.85, +-8.18,8.83, +-7.68,9.16, +-7.16,8.54, +-7.54,8.98, +-7.75,9.23, +-7.75,9.23, +-7.4,8.82, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.33,8.74, +-7.72,9.2, +-7.69,9.17, +-7.26,8.65, +-7.12,8.49, +-7.12,8.49, +-7.09,8.45, +-6.75,8.04, +-6.66,7.94, +-6.59,7.86, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.34,7.56, +-5.87,7, +-5.29,8.14, +-4.81,8.32, +-4.5,7.8, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.22,7.31, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.02,6.88, +-4.94,6.25, +-4.5,5.36, +-4.6,5.48, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.39,5.44, +-3.44,5.97, +-3.01,5.22, +-2.3,5.31, +-1.82,4.71, +-2.08,4.58, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.31,3.59, +-1.4,3.84, +-1.31,3.45, +-2.49,3.52, +-3.25,3.87, +-2.77,4.21, +-2.53,4.38, +-3.01,4.04, +-3.46,3.66, +-4.21,2.77, +-4.88,1.26, +-5.89,-0.35, +-6.25,-1.51, +-6.84,-2.49, +-7.79,-2.84, +-8.4,-3.06, +-9.01,-3.28, +-9.32,-3.39, +-9.68,-3.52, +-10.41,-3.79, +-10.41,-3.79, +-10.26,-3.73, +-9.32,-3.39, +-8.01,-4.29, +-7.74,-4.47, +-7.46,-4.31, +-6.9,-3.99, +-6.62,-3.82, +-6.23,-3.13, +-6.27,-2.28, +-5.66,-2.06, +-5.66,-2.06, +-4.85,-1.77, +-6.74,-4.75, +-6.17,-5.18, +-5.45,-5.78, +-5.12,-6.11, +-5.26,-6.27, +-5.75,-6.85, +-4.67,-7.74, +-4.74,-8.21, +-4.79,-6.6, +-5.47,-6.52, +-6.09,-7.26, +-6.38,-7.9, +-5.05,-8.75, +-4.96,-8.59, +-4.96,-8.59, +-4.9,-8.48, +-4.96,-8.59, +-4.88,-8.45, +-4.8,-8.31, +-4.17,-8.95, +-4.37,-10.14, +-3.79,-10.41, +-3.79,-10.41, +-3.79,-10.41, +-3.79,-10.41, +-2.86,-10.66, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.6,-10.94, +-0.24,-11.06, +-0,-11.62, +0,-12.05, +0,-12.05, +0,-12.05, +0.23,-12.03, +1.76,-11.98, +2.21,-12.51, +2.24,-12.72, +2.09,-11.87, +2.13,-12.08, +2.26,-12.83, +2.26,-12.83, +2.17,-12.29, +2.2,-12.47, +2.09,-11.87, +2.09,-11.87, +3.89,-11.72, +4.45,-12.24, +4.62,-12.7, +4.68,-12.85, +4.79,-13.15, +4.51,-12.39, +2.72,-13.7, +4.68,-12.85, +4.79,-13.15, +4.79,-13.15, +5.05,-12.23, +6.27,-10.86, +6.03,-10.44, +6.6,-10.04, +7.75,-9.23, +7.43,-8.86, +7.12,-8.49, +7.23,-8.61, +7.12,-8.49, +7.46,-8.15, +7.88,-7.45, +7.35,-8.26, +7.12,-8.49, +7.12,-8.49, +6.87,-8.19, +7.47,-8.9, +7.47,-8.9, +7.06,-8.41, +6.94,-7.94, +7.76,-6.76, +8.26,-6.93, +8.58,-7.2, +8.61,-7.23, +8.61,-7.23, +8.15,-6.84, +7.74,-6.5, +7,-5.54, +7.22,-4.17, +6.9,-3.98, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +8.92,-5.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.06,5.81, +-9.81,6.73, +-9.23,7.75, +-9.23,7.75, +-9.18,7.71, +-8.66,8.08, +-7.32,8.72, +-7.12,8.49, +-7.54,8.98, +-7.75,9.23, +-7.75,9.23, +-7.48,8.92, +-7.14,8.5, +-7.12,8.49, +-7.12,8.49, +-7.16,8.54, +-7.71,9.18, +-7.75,9.23, +-7.48,8.92, +-7.14,8.5, +-7.09,8.45, +-6.72,8.01, +-6.4,7.63, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.16,7.35, +-5.92,7.5, +-4.68,8.11, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.29,7.44, +-4,6.92, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.01,6.89, +-4.51,6.29, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-3.56,5.62, +-2.27,5.56, +-1.91,5.26, +-1.73,4.75, +-1.58,4.34, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.33,3.65, +-1.33,3.65, +-1.37,3.12, +-2.21,3.42, +-2.9,3.92, +-2.53,4.38, +-2.53,4.38, +-2.77,4.21, +-3.46,3.66, +-4.21,2.77, +-4.9,1.16, +-5.64,-0.7, +-6.06,-1.83, +-6.92,-2.52, +-7.79,-2.84, +-8.4,-3.06, +-9.01,-3.28, +-9.32,-3.39, +-9.68,-3.52, +-10.41,-3.79, +-10.41,-3.79, +-9.68,-3.52, +-8.64,-4.33, +-7.74,-4.47, +-7.74,-4.47, +-7.46,-4.31, +-6.9,-3.99, +-6.62,-3.82, +-6.23,-3.13, +-6.37,-2.32, +-5.66,-2.06, +-5.66,-2.06, +-4.9,-1.78, +-5.14,-2.7, +-5.42,-3.9, +-5.44,-4.52, +-5.54,-6.6, +-5.64,-6.73, +-5.75,-6.85, +-4.68,-7.59, +-4.47,-7.74, +-4.7,-6.4, +-5.23,-6.23, +-5.54,-6.6, +-5.66,-8.09, +-5.35,-9.26, +-5.25,-9.09, +-5.25,-9.09, +-4.96,-8.59, +-4.9,-8.49, +-4.74,-8.21, +-4.46,-8.11, +-3.36,-8.74, +-3.41,-9.38, +-3.69,-10.14, +-3.79,-10.41, +-3.79,-10.41, +-3.38,-10.52, +-2.34,-10.8, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-0.64,-11.02, +-0,-11.08, +-0,-11.08, +-0,-11.57, +0.7,-11.99, +0.35,-12.02, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.21,-12.51, +2.26,-12.83, +2.09,-11.87, +2.15,-12.19, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +3.53,-12.6, +4.62,-12.7, +4.75,-13.05, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.31,-12.44, +4.71,-12.95, +4.68,-12.85, +4.58,-12.58, +4.51,-12.39, +6.19,-11.29, +6.11,-10.58, +6.31,-10.24, +7.36,-9.31, +7.23,-8.61, +7.28,-8.67, +7.33,-8.74, +7.54,-8.98, +7.35,-8.26, +8.49,-7.12, +8.49,-7.12, +8.26,-7.35, +6.87,-8.19, +7.12,-8.49, +7,-8.34, +7.96,-9.48, +7.59,-9.05, +7.23,-8.61, +7.96,-7.1, +8.04,-6.75, +8.58,-7.2, +8.91,-7.48, +8.98,-7.54, +9.01,-7.56, +8,-6.71, +7.43,-6.23, +6.85,-5.75, +7.09,-4.98, +7.31,-4.67, +7.6,-4.39, +7.85,-4.53, +8.31,-4.79, +3.2,-1.85, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.44,6.03, +-9.98,6.29, +-8.95,7.26, +-8.84,7.41, +-8.67,7.28, +-8.33,7.3, +-7.42,8.21, +-7.12,8.49, +-7.54,8.98, +-7.75,9.23, +-7.75,9.23, +-7.73,9.22, +-7.41,8.84, +-7.16,8.54, +-7.16,8.54, +-7.19,8.6, +-7.63,9.24, +-7.75,9.23, +-7.71,9.18, +-7.21,8.59, +-7.08,8.44, +-6.83,8.14, +-6.75,8.04, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.16,7.35, +-5.58,6.98, +-4.5,7.73, +-4.47,7.74, +-4.47,7.74, +-4.42,7.66, +-4,6.92, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.06,6.85, +-4.88,6.3, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-3.5,5.66, +-2.54,5.44, +-1.84,5.05, +-1.73,4.75, +-1.51,4.14, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.35,3.71, +-1.35,3.71, +-1.28,3.14, +-1.85,3.2, +-2.16,3.58, +-2.4,3.68, +-2.4,3.68, +-3.02,3.9, +-3.46,3.66, +-4.21,2.77, +-4.93,0.87, +-5.5,-0.79, +-5.97,-2.17, +-6.78,-2.47, +-7.49,-2.73, +-8.26,-3.37, +-8.16,-4.09, +-8.86,-4.37, +-8.83,-4.43, +-9.09,-5.25, +-8.84,-5.1, +-8.71,-4.7, +-7.73,-5.06, +-6.85,-5.75, +-6.85,-5.75, +-7.17,-4.73, +-6.9,-3.99, +-6.62,-3.82, +-6.15,-3.31, +-6.58,-2.39, +-5.66,-2.06, +-5.66,-2.06, +-5.56,-2.02, +-5.36,-1.95, +-5.03,-2.91, +-5.22,-3.01, +-5.63,-6, +-5.75,-6.85, +-5.18,-7.25, +-4.47,-7.74, +-4.47,-7.74, +-4.56,-6.24, +-5.05,-6.02, +-5.27,-6.79, +-5.64,-8.34, +-5.48,-9.48, +-5.54,-9.6, +-5.54,-9.6, +-5.25,-9.09, +-4.96,-8.59, +-4.96,-8.59, +-4.48,-8.27, +-3,-8.25, +-3.02,-8.29, +-3.52,-9.68, +-3.79,-10.41, +-3.79,-10.41, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.5,-10.95, +-0.43,-11.04, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.73, +0.47,-12.01, +1.4,-11.93, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.15,-12.19, +2.18,-12.35, +2.09,-11.87, +2.15,-12.19, +2.26,-12.83, +2.39,-13.54, +2.29,-12.99, +2.35,-13.3, +2.37,-13.46, +4.39,-13.26, +4.91,-13.49, +5.06,-13.91, +5.01,-13.76, +4.95,-13.61, +4.79,-13.15, +4.75,-12.23, +4.45,-12.24, +4.45,-12.24, +4.75,-12.1, +6.12,-11.11, +6.23,-10.79, +6.98,-9.77, +7.75,-9.23, +7.75,-9.23, +7.29,-8.69, +7.17,-8.55, +6.96,-8.29, +7.12,-8.49, +7.8,-7.8, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +7.33,-7.73, +6.56,-7.82, +7.12,-8.49, +7.64,-9.11, +7.67,-9.14, +7.7,-8.14, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.61,-7.23, +9.48,-7.96, +8.67,-7.28, +7.89,-6.62, +7.35,-6.16, +6.85,-5.75, +7.04,-5.9, +7.62,-4.65, +7.74,-4.47, +8.24,-4.75, +8.59,-4.96, +2.86,-1.65, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.71,5.6, +-9.65,5.57, +-8.88,6.61, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-7.85,7.79, +-7.15,8.46, +-7.41,8.84, +-7.73,9.22, +-7.75,9.23, +-7.75,9.23, +-7.73,9.21, +-7.66,9.13, +-7.68,9.16, +-7.55,9.27, +-6.97,9.83, +-7.7,9.27, +-7.48,8.92, +-7.14,8.5, +-7.12,8.49, +-7.12,8.49, +-7,8.34, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.16,7.35, +-5.64,6.94, +-4.69,7.6, +-4.47,7.74, +-4.44,7.69, +-4.11,7.13, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.99,6.86, +-4.47,6.51, +-5.09,6.13, +-4.6,5.48, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-3.94,5.75, +-3.44,5.97, +-2.17,5.61, +-2.02,5.56, +-1.73,4.75, +-1.58,4.34, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.63,3.21, +-1.3,2.24, +-2.24,3.08, +-2.62,3.13, +-2.62,3.13, +-3.04,3.62, +-3.46,3.66, +-4.21,2.77, +-4.93,0.87, +-5.21,-1.27, +-5.97,-2.17, +-6.58,-2.39, +-7.39,-2.94, +-7.74,-4.47, +-7.74,-4.47, +-8.1,-4.68, +-8.59,-4.96, +-8.59,-4.96, +-8.59,-4.96, +-8.21,-4.74, +-7.25,-5.18, +-6.85,-5.75, +-6.85,-5.75, +-6.6,-5.54, +-6.64,-4.36, +-6.81,-3.93, +-6.06,-3.5, +-6.52,-2.52, +-6.27,-2.28, +-5.66,-2.06, +-5.66,-2.06, +-5.61,-2.18, +-5.22,-3.01, +-5.22,-3.01, +-5.43,-3.84, +-5.43,-4.89, +-5.13,-5.97, +-4.47,-7.74, +-4.47,-7.74, +-4.7,-6.4, +-5.12,-6.11, +-4.47,-7.74, +-5.03,-8.71, +-5.54,-9.6, +-5.61,-9.72, +-5.54,-9.6, +-5.54,-9.6, +-4.96,-8.59, +-4.74,-8.21, +-4.16,-7.89, +-2.87,-7.9, +-2.84,-7.79, +-3.21,-8.81, +-3.7,-10.17, +-3.38,-10.52, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.6,-10.94, +-0.32,-11.05, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.73, +1.05,-11.8, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.11,-11.98, +2.09,-11.87, +2.19,-12.4, +2.32,-13.15, +2.43,-13.78, +2.43,-13.78, +2.54,-14.42, +3.44,-14.52, +4.98,-13.67, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.01,-13.76, +6.01,-12.18, +4.45,-12.24, +4.45,-12.24, +6.09,-11.3, +6.35,-11, +6.96,-9.93, +7.75,-9.23, +7.43,-8.86, +7.54,-8.98, +7.18,-8.56, +6.62,-7.89, +6.37,-7.59, +7.41,-7.79, +8.49,-7.12, +8.49,-7.12, +7.89,-6.62, +7.46,-6.51, +6.82,-8.13, +6.85,-8.16, +7.12,-8.49, +7.54,-8.98, +7.01,-8.36, +7.78,-7.28, +8.61,-7.23, +7.93,-7.91, +8.49,-7.12, +8.86,-7.43, +9.36,-7.85, +8.86,-7.43, +8.19,-6.87, +7.35,-6.16, +6.85,-5.75, +7.04,-5.9, +7.59,-4.68, +7.74,-4.47, +8.27,-4.77, +8.45,-4.88, +9.43,-5.44, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.6,5.54, +-9.6,5.54, +-9.19,6.14, +-8.5,7.11, +-8.65,7.26, +-8.51,7.14, +-8.4,7.22, +-7.27,8.36, +-7.16,8.54, +-7.71,9.18, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.36,9.53, +-7.6,9.35, +-7.41,8.84, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.04,8.39, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.16,7.35, +-5.75,6.85, +-5.14,7.29, +-4.39,7.6, +-4.15,7.18, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.01,6.74, +-4.2,5.99, +-5.02,6.1, +-4.64,5.53, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-3.83,5.83, +-3.39,5.87, +-2.06,5.66, +-1.99,5.46, +-1.73,4.75, +-1.62,4.44, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.56,3.76, +-1.64,3.08, +-1.38,2.39, +-2.24,2.93, +-2.62,3.13, +-2.83,3.38, +-3.25,3.87, +-3.59,3.53, +-4.33,2.5, +-4.95,0.77, +-5.18,-1.37, +-5.91,-2.29, +-6.23,-3.13, +-6.9,-3.99, +-7.37,-4.26, +-7.74,-4.47, +-7.74,-4.47, +-7.55,-4.75, +-7.73,-5.06, +-7.52,-4.79, +-7.3,-5.11, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.24,-4.93, +-6.9,-3.99, +-6.34,-3.66, +-6.32,-2.95, +-6.58,-2.39, +-6.42,-2.34, +-6.01,-2.19, +-5.42,-2.58, +-5.22,-3.01, +-5.52,-3.34, +-5.36,-4.5, +-5.36,-4.5, +-5.26,-4.86, +-5.15,-5.65, +-5.26,-6.15, +-4.91,-6.12, +-4.91,-6.39, +-4.28,-7.41, +-4.78,-8.28, +-5.46,-9.45, +-5.54,-9.6, +-5.54,-9.6, +-5.44,-9.43, +-4.96,-8.59, +-4.71,-8.16, +-4.07,-7.04, +-3.01,-7, +-2.73,-7.49, +-2.95,-8.1, +-3.47,-9.53, +-2.55,-10.75, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.5,-10.95, +-0.21,-11.06, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.36, +-0,-11.73, +1.05,-11.8, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.03,-11.51, +2.09,-11.87, +2.09,-11.87, +2.18,-12.35, +2.29,-12.99, +2.43,-13.78, +2.54,-14.38, +3.86,-14.4, +3.86,-14.4, +4.7,-14.18, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.71,-13.32, +6.24,-11.94, +4.68,-12.13, +6.01,-11.06, +6.43,-11.14, +6.5,-10.5, +7.98,-9.51, +7.75,-9.23, +7.64,-9.11, +7.43,-8.86, +6.92,-8.25, +6.37,-7.59, +7.08,-6.66, +8.04,-6.75, +8.49,-7.12, +8.49,-7.12, +7.89,-6.62, +6.78,-7.19, +7.03,-8.38, +7.12,-8.49, +6.8,-8.1, +7.19,-8.57, +6.9,-7.75, +8.29,-6.96, +8.5,-7.41, +8.49,-7.12, +8.49,-7.12, +8.61,-7.23, +9.23,-7.75, +9.14,-7.67, +8.34,-7, +7.47,-6.27, +6.85,-5.75, +7.04,-5.9, +8.16,-5.01, +8.59,-4.96, +7.95,-4.59, +8.45,-4.88, +9.57,-5.52, +1.41,-0.81, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.6,5.54, +-9.6,5.54, +-9.53,5.65, +-8.67,6.89, +-8.51,7.14, +-8.5,7.14, +-8.42,7.2, +-7.35,8.29, +-7.16,8.54, +-7.71,9.18, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.41,8.84, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.04,8.39, +-6.43,7.66, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.35,7.57, +-6.31,7.52, +-6.32,7.53, +-5.96,7.1, +-5.75,6.85, +-4.89,7.07, +-4.06,7.03, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.17,6.63, +-4.42,5.42, +-4.71,5.61, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-4.5,5.36, +-3.42,5.92, +-3.13,6.23, +-2.22,5.59, +-1.95,5.36, +-1.73,4.75, +-1.73,4.75, +-1.43,3.94, +-1.4,3.84, +-1.4,3.84, +-1.61,3.74, +-1.97,3.57, +-1.98,3.42, +-1.72,2.97, +-2.24,2.93, +-3.24,2.82, +-3.8,3.32, +-3.46,3.66, +-3.8,3.32, +-4.33,2.5, +-5.01,0.5, +-5.13,-1.56, +-5.44,-2.54, +-6.06,-3.5, +-6.62,-3.82, +-6.9,-3.99, +-6.92,-4.8, +-6.75,-5.33, +-6.45,-5.64, +-6.48,-5.44, +-6.53,-5.48, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.44,-5.4, +-6.55,-4.49, +-6.81,-3.93, +-6.3,-3.48, +-6.52,-2.52, +-6.58,-2.39, +-6.32,-2.95, +-6.06,-3.5, +-6.06,-3.5, +-5.71,-4, +-5.36,-4.5, +-5.36,-4.5, +-5.36,-4.5, +-5.36,-4.5, +-4.89,-5.2, +-4.52,-5.74, +-4.26,-6.51, +-3.86,-6.69, +-4.8,-8.31, +-5.39,-9.34, +-5.54,-9.6, +-5.48,-9.48, +-5.28,-9.15, +-4.96,-8.59, +-4.47,-7.74, +-3.93,-6.81, +-3.19,-6.53, +-2.61,-7.16, +-2.69,-7.39, +-2.87,-8.9, +-2.17,-10.45, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.92,-10.91, +-1.68,-10.93, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-12.05, +0.23,-11.82, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.08,-11.78, +1.96,-11.1, +2.54,-11.75, +2.17,-12.29, +2.26,-12.83, +2.4,-13.62, +2.85,-14.3, +3.94,-14.71, +5.17,-14.22, +5.17,-14.22, +5.23,-14.37, +5.12,-14.07, +5.12,-14.07, +5.23,-14.37, +5.29,-14.52, +5.12,-14.07, +6.48,-12.9, +6.3,-12.05, +6.17,-11.44, +6.51,-11.28, +6.51,-11.28, +7.13,-10.85, +8.22,-9.79, +7.75,-9.23, +7.54,-8.98, +7.12,-8.49, +7.05,-8.4, +6.83,-7.69, +7.59,-6.37, +8.04,-6.75, +8.67,-7.28, +8.49,-7.12, +7.46,-7.05, +6.62,-7.89, +7.67,-9.14, +7.54,-8.98, +6.75,-8.04, +6.87,-8.19, +7.46,-8.15, +8.49,-7.12, +8.74,-7.33, +8.49,-7.12, +8.49,-7.12, +8.65,-7.26, +8.98,-7.54, +9.17,-7.69, +8.57,-7.19, +7.59,-6.37, +7.17,-6.02, +7.67,-5.79, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +10.3,-5.94, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.6,5.54, +-9.66,5.58, +-9.63,5.56, +-9.6,5.54, +-8.71,6.83, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-8.21,7.42, +-7.34,8.32, +-7.52,8.97, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.41,8.84, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.06,8.41, +-6.54,7.8, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.15,7.33, +-5.79,6.9, +-5.79,6.9, +-5.76,6.86, +-5.75,6.85, +-5.45,6.91, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.74,6.23, +-4.5,5.36, +-4.69,5.58, +-4.5,5.36, +-4.29,5.11, +-4.5,5.36, +-4.36,5.2, +-4.43,5.28, +-4.5,5.36, +-3.5,6.06, +-2.86,5.88, +-2.06,5.66, +-2.02,5.56, +-1.73,4.75, +-1.73,4.75, +-1.65,4.55, +-1.4,3.84, +-1.61,3.74, +-2.04,3.54, +-1.86,3.62, +-1.72,3.69, +-1.94,3.37, +-2.53,3.2, +-3.62,3.04, +-3.87,3.25, +-3.66,3.46, +-3.87,3.25, +-4.33,2.5, +-5.03,0.33, +-5.13,-1.56, +-5.37,-2.7, +-5.87,-3.39, +-6.35,-3.84, +-6.19,-5, +-6.11,-5.12, +-6.11,-5.12, +-5.48,-5.75, +-5.89,-5.34, +-6.35,-5.33, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.49,-5.14, +-6.9,-3.99, +-6.9,-3.99, +-6.62,-3.82, +-6.2,-3.58, +-6.06,-3.5, +-6.06,-3.5, +-6.06,-3.5, +-5.44,-4.39, +-5.36,-4.5, +-5.36,-4.5, +-5.36,-4.5, +-5.26,-4.59, +-4.5,-5.36, +-4.5,-5.36, +-4.27,-5.78, +-4.08,-6.25, +-4.31,-7.46, +-5.05,-8.75, +-4.96,-8.59, +-5.25,-9.09, +-5.05,-8.75, +-4.47,-7.74, +-4.15,-7.18, +-3.82,-6.62, +-2.81,-6.38, +-2.5,-6.88, +-2.62,-7.18, +-1.55,-8.81, +-1.7,-9.66, +-1.72,-9.76, +-1.81,-10.27, +-1.9,-10.78, +-1.92,-10.91, +-1.35,-10.96, +-0,-10.95, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.89, +0.7,-11.99, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.4,-11.62, +4.12,-11.33, +3.72,-12.43, +2.37,-13.42, +2.45,-13.89, +4.35,-14.44, +5.15,-15.06, +5.6,-15.38, +5.45,-14.98, +5.41,-14.88, +5.38,-14.78, +5.24,-14.41, +5.4,-14.83, +5.34,-14.67, +5.12,-14.07, +5.95,-12.79, +6.76,-11.7, +6.43,-11.14, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +8.19,-9.76, +7.75,-9.23, +7.43,-8.86, +8.03,-7.58, +7.4,-8.21, +7.48,-7.58, +8.09,-6.79, +8.65,-7.26, +8.67,-7.28, +7.58,-8.03, +7.02,-8.36, +6.87,-8.19, +7.9,-9.42, +7.85,-9.36, +7.75,-9.23, +7,-8.34, +7.61,-8.69, +8.61,-7.68, +8.61,-7.23, +8.49,-7.12, +8.49,-7.12, +8.34,-7, +8.49,-7.12, +9.05,-7.59, +8.49,-7.12, +7.74,-6.5, +7.59,-6.37, +8.48,-5.56, +8.7,-5.02, +8.59,-4.96, +8.59,-4.96, +9.09,-5.25, +10.72,-6.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.76,5.64, +-10.16,5.86, +-10.1,5.83, +-9.65,5.57, +-9.22,6.12, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-7.64,8, +-7.18,8.55, +-7.59,9.05, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.56,9.13, +-7.25,8.64, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.83,8.14, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.33,7.54, +-5.86,6.98, +-5.75,6.85, +-5.75,6.85, +-5.75,6.85, +-5.49,6.89, +-4.04,6.87, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-4.05,6.86, +-4.77,6.21, +-4.5,5.36, +-4.58,5.46, +-4.5,5.36, +-4.15,4.95, +-4.56,4.69, +-4.25,4.7, +-4.43,5.28, +-4.5,5.36, +-3.44,5.97, +-2.54,5.56, +-2.06,5.66, +-2.01,5.51, +-1.73,4.75, +-1.73,4.75, +-1.73,4.75, +-1.52,4.18, +-1.72,3.69, +-2.04,3.54, +-1.96,3.57, +-1.97,3.57, +-2.04,3.54, +-2.49,3.22, +-3.62,3.04, +-3.87,3.25, +-3.32,3.8, +-3.59,3.53, +-4.29,2.59, +-4.95,0.77, +-4.88,-1.26, +-5.46,-2.24, +-5.41,-3.12, +-5.71,-4, +-5.98,-5.02, +-6.11,-5.12, +-5.86,-5.37, +-5.12,-6.11, +-5.45,-5.78, +-6.35,-5.33, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-7.02,-5.89, +-7.02,-5.89, +-6.85,-5.75, +-6.85,-5.75, +-6.98,-4.81, +-6.9,-3.99, +-6.9,-3.99, +-6.9,-3.98, +-6.9,-3.99, +-6.69,-3.86, +-5.71,-4, +-5.36,-4.5, +-5.36,-4.5, +-5.36,-4.5, +-5.11,-4.29, +-4.24,-4.24, +-4.08,-4.86, +-4.29,-5.11, +-4.36,-5.46, +-4.3,-6.01, +-3.99,-6.9, +-4.69,-8.12, +-4.96,-8.59, +-4.96,-8.59, +-4.58,-7.93, +-2.98,-7.73, +-2.73,-7.49, +-2.7,-7.03, +-2.55,-6.68, +-2.44,-6.69, +-2.62,-7.18, +-1.58,-8.97, +-1.67,-9.44, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-1.82,-10.34, +-1.43,-9.79, +-0.29,-9.89, +-0,-10.11, +-0,-10.69, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +0,-11.4, +0.7,-11.99, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.18,-12.35, +4.23,-11.63, +4.18,-11.48, +4.57,-12.54, +3.55,-13.32, +2.99,-14.47, +5.24,-14.41, +5.62,-15.43, +5.78,-15.89, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.33,-14.65, +5.12,-14.07, +5.12,-14.07, +4.97,-13.66, +5.43,-12.61, +6.55,-11.35, +6.51,-11.28, +6.51,-11.28, +6.67,-11.56, +6.82,-11.81, +7.85,-9.95, +7.75,-9.23, +7.54,-8.98, +8.26,-7.35, +8.15,-7.46, +7.12,-8.49, +7.12,-8.49, +8.74,-7.33, +8.58,-7.2, +8.03,-7.58, +6.82,-7.76, +7,-8.34, +7.54,-8.98, +7.61,-9.07, +7.68,-9.15, +7.51,-8.95, +7.36,-8.77, +8.37,-8.39, +8.98,-7.54, +8.67,-7.28, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.67,-7.28, +8.86,-7.43, +7.89,-6.62, +7.97,-5.84, +8.59,-4.96, +8.75,-5.05, +8.59,-4.96, +8.59,-4.96, +9.26,-5.35, +6.35,-3.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.29,5.33, +-10.11,5.76, +-10.04,5.8, +-9.8,5.66, +-9.52,5.67, +-8.75,6.78, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-7.82,7.82, +-7.44,8.81, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.69,9.17, +-7.5,9.01, +-7.12,9.3, +-6.59,9.17, +-7.09,8.52, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.93,8.26, +-6.47,7.71, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-5.95,7.09, +-5.75,6.85, +-5.75,6.85, +-5.75,6.85, +-5.64,6.77, +-4.98,6.22, +-4.31,6.69, +-4.19,6.77, +-4.37,6.65, +-4.13,6.81, +-4.33,6.47, +-4.96,6.2, +-5.02,6.03, +-4.51,5.38, +-4.43,5.28, +-4.29,5.11, +-4.29,4.88, +-4.49,4, +-4.99,4.19, +-4.64,5.22, +-4.5,5.36, +-3.42,5.92, +-2.54,5.44, +-2.06,5.66, +-2.06,5.66, +-2.02,5.56, +-1.73,4.75, +-1.73,4.75, +-1.56,4.29, +-1.54,3.77, +-1.83,3.64, +-1.97,3.57, +-2.04,3.54, +-2.04,3.54, +-2.49,3.22, +-3.01,3.25, +-3.46,3.66, +-3.25,3.87, +-3.46,3.66, +-4.21,2.77, +-4.93,0.87, +-4.95,-0.87, +-4.9,-1.78, +-5.22,-3.01, +-5.5,-4.11, +-5.26,-4.59, +-4.73,-5.13, +-4.65,-5.35, +-5.12,-6.11, +-5.12,-6.11, +-6.49,-5.65, +-6.85,-5.75, +-6.85,-5.75, +-7.1,-5.96, +-7.59,-6.37, +-7.59,-6.37, +-7.1,-5.96, +-6.85,-5.75, +-7,-5.54, +-7.6,-4.39, +-7.04,-4.07, +-6.9,-3.99, +-6.9,-3.99, +-6.79,-4.15, +-6.11,-4.93, +-5.44,-4.57, +-5.36,-4.5, +-5.11,-4.29, +-4.62,-3.87, +-4.04,-4.45, +-3.87,-4.62, +-3.94,-4.7, +-4,-5.71, +-3.78,-6.54, +-3.99,-6.9, +-4.07,-7.04, +-4.23,-7.32, +-4.23,-7.32, +-4.07,-7.04, +-3.13,-7.84, +-2.78,-7.64, +-2.67,-7.34, +-2.48,-6.8, +-2.58,-7.1, +-1.61,-7.57, +-1.65,-9.34, +-1.9,-10.78, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-1.77,-10.02, +-1.86,-10.53, +-1.22,-10.39, +-0,-10.5, +-0,-11.08, +-0,-11.08, +-0,-10.93, +-0,-11.08, +0,-11.08, +1.4,-11.93, +2.09,-11.87, +2.12,-12.03, +2.79,-12.56, +3.57,-12.56, +4.53,-12.44, +4.57,-12.54, +4.79,-13.15, +5.01,-13.76, +5.12,-14.07, +5.35,-14.7, +5.73,-15.74, +5.78,-15.89, +5.45,-14.98, +5.29,-14.52, +5.37,-14.75, +5.17,-14.22, +5.12,-14.07, +5.12,-14.07, +4.84,-13.3, +6,-11.52, +6.51,-11.28, +6.51,-11.28, +6.67,-11.56, +6.94,-12.03, +7,-12.12, +7.34,-10.31, +7.75,-9.23, +7.76,-8.82, +8.49,-7.12, +8.49,-7.12, +7.58,-8.03, +8.03,-7.58, +8.49,-7.12, +8.49,-7.12, +8.26,-7.35, +7,-8.34, +7.41,-8.84, +7.2,-8.58, +7,-8.34, +7.64,-9.11, +7.48,-8.91, +7.22,-8.11, +8.65,-7.56, +8.74,-7.33, +8.67,-7.28, +8.49,-7.12, +8.61,-7.23, +8.49,-7.12, +8.67,-7.28, +8.49,-7.12, +7.89,-6.62, +8.47,-5.63, +8.59,-4.96, +8.59,-4.96, +8.27,-4.77, +8.59,-4.96, +7.66,-4.42, +2.5,-1.45, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.39,3.85, +-10.04,4.66, +-9.6,5.54, +-9.6,5.54, +-9.6,5.54, +-9.46,5.76, +-8.63,6.94, +-8.49,7.12, +-8.49,7.12, +-8.45,7.26, +-7.83,8.98, +-7.75,9.23, +-7.75,9.23, +-7.64,9.1, +-7.33,8.74, +-7.26,8.65, +-7.09,8.54, +-6.31,9.09, +-6.66,8.84, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.56,7.82, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.08,7.25, +-5.75,6.85, +-5.75,6.85, +-5.75,6.85, +-5.73,6.83, +-5.35,6.37, +-5.04,6.17, +-4.99,6.21, +-5.1,6.07, +-4.57,5.8, +-4.61,5.49, +-5.04,6.01, +-5.04,6.01, +-4.54,5.41, +-4.08,4.86, +-4.08,4.86, +-4.17,4.51, +-4.62,3.87, +-5.2,4.36, +-4.88,4.98, +-4.5,5.36, +-3.28,5.69, +-2.96,5.36, +-2.06,5.66, +-2.06,5.66, +-1.99,5.46, +-1.73,4.75, +-1.73,4.75, +-1.63,4.49, +-1.4,3.84, +-1.5,3.79, +-1.93,3.59, +-2.04,3.54, +-2.04,3.54, +-2.24,3.4, +-2.71,3.23, +-3.18,3.79, +-3.25,3.87, +-3.46,3.66, +-4.21,2.77, +-4.88,1.26, +-5.02,-0.39, +-4.8,-1.54, +-5.07,-2.82, +-4.85,-3.92, +-4.84,-5.02, +-4.5,-5.36, +-4.5,-5.36, +-5.12,-6.11, +-5.12,-6.11, +-6.3,-6.3, +-6.85,-5.75, +-6.97,-5.85, +-7.59,-6.37, +-7.59,-6.37, +-7.59,-6.37, +-7.59,-6.37, +-7.35,-6.16, +-6.93,-5.82, +-7.15,-5.32, +-7.74,-4.47, +-7.46,-4.31, +-6.99,-4.42, +-6.19,-5, +-6.11,-5.12, +-5.61,-4.71, +-5.36,-4.5, +-4.86,-4.08, +-4.62,-3.87, +-3.87,-4.62, +-3.87,-4.62, +-3.75,-4.7, +-3.34,-5.49, +-3.68,-6.38, +-3.99,-6.9, +-3.99,-6.9, +-3.71,-7.03, +-3.53,-7, +-2.87,-7.42, +-2.76,-7.59, +-2.73,-7.49, +-2.7,-7.41, +-2.56,-7.03, +-1.97,-7.19, +-1.44,-8.17, +-1.67,-9.44, +-1.92,-10.91, +-1.86,-10.53, +-1.72,-9.76, +-1.75,-9.93, +-1.72,-9.76, +-1.22,-10.59, +-0.43,-11.04, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.16, +0,-11.73, +0,-11.91, +1.4,-11.93, +2.12,-12.03, +2.6,-12.57, +4.45,-12.24, +4.69,-12.89, +4.79,-13.15, +4.79,-13.15, +4.9,-13.46, +5.12,-14.07, +5.17,-14.2, +5.41,-14.88, +5.53,-15.18, +5.49,-15.08, +5.34,-14.67, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +4.95,-13.61, +4.79,-13.15, +6.25,-11.4, +6.51,-11.28, +6.51,-11.28, +6.59,-11.42, +7,-12.12, +6.63,-11.49, +6.84,-10.46, +7.54,-8.98, +7.67,-9.14, +8.56,-7.18, +8.56,-7.18, +8.82,-7.4, +8.9,-7.47, +8.49,-7.12, +8.49,-7.12, +8.38,-7.45, +7.12,-8.49, +7.54,-8.98, +7.51,-8.95, +6.87,-8.19, +7,-8.34, +7,-8.34, +7.11,-7.47, +8.46,-7.1, +8.98,-7.54, +9.14,-7.67, +8.57,-7.19, +8.49,-7.12, +8.49,-7.12, +8.4,-7.05, +8.23,-6.91, +8.04,-6.75, +8.59,-5.46, +9.26,-5.35, +8.59,-4.96, +8.04,-4.89, +8.59,-4.96, +6.06,-3.5, +1.3,-0.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.41,3.79, +-10.19,4.32, +-9.6,5.54, +-9.6,5.54, +-9.6,5.54, +-9.6,5.54, +-8.68,6.87, +-8.49,7.12, +-8.49,7.12, +-8.82,7.43, +-8.64,8.34, +-7.75,9.23, +-7.75,9.23, +-7.26,8.65, +-7.12,8.49, +-7.12,8.49, +-7.39,8.8, +-6.86,8.77, +-6.56,8.92, +-7.1,8.51, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.04,8.39, +-6.8,8.11, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.21,7.4, +-5.79,6.9, +-5.75,6.85, +-5.75,6.85, +-5.75,6.85, +-5.52,6.58, +-5.14,6.13, +-5.12,6.11, +-4.91,5.86, +-4.5,5.36, +-4.5,5.36, +-5.04,6.01, +-5.05,6.02, +-4.56,5.39, +-4.01,4.78, +-4.22,5.03, +-4.51,4.29, +-5.03,4.22, +-5.36,4.5, +-5.07,4.79, +-4.5,5.36, +-3.56,5.62, +-3.01,5.22, +-2.06,5.66, +-2.12,5.81, +-2.06,5.66, +-1.73,4.75, +-1.73,4.75, +-1.73,4.75, +-1.47,4.04, +-1.47,3.8, +-1.97,3.57, +-1.9,3.6, +-1.97,3.57, +-1.97,3.57, +-2.24,3.4, +-2.24,3.4, +-2.56,3.17, +-3.07,3.14, +-3.8,2.5, +-4.36,1.31, +-4.87,-0.29, +-4.83,-1.44, +-4.52,-2.61, +-4.62,-3.87, +-4.42,-5.27, +-4.5,-5.36, +-4.64,-5.53, +-5.12,-6.11, +-5.4,-6.44, +-5.75,-6.85, +-6.36,-6.24, +-7.1,-6.71, +-7.05,-6.92, +-6.78,-7.19, +-7.32,-6.64, +-7.59,-6.37, +-7.59,-6.37, +-7.47,-6.27, +-6.96,-5.84, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.11,-5.12, +-6.11,-5.12, +-5.61,-4.71, +-5.36,-4.5, +-4.62,-3.87, +-4.45,-4.04, +-3.87,-4.62, +-3.87,-4.62, +-3.59,-4.82, +-3.13,-5.43, +-3.42,-5.92, +-3.58,-6.2, +-3.06,-7.15, +-2.56,-7.03, +-2.39,-6.58, +-2.39,-6.58, +-2.56,-7.03, +-2.67,-7.34, +-2.76,-7.59, +-2.28,-7.72, +-1.42,-8.06, +-1.46,-8.27, +-1.68,-9.55, +-1.79,-10.15, +-1,-10.09, +-1,-10.22, +-0.38,-10.11, +-1.5,-10.17, +-0.57,-10.64, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.08, +-0,-11.93, +0,-12.05, +0.78,-11.98, +1.86,-11.89, +2.19,-12.4, +3.72,-12.43, +4.65,-12.77, +4.79,-13.15, +4.16,-13.07, +4.84,-13.3, +5.12,-14.07, +5.12,-14.07, +5.24,-14.41, +5.45,-14.98, +5.45,-14.98, +5.23,-14.37, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.08,-13.95, +4.79,-13.15, +4.75,-13.05, +4.53,-12.44, +6.05,-11.23, +6.13,-10.62, +6.35,-11, +6.67,-11.56, +7,-12.12, +6.57,-11.38, +6.47,-10.52, +7.23,-8.61, +7.33,-8.74, +8.49,-7.12, +8.61,-7.23, +8.86,-7.43, +8.98,-7.54, +8.77,-7.36, +8.49,-7.12, +8.34,-7.27, +7.12,-8.49, +7.11,-8.47, +7.43,-8.86, +6.5,-7.74, +6.5,-7.74, +6.65,-7.93, +7.06,-6.68, +7.89,-6.62, +8.98,-7.54, +9.42,-7.9, +9.23,-7.75, +8.49,-7.12, +8.49,-7.12, +7.98,-6.69, +7.71,-6.47, +7.79,-6.54, +7.84,-6.02, +9.03,-5.22, +8.09,-5.66, +8.21,-5.49, +6.99,-4.37, +3.2,-1.85, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.41,3.79, +-10.3,4.07, +-9.96,4.79, +-9.63,5.48, +-9.65,5.57, +-9.78,5.65, +-9.11,6.27, +-8.49,7.12, +-8.49,7.12, +-8.85,7.43, +-9.04,7.92, +-8.06,8.95, +-7.54,8.98, +-7.15,8.52, +-7.12,8.49, +-7.12,8.49, +-7.41,8.84, +-7.16,8.54, +-6.59,8.89, +-6.71,8.8, +-7.12,8.49, +-7.12,8.49, +-7.11,8.5, +-6.65,7.96, +-6.37,7.59, +-6.37,7.59, +-6.32,7.64, +-6.37,7.59, +-6.1,7.8, +-6.07,7.83, +-6.37,7.59, +-5.87,7, +-5.75,6.85, +-5.75,6.85, +-5.75,6.85, +-5.75,6.85, +-5.23,6.24, +-5.12,6.11, +-5.01,5.97, +-4.79,5.71, +-4.54,5.41, +-5.04,6.01, +-5.21,5.46, +-4.67,5.08, +-4.08,4.86, +-4.01,4.78, +-4.62,3.87, +-5.36,4.5, +-5.36,4.5, +-4.93,4.93, +-4.5,5.36, +-3.67,5.55, +-2.95,5.38, +-2.06,5.66, +-2.09,5.74, +-2.06,5.66, +-1.88,5.16, +-1.73,4.75, +-1.73,4.75, +-1.54,4.24, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.4,3.84, +-1.45,3.38, +-1.94,3.37, +-2.04,3.54, +-2.43,3.26, +-2.96,2.79, +-3.56,1.96, +-3.94,1.01, +-4.06,-0.24, +-4.17,-1.35, +-4.15,-2.85, +-4.06,-4.2, +-4.01,-4.78, +-4.36,-5.2, +-4.78,-5.69, +-5.12,-6.11, +-5.54,-6.6, +-5.75,-6.85, +-5.96,-7.1, +-6.37,-7.59, +-6.37,-7.59, +-7,-8.34, +-7.12,-8.49, +-7.32,-6.64, +-7.59,-6.37, +-7.59,-6.37, +-7.35,-6.16, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.35,-5.33, +-6.11,-5.12, +-5.61,-4.71, +-5.36,-4.5, +-4.62,-3.87, +-4.37,-4.12, +-3.87,-4.62, +-3.87,-4.62, +-3.26,-5.05, +-3.08,-5.34, +-3.28,-5.69, +-3.61,-6.25, +-2.87,-7.42, +-2.54,-6.98, +-2.28,-6.27, +-2.1,-5.76, +-2.13,-5.87, +-2.5,-6.88, +-2.03,-7.96, +-1.5,-8.49, +-1.38,-7.85, +-1.5,-8.49, +-1.72,-9.76, +-1.15,-9.81, +-0,-9.91, +-0.32,-10.27, +-0.54,-10.8, +-0.86,-11.01, +-0.23,-11.38, +-0,-11.4, +-0,-11.4, +-0,-11.4, +-0,-11.4, +-0,-11.73, +-0,-12.54, +-0,-12.54, +0,-12.05, +0.35,-12.02, +2.99,-12.63, +4.45,-12.24, +4.7,-12.92, +4.79,-13.15, +2.26,-12.83, +2.34,-13.25, +3.03,-13.85, +4.22,-13.97, +4.24,-14.05, +4.5,-14.9, +4.61,-14.58, +3.77,-13.92, +4.67,-14.02, +5.12,-14.07, +5.12,-14.07, +4.95,-13.61, +4.79,-13.15, +4.51,-12.39, +4.8,-12.08, +4.96,-11.33, +6.11,-10.58, +6.46,-11.19, +6.51,-11.28, +6.71,-11.62, +6.51,-11.28, +6.79,-9.9, +7.23,-8.61, +7.12,-8.49, +8.58,-7.2, +8.49,-7.12, +8.74,-7.33, +9.23,-7.75, +8.58,-7.2, +8.49,-7.12, +8.49,-7.12, +7.12,-8.49, +7,-8.34, +7.12,-8.49, +6.71,-7.99, +6.37,-7.59, +6.37,-7.59, +7.19,-6.78, +7.59,-6.37, +8.46,-7.1, +9.79,-8.22, +9.36,-7.85, +8.86,-7.43, +8.49,-7.12, +8.04,-6.75, +7.74,-6.5, +7.89,-6.62, +8.18,-6.55, +8.93,-5.82, +7.7,-6.22, +8.34,-5.31, +5.84,-3.37, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.41,3.79, +-10.41,3.79, +-10.37,3.89, +-9.89,5.25, +-10.1,5.83, +-10.36,5.98, +-9.71,5.93, +-8.49,7.12, +-8.49,7.12, +-8.54,7.16, +-8.98,7.53, +-8.31,8.7, +-7.46,8.88, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.16,8.54, +-7.14,8.5, +-7.11,8.5, +-6.87,8.69, +-7.04,8.55, +-7.12,8.49, +-6.8,8.74, +-6,8.63, +-6.17,8.16, +-6.26,7.95, +-5.61,8.16, +-6.37,7.59, +-5.89,7.96, +-5.71,8.1, +-5.83,8, +-5.87,7, +-5.75,6.85, +-5.75,6.85, +-5.75,6.85, +-5.75,6.85, +-5.25,6.25, +-5.12,6.11, +-5.12,6.11, +-5.11,6.09, +-5.07,6.04, +-5.1,6.08, +-5.37,5.15, +-4.64,5.22, +-4.39,5.24, +-4.24,4.24, +-4.71,3.95, +-5.36,4.5, +-5.36,4.5, +-4.88,4.98, +-4.5,5.36, +-3.84,5.56, +-3.16,5.47, +-2.41,5.79, +-2.21,6.07, +-2.02,5.56, +-1.78,4.88, +-1.73,4.75, +-1.73,4.75, +-1.62,4.44, +-1.4,3.84, +-1.56,4.29, +-1.4,3.84, +-1.4,3.84, +-1.14,2.81, +-1.52,2.64, +-2.04,3.38, +-2.62,3.12, +-3.31,2.36, +-3.79,1.46, +-4.05,0.39, +-4.01,-0.63, +-3.69,-1.72, +-3.66,-3.1, +-3.56,-4.24, +-3.3,-5.02, +-3.26,-5.24, +-5.02,-5.98, +-5.12,-6.11, +-5.75,-6.85, +-5.75,-6.85, +-6.16,-7.35, +-6.37,-7.59, +-6.87,-8.19, +-7.12,-8.49, +-7.12,-8.49, +-6.71,-7.99, +-7.07,-6.9, +-7.59,-6.37, +-7.59,-6.37, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.35,-5.33, +-6.11,-5.12, +-5.61,-4.71, +-5.36,-4.5, +-4.62,-3.87, +-4.37,-4.12, +-3.87,-4.62, +-3.87,-4.62, +-3.4,-4.95, +-3.18,-5.5, +-3.42,-5.92, +-2.39,-6.58, +-2.5,-6.88, +-2.39,-6.58, +-2.12,-5.81, +-1.78,-4.9, +-2.01,-5.51, +-1.7,-7.46, +-1.37,-7.74, +-1.38,-7.85, +-1.38,-7.85, +-1.55,-8.81, +-1.72,-9.76, +-1.34,-9.8, +-0.98,-9.83, +-0,-10.58, +-0,-11.08, +-0.32,-11.05, +-0.7,-11.99, +-0.35,-12.02, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.23, +-0,-13.02, +0,-12.7, +0.7,-11.99, +1.4,-11.93, +2.64,-12.06, +3.15,-12.3, +3.52,-12.99, +3.52,-12.99, +2.26,-12.83, +2.37,-13.46, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.6,-14.74, +2.6,-14.74, +3.63,-14.13, +5.8,-13.34, +6.1,-13.61, +6.87,-13.01, +6.61,-12.54, +5.41,-12.5, +4.45,-12.24, +4.99,-11.83, +5.53,-10.82, +6.27,-10.86, +6.43,-11.14, +6.51,-11.28, +6.45,-11.17, +6.39,-10.38, +7.46,-9.43, +7.12,-8.49, +7.58,-8, +8.55,-7.17, +8.57,-7.19, +8.49,-7.12, +8.74,-7.33, +8.49,-7.12, +8.86,-7.43, +8.49,-7.12, +7.58,-8.03, +7.26,-8.66, +7.23,-8.61, +6.5,-7.74, +5.96,-7.1, +6.29,-7.5, +7.59,-6.37, +7.59,-6.37, +7.79,-6.54, +9.15,-7.68, +9.73,-8.16, +9.11,-7.64, +8.49,-7.12, +8.49,-7.12, +7.89,-6.62, +7.59,-6.37, +7.59,-6.37, +7.91,-6.26, +7.59,-6.37, +8.46,-5.13, +9.06,-4.86, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.84,3.94, +-10.74,3.91, +-10.43,3.8, +-10.62,4.18, +-10.39,5.56, +-10.4,6, +-10.28,6.04, +-8.76,6.76, +-8.49,7.12, +-8.49,7.12, +-8.67,7.28, +-8.28,8.67, +-7.33,8.74, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.87,8.68, +-6.97,8.61, +-7.05,8.54, +-6.09,9.23, +-5.54,9.6, +-5.54,9.6, +-5.8,8.96, +-5.35,8.49, +-6.04,7.85, +-5.56,8.2, +-5.49,8.24, +-5.55,8.19, +-5.91,7.05, +-5.75,6.85, +-5.75,6.85, +-5.75,6.85, +-5.72,6.82, +-5.27,6.28, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.39,5.85, +-5.52,5.72, +-5.89,5.32, +-4.43,5.28, +-4.15,4.95, +-4.37,4.12, +-4.62,3.87, +-5.36,4.5, +-5.36,4.5, +-5.36,4.5, +-4.5,5.36, +-3.98,5.55, +-3.5,6.06, +-3.07,5.73, +-2.34,6.42, +-2.06,5.66, +-1.73,4.75, +-1.73,4.75, +-1.73,4.75, +-1.73,4.75, +-1.62,4.44, +-1.47,4.04, +-1.43,3.94, +-1.5,3.48, +-1.55,2.45, +-1.72,2.34, +-2.27,2.39, +-3.33,2.33, +-3.55,1.54, +-2.86,0.5, +-3.61,-0.67, +-3.74,-1.61, +-3.41,-2.19, +-3.2,-3.37, +-3.25,-3.87, +-2.61,-4.32, +-2.53,-4.38, +-3.42,-4.54, +-4.5,-5.36, +-5.75,-6.85, +-5.75,-6.85, +-6.37,-7.59, +-6.37,-7.59, +-7.12,-8.49, +-7.12,-8.49, +-7.12,-8.49, +-7,-8.34, +-6.37,-7.59, +-6.82,-7.15, +-7.21,-6.3, +-6.85,-5.75, +-6.85,-5.75, +-6.85,-5.75, +-6.6,-5.54, +-6.11,-5.12, +-5.61,-4.71, +-5.36,-4.5, +-4.62,-3.87, +-4.37,-4.12, +-3.87,-4.62, +-3.87,-4.62, +-3.87,-4.62, +-3.38,-5.62, +-3.5,-6.06, +-3.29,-6.51, +-2.65,-7.29, +-2.62,-7.18, +-2.28,-6.27, +-1.58,-4.9, +-1.17,-5.4, +-1.24,-7.03, +-1.27,-7.21, +-1.44,-8.17, +-1.41,-8.01, +-1.61,-9.13, +-1.15,-9.81, +-0.86,-9.84, +-1.29,-9.8, +-0.16,-10.68, +-0,-11.08, +-0,-11.3, +-0,-11.51, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +0.75,-12.47, +0.35,-12.67, +1.02,-11.8, +2.09,-11.87, +2.09,-11.87, +2.18,-12.39, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.39,-13.57, +2.43,-13.78, +2.43,-13.78, +2.43,-13.78, +2.6,-14.74, +3.86,-14.4, +4.42,-13.42, +4.79,-13.15, +4.66,-12.81, +4.68,-12.85, +6.7,-13.33, +6.25,-11.87, +6.12,-11.11, +5.17,-11.1, +5.39,-10.73, +6.21,-10.75, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.31,-10.24, +7.33,-8.74, +7,-8.34, +6.5,-7.74, +7.26,-7.53, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +7.63,-7.15, +7.59,-9.05, +7.33,-8.74, +6.16,-7.35, +5.75,-6.85, +6.44,-6.16, +7.59,-6.37, +7.74,-6.5, +8.26,-6.29, +8.84,-7.42, +9.07,-7.61, +8.65,-7.26, +8.12,-6.81, +8.04,-6.75, +7.74,-6.5, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +7.76,-6.14, +8.59,-4.96, +7.96,-4.23, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.33,4.12, +-11.08,4.03, +-10.41,3.79, +-10.41,3.79, +-9.96,4.97, +-9.9,5.72, +-10.3,5.94, +-9.16,6.2, +-8.51,7.09, +-8.49,7.12, +-8.6,7.22, +-8.82,7.91, +-7.39,8.3, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.03,8.56, +-6.08,9.24, +-5.54,9.6, +-5.48,9.48, +-5.31,9.2, +-5,8.66, +-5.53,8.2, +-5.48,8.26, +-5.49,8.24, +-5.37,8.27, +-5.79,7.07, +-5.75,6.85, +-5.75,6.85, +-5.64,6.73, +-5.25,6.25, +-5.14,6.12, +-5.12,6.11, +-5.12,6.11, +-5.28,5.96, +-5.96,5.28, +-6.09,5.11, +-5.76,4.98, +-3.94,4.7, +-3.94,4.7, +-4.37,4.12, +-4.86,4.08, +-4.99,4.19, +-4.99,4.19, +-5.15,4.32, +-4.62,5.24, +-4.4,5.43, +-3.39,5.61, +-2.66,5.62, +-2.36,6.47, +-2.08,5.42, +-2.17,4.54, +-2.26,4.5, +-1.99,4.63, +-1.73,4.75, +-1.56,4.29, +-1.45,3.99, +-1.33,3.65, +-1.49,3.12, +-1.87,2.23, +-1.93,2.18, +-2.3,1.78, +-3.06,1.52, +-3.18,0.86, +-2.9,-0.06, +-3.54,-1, +-3.62,-1.87, +-3.17,-2.57, +-3.14,-3.37, +-3.04,-3.62, +-2.4,-3.68, +-2.53,-4.38, +-3.25,-3.87, +-3.25,-3.87, +-5.75,-6.85, +-5.9,-7.04, +-6.37,-7.59, +-6.37,-7.59, +-7.12,-8.49, +-7.12,-8.49, +-7.12,-8.49, +-7.05,-8.4, +-6.37,-7.59, +-6.37,-7.59, +-5.96,-7.1, +-5.75,-6.85, +-5.75,-6.85, +-5.75,-6.85, +-5.54,-6.6, +-5.26,-5.97, +-5.01,-5.31, +-4.98,-4.88, +-4.86,-4.08, +-4.12,-4.37, +-3.87,-4.62, +-3.87,-4.62, +-4.03,-4.8, +-3.99,-6.51, +-4.23,-7.32, +-4.15,-7.18, +-3.3,-7.04, +-2.73,-7.49, +-2.5,-6.88, +-1.78,-5.91, +-1.06,-6.02, +-1.18,-6.68, +-1.22,-6.89, +-1.33,-7.53, +-1.31,-7.42, +-0.79,-8.87, +-0.86,-9.84, +-0.86,-9.84, +-0,-9.91, +-0.43,-10.02, +-0,-11.08, +-0,-11.73, +-0,-12.05, +-0,-12.05, +-0,-12.05, +-0,-12.05, +0,-12.05, +0.23,-12.03, +0.47,-12.01, +1.32,-11.29, +1.49,-11.92, +2.09,-11.87, +2.09,-11.87, +2.18,-12.35, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.35,-13.3, +2.43,-13.78, +2.43,-13.78, +2.49,-14.1, +3.16,-14.59, +4.73,-13.84, +4.79,-13.15, +4.79,-13.15, +4.54,-12.47, +5.46,-12.48, +7.48,-12.96, +6.84,-11.84, +6.59,-11.42, +5.65,-10.88, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +5.9,-10.23, +6.84,-9.6, +7.12,-8.49, +6.87,-8.19, +7.12,-8.49, +7.28,-7.39, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.23,-6.91, +8.09,-6.79, +7.35,-8.53, +7.26,-8.65, +6.28,-7.49, +5.75,-6.85, +6.44,-6.16, +7.59,-6.37, +7.59,-6.37, +8.65,-6.3, +9.31,-7.36, +8.34,-7, +8.34,-7, +8.38,-7.03, +7.79,-6.54, +7.69,-6.46, +7.82,-6.56, +7.74,-6.5, +7.89,-6.62, +7.88,-5.97, +8.59,-4.96, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.35,4.06, +-11.08,4.03, +-10.56,3.84, +-10.44,3.8, +-10.06,4.62, +-9.65,5.49, +-9.88,5.7, +-9.56,5.6, +-8.74,6.8, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-8.09,7.55, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.34,9.05, +-5.46,9.46, +-5.1,8.83, +-4.98,8.62, +-4.96,8.59, +-4.96,8.59, +-5.18,8.45, +-5.32,8.35, +-4.79,8.3, +-5.42,7.1, +-5.65,6.73, +-5.48,6.54, +-5.16,6.15, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.26,5.98, +-5.94,5.3, +-6.11,5.12, +-5.99,5.03, +-4.37,4.58, +-3.87,4.62, +-4.08,4.86, +-4.62,3.87, +-4.95,4.15, +-5.11,4.29, +-4.68,3.93, +-4.77,4, +-4.48,4.77, +-4.43,5.28, +-3.6,5.73, +-3.17,5.5, +-2.86,5.56, +-2.69,4.66, +-2.53,4.38, +-2.53,4.38, +-2.89,4.12, +-2.44,3.81, +-2.21,3.36, +-1.43,2.95, +-1.33,3.71, +-1.54,2.75, +-1.64,2.4, +-1.83,2.27, +-2.11,1.99, +-2.54,1.4, +-2.82,0.72, +-3.48,-0.24, +-3.93,-1.05, +-3.54,-2.04, +-3.13,-2.62, +-3.44,-3.46, +-3.25,-3.87, +-2.73,-3.71, +-2.69,-4.26, +-3.25,-3.87, +-3.25,-3.87, +-5.75,-6.85, +-5.96,-7.1, +-6.37,-7.59, +-6.37,-7.59, +-7.12,-8.49, +-7.12,-8.49, +-7.12,-8.49, +-6.84,-8.15, +-6.37,-7.59, +-6.37,-7.59, +-5.89,-7.02, +-5.75,-6.85, +-5.75,-6.85, +-5.75,-6.85, +-5.54,-6.6, +-5.12,-6.11, +-4.71,-5.61, +-4.5,-5.36, +-4.39,-5.24, +-3.87,-4.62, +-3.87,-4.62, +-4.08,-4.86, +-4.32,-5.15, +-4.05,-6.47, +-4.15,-7.18, +-4.2,-7.28, +-3.43,-7.16, +-2.65,-7.29, +-2.39,-6.58, +-1.68,-6.89, +-1.12,-6.34, +-1.19,-6.73, +-1.24,-7.05, +-1.27,-7.21, +-1.33,-7.53, +-0.98,-8.53, +-0.74,-9.43, +-0.17,-8.93, +-0,-9.27, +-0,-10.04, +-0,-11.08, +0,-11.73, +0,-12.05, +0.7,-11.99, +0.35,-12.02, +0.7,-11.99, +0.52,-12.01, +1.05,-11.96, +1.4,-11.93, +1.66,-11.26, +1.27,-11.7, +2.09,-11.87, +2.08,-11.77, +2.18,-12.39, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.37,-13.46, +2.35,-13.3, +2.72,-13.7, +4.08,-13.67, +4.9,-13.46, +4.79,-13.15, +4.79,-13.15, +4.75,-13.06, +4.45,-12.24, +5.46,-12.48, +7.11,-12.31, +6.79,-11.76, +6.43,-11.14, +6.09,-10.54, +5.86,-10.16, +6.03,-10.44, +6.03,-10.44, +5.86,-10.16, +5.72,-9.91, +6.68,-9, +7.12,-8.49, +6.96,-8.29, +6.72,-7.79, +8.44,-7.08, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.19,-6.87, +7.59,-6.37, +7.59,-6.37, +7.28,-7.68, +6.5,-7.74, +6.12,-6.99, +5.95,-6.65, +6.74,-5.86, +7.04,-5.9, +7.59,-6.37, +9.01,-6.29, +9.83,-6.89, +8.61,-7.23, +7.74,-6.5, +8.38,-7.03, +8.04,-6.75, +8.34,-7, +7.59,-6.37, +7.59,-6.37, +7.69,-6.46, +8.26,-5.43, +8.92,-5.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.78,2.5, +-11.23,3.86, +-10.9,3.97, +-11,4, +-10.95,4.13, +-10.25,5.68, +-10.1,5.83, +-9.61,5.55, +-8.76,6.76, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-8.09,7.55, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.11,8.47, +-7.01,8.36, +-6.49,8.07, +-6.27,8, +-5.23,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.64,8.04, +-5.35,7.1, +-5.21,6.34, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.38,5.87, +-6.11,5.12, +-6.11,5.12, +-5.23,4.71, +-3.6,4.29, +-3.94,4.7, +-4.11,4.53, +-4.62,3.87, +-5.02,4.21, +-5.36,4.5, +-5.2,4.36, +-4.7,3.94, +-4.21,4.74, +-3.94,4.96, +-3.16,5.12, +-3.09,5.36, +-3.01,5.22, +-2.8,4.84, +-2.84,4.29, +-3.01,4.04, +-3.25,3.87, +-2.76,3.29, +-2.3,3.35, +-2.24,3.87, +-2.37,4.1, +-2.48,4.29, +-1.8,3.11, +-1.67,2.38, +-2.05,2.05, +-2.43,1.6, +-2.98,0.65, +-3.95,-0.35, +-3.9,-1.17, +-3.54,-2.04, +-3.54,-2.97, +-3.66,-3.46, +-3.25,-3.87, +-3.25,-3.87, +-3.01,-4.04, +-2.89,-4.12, +-2.97,-4.26, +-4.68,-7.59, +-5.21,-7.42, +-6.37,-7.59, +-6.37,-7.59, +-6.96,-8.29, +-7.12,-8.49, +-7.12,-8.49, +-6.46,-7.69, +-6.37,-7.59, +-6.37,-7.59, +-5.75,-6.85, +-5.75,-6.85, +-5.75,-6.85, +-5.75,-6.85, +-5.54,-6.6, +-5.12,-6.11, +-4.85,-5.77, +-4.5,-5.36, +-4.5,-5.36, +-4.5,-5.36, +-4.5,-5.36, +-4.5,-5.36, +-4.12,-5.45, +-3.66,-6.34, +-3.99,-6.9, +-3.99,-7.43, +-2.94,-7.39, +-2.62,-7.18, +-2.39,-6.58, +-1.28,-7.24, +-1.29,-7.32, +-1.18,-6.68, +-1.16,-6.57, +-1.22,-6.89, +-0.69,-7.91, +-0.23,-8.76, +-0,-8.94, +-0,-8.94, +-0,-9.19, +-0,-10.3, +0.32,-11.05, +0.7,-11.67, +1.31,-11.94, +0.47,-12.01, +1.16,-11.95, +1.14,-11.95, +0.63,-12, +0.78,-11.98, +1.05,-11.96, +1.28,-11.29, +1.95,-11.07, +2.04,-11.55, +2.09,-11.87, +2.15,-12.19, +2.26,-12.83, +2.26,-12.83, +2.26,-12.83, +2.28,-12.93, +3.64,-13.09, +4.01,-13.16, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.75,-13.04, +4.51,-12.39, +5.3,-12.2, +7,-12.12, +6.57,-11.38, +6.34,-10.97, +6.03,-10.44, +5.86,-10.16, +5.96,-10.32, +5.95,-10.3, +5.54,-9.6, +6.07,-9.82, +7.33,-8.74, +7.12,-8.49, +7.12,-8.49, +7.49,-7.51, +8.74,-7.33, +8.57,-7.19, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.04,-6.75, +7.59,-6.37, +7.41,-6.22, +6.43,-6.85, +6.22,-6.61, +6.22,-6.61, +6.51,-6.25, +6.85,-5.75, +6.85,-5.75, +7.59,-6.37, +8.66,-5.52, +10.1,-5.83, +9.11,-7.64, +7.58,-6, +7.72,-6.48, +8.49,-7.12, +8.49,-7.12, +7.82,-6.56, +7.59,-6.37, +7.74,-6.5, +8.59,-5.46, +1.91,-1.1, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.87,2.09, +-11.54,3.35, +-11.26,4.1, +-11.33,4.12, +-11.28,4.23, +-10.54,5.84, +-10.33,5.96, +-9.65,5.57, +-9.11,6.26, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-8.2,7.44, +-7.17,8.45, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.89,8.21, +-6.45,7.69, +-5.58,8.22, +-5.42,8.29, +-5.13,8.48, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.94,8.55, +-4.78,8.29, +-4.51,7.82, +-4.91,7.46, +-4.84,6.98, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.29,5.48, +-5.87,4.93, +-6.11,5.12, +-3.81,4.07, +-3.32,3.95, +-3.87,4.62, +-4.28,4.21, +-4.62,3.87, +-4.99,4.19, +-5.36,4.5, +-5.11,4.29, +-4.74,3.98, +-4.2,4.29, +-3.68,4.75, +-3.3,5.02, +-3.4,4.95, +-3.2,5.08, +-3.64,4.28, +-3.97,4.37, +-3.25,3.87, +-3.25,3.87, +-2.94,3.5, +-2.14,3.47, +-2.04,3.54, +-1.49,3.15, +-1.93,3.8, +-2.72,4.93, +-2.18,3.67, +-3.51,3.8, +-2.77,1.8, +-3.57,0.64, +-4.04,-0.47, +-3.89,-1.2, +-3.54,-2.04, +-3.87,-3.25, +-3.46,-3.66, +-3.25,-3.87, +-3.01,-4.04, +-2.61,-4.32, +-2.53,-4.38, +-2.74,-4.75, +-3.88,-6.72, +-4.47,-7.74, +-4.9,-8.49, +-4.96,-8.59, +-5.66,-8.09, +-6.11,-8.66, +-5.79,-8.24, +-6.37,-7.59, +-6.37,-7.59, +-6.37,-7.59, +-5.75,-6.85, +-5.75,-6.85, +-5.75,-6.85, +-5.75,-6.85, +-5.54,-6.6, +-5.12,-6.11, +-4.94,-5.89, +-4.5,-5.36, +-4.5,-5.36, +-4.5,-5.36, +-4.5,-5.36, +-4.5,-5.36, +-3.92,-5.62, +-3.61,-6.25, +-3.38,-6.71, +-2.69,-7.39, +-2.54,-6.98, +-2.36,-6.47, +-2.04,-6.67, +-1.27,-7.21, +-1.3,-7.37, +-1.22,-6.89, +-0.69,-8.07, +-0.52,-8.73, +-0,-8.94, +-0,-8.94, +-0,-9.05, +-0,-9.18, +0,-9.91, +0.19,-10.16, +0.55,-11.03, +1.05,-11.96, +1.05,-11.96, +1.74,-11.9, +1.05,-11.96, +1.05,-11.96, +1.74,-11.9, +2.09,-11.87, +2.09,-11.87, +2.04,-11.55, +1.92,-10.91, +2.26,-11.49, +2.92,-11.75, +2.96,-12.47, +2.6,-12.57, +2.26,-12.83, +3.36,-12.53, +4.09,-12.34, +4.66,-12.81, +4.62,-12.7, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.75,-13.06, +4.51,-12.39, +5.28,-12.21, +6.84,-11.84, +6.51,-11.28, +7.44,-10.63, +6.6,-10.04, +6.03,-10.44, +5.6,-9.7, +5.54,-9.6, +5.8,-9.41, +7.19,-9.23, +7.57,-9.02, +7.12,-8.49, +7.12,-8.49, +7.8,-7.8, +8.74,-7.33, +8.86,-7.43, +8.49,-7.12, +8.49,-7.12, +8.36,-7.02, +7.69,-6.46, +7.59,-6.37, +7.05,-5.92, +6.79,-6.04, +6.4,-6.42, +6.94,-6.57, +6.73,-6.21, +6.85,-5.75, +6.85,-5.75, +7.76,-6.14, +8.71,-5.03, +9.91,-5.72, +10.3,-6.22, +8.36,-6.79, +7.17,-6.02, +8.19,-6.87, +8.49,-7.12, +8.26,-6.93, +7.74,-6.5, +7.59,-6.37, +6.44,-4.22, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.93,2.1, +-11.68,3.16, +-11.37,4.14, +-11.33,4.12, +-11.32,4.14, +-11,4.87, +-10.35,5.98, +-9.65,5.57, +-9.44,5.8, +-8.53,7.07, +-8.49,7.12, +-8.67,7.27, +-8.63,7.36, +-7.38,8.32, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-6.66,7.94, +-6.37,7.59, +-6.31,7.64, +-5.42,8.28, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.95,8.57, +-4.68,8.1, +-4.47,7.74, +-4.47,7.74, +-4.56,7.69, +-5.27,6.73, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-5.12,6.11, +-4.97,5.84, +-5.28,4.68, +-5.81,4.87, +-5.33,4.68, +-3.48,3.18, +-3.25,3.87, +-3.71,4, +-4.62,3.87, +-4.87,3.5, +-5.22,3.57, +-5.67,4.05, +-5.42,4.04, +-4.94,3.97, +-4.29,4.2, +-3.46,4.12, +-3.87,4.62, +-3.87,4.62, +-4,4.49, +-4.62,3.87, +-4.62,3.87, +-3.58,4, +-3.25,3.87, +-2.96,3.81, +-2.04,3.54, +-1.97,3.57, +-1.35,3.71, +-1,2.74, +-1,2.74, +-1.29,2.61, +-3.44,3.94, +-4.31,2.92, +-4.01,0.59, +-4.04,-0.47, +-3.72,-1.64, +-3.35,-2.3, +-3.87,-3.25, +-3.32,-3.8, +-3.25,-3.87, +-2.69,-4.26, +-2.53,-4.38, +-2.65,-4.59, +-3.01,-5.22, +-3.18,-5.5, +-4.23,-7.32, +-4.71,-8.16, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-5.27,-8.37, +-5.59,-8.15, +-5.57,-6.98, +-5.75,-6.85, +-5.75,-6.85, +-5.75,-6.85, +-5.54,-6.6, +-5.12,-6.11, +-5.04,-6.01, +-4.57,-5.9, +-4.33,-7.88, +-3.98,-8.61, +-4.34,-6.48, +-4.13,-6.68, +-3.46,-7.15, +-3.58,-6.2, +-2.84,-6.54, +-2.39,-6.58, +-2.39,-6.58, +-2.2,-6.05, +-2.09,-6.54, +-1.23,-7, +-1.16,-6.57, +-1.31,-7.42, +-1.08,-7.88, +-0.48,-8.68, +-0,-8.94, +-0,-9.27, +-0,-9.91, +0,-9.91, +0,-9.91, +0.29,-9.89, +0.72,-11.02, +1.07,-10.99, +1.96,-11.12, +1.86,-11.89, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +1.6,-10.94, +1.91,-10.86, +2.01,-11.39, +2.85,-11.67, +2.22,-12.61, +2.24,-12.72, +2.26,-12.83, +3.97,-12.37, +4.45,-12.24, +4.5,-12.35, +4.62,-12.7, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.74,-13.02, +4.75,-13.05, +4.53,-12.44, +5.34,-12.42, +7.19,-10.94, +8.37,-9.98, +8.14,-9.7, +7.96,-9.48, +7.54,-8.98, +6.86,-8.67, +6.86,-8.67, +6.79,-8.87, +7.46,-9.43, +7.43,-8.86, +7.12,-8.49, +7.18,-8.56, +7.88,-7.73, +8.74,-7.33, +8.98,-7.54, +8.49,-7.12, +8.49,-7.12, +8.04,-6.75, +7.59,-6.37, +7.35,-6.16, +6.77,-5.68, +6.52,-5.92, +6.81,-5.94, +7.35,-6.16, +7.04,-5.9, +6.85,-5.75, +6.85,-5.75, +7.68,-5.69, +8.59,-4.96, +9.06,-5.23, +10.73,-5.79, +9.25,-6.45, +7.43,-6.23, +7.53,-6.32, +8.4,-7.05, +8.67,-7.28, +8.04,-6.75, +7.89,-6.62, +8.96,-5.18, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.76,2.25, +-12.62,2.63, +-11.58,4.17, +-11.33,4.12, +-11.33,4.12, +-11.08,4.7, +-10.38,5.99, +-9.65,5.57, +-9.42,5.82, +-8.57,7.02, +-8.49,7.12, +-8.5,7.14, +-8.82,7.4, +-8.32,7.46, +-7.37,8.26, +-7.12,8.49, +-7.12,8.49, +-7.12,8.49, +-7.08,8.44, +-6.61,7.88, +-6.37,7.59, +-6.16,7.76, +-5.75,8.05, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.75,8.22, +-4.49,7.77, +-4.47,7.74, +-4.47,7.74, +-4.52,7.72, +-5.13,7.02, +-5.16,6.15, +-5.12,6.11, +-5.01,5.97, +-4.94,5.89, +-4.92,5.08, +-5.37,4.53, +-5.83,4.89, +-5.54,3.65, +-5.27,1.92, +-6.21,2.26, +-7.18,2.62, +-7.49,2.73, +-8.4,3.06, +-8.4,3.06, +-8.4,3.06, +-8.2,2.98, +-6.29,2.95, +-6.08,3.01, +-3.25,3.87, +-3.77,4.26, +-4.57,4, +-4.92,3.37, +-4.88,3.49, +-4.62,3.87, +-4.23,3.8, +-3.25,3.87, +-2.85,4.15, +-2.09,3.63, +-1.61,3.74, +-1.4,3.84, +-1,2.74, +-1,2.74, +-1.56,3.19, +-2.46,2.85, +-5.02,2.76, +-6.1,1, +-5.22,-1.16, +-3.98,-2.11, +-3.17,-2.56, +-3.69,-3.43, +-3.25,-3.87, +-5.3,-7.32, +-4.54,-7.86, +-4.78,-8.28, +-3.44,-5.96, +-6.03,-10.44, +-5.67,-9.82, +-5.15,-8.92, +-4.88,-9.03, +-4.84,-8.38, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-4.96,-8.59, +-4.47,-7.74, +-4.61,-7.64, +-4.9,-7.45, +-4.9,-7.45, +-4.85,-7.09, +-4.74,-6.37, +-5.04,-6.01, +-4.63,-6.28, +-3.98,-10.11, +-3.79,-10.41, +-3.84,-10.56, +-3.79,-10.41, +-3.46,-7.15, +-3.5,-6.06, +-2.59,-6.72, +-2.62,-7.18, +-2.25,-6.17, +-1.93,-5.3, +-2,-6.18, +-1.33,-7.53, +-1.05,-5.93, +-0.46,-7.93, +-0,-7.97, +-0,-8.29, +-0,-8.94, +-0,-8.94, +0.27,-9.32, +0.38,-9.88, +0.57,-9.86, +1.15,-9.81, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +2.01,-11.39, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +1.94,-11.02, +2.21,-10.98, +2.05,-11.63, +2.09,-11.87, +2.09,-11.87, +2.12,-12.03, +3.61,-11.96, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.68,-12.85, +4.79,-13.15, +4.79,-13.15, +4.68,-12.85, +4.75,-13.04, +4.79,-13.15, +4.73,-13, +6.26,-12.46, +7.54,-10.16, +8.22,-9.79, +7.8,-9.3, +7.75,-9.23, +7.68,-9.15, +7.3,-8.7, +7.21,-8.59, +6.9,-8.65, +7.36,-9.31, +7.33,-8.74, +7.12,-8.49, +7.37,-8.41, +8.49,-7.12, +8.82,-7.4, +8.82,-7.4, +8.49,-7.12, +8.49,-7.12, +8.19,-6.87, +7.59,-6.37, +7.35,-6.16, +6.85,-5.75, +6.3,-6.75, +6.04,-6.79, +7.19,-6.78, +6.6,-5.88, +6.85,-5.75, +6.85,-5.75, +8.16,-4.93, +8.59,-4.96, +8.71,-4.7, +11.03,-4.76, +9.04,-6.33, +8.34,-7, +7.1,-5.96, +8.26,-6.93, +8.49,-7.12, +8.57,-7.19, +8.76,-6.73, +9.22,-5.32, +1.43,-0.83, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.83,2.26, +-12.64,2.53, +-11.35,4.06, +-11.33,4.12, +-11.33,4.12, +-10.98,4.91, +-10.18,5.88, +-9.6,5.54, +-9.6,5.54, +-8.92,6.62, +-8.9,7.47, +-8.6,7.22, +-8.59,7.21, +-8.51,7.14, +-8.03,7.6, +-7.19,8.43, +-7.12,8.49, +-7.12,8.49, +-7,8.34, +-6.37,7.59, +-6.37,7.59, +-5.95,7.92, +-5.27,8.39, +-4.96,8.59, +-4.96,8.59, +-4.96,8.59, +-4.8,8.31, +-4.5,7.8, +-4.47,7.74, +-4.47,7.74, +-4.45,7.71, +-4.48,7.66, +-5.06,7.03, +-5.18,6.17, +-4.92,5.86, +-4.56,5.44, +-4.63,5.42, +-5.15,4.72, +-5.78,4.85, +-6.22,4.97, +-4.88,1.26, +-5.59,1.91, +-6.58,2.39, +-7.18,2.62, +-7.49,2.73, +-8.4,3.06, +-8.4,3.06, +-8.4,3.06, +-7.95,2.89, +-7.21,2.76, +-6.58,2.39, +-5.76,2.75, +-5.84,2.97, +-6.35,2.88, +-5.61,2.93, +-5.22,3.01, +-4.92,3.44, +-4.37,3.67, +-3.25,3.87, +-2.77,4.21, +-2.04,3.54, +-1.4,3.84, +-1.32,3.86, +-0.73,3.14, +-0.75,2.81, +-1.42,3.68, +-2.3,3.35, +-3.36,2.24, +-4.63,0.7, +-7.51,-2.49, +-6.32,-4.25, +-4.13,-4.36, +-6.36,-7.58, +-6.61,-8.03, +-4.04,-6.57, +-4.81,-8.32, +-5.02,-8.7, +-5.02,-8.7, +-4.46,-7.73, +-5.83,-10.1, +-4.57,-10.12, +-4.18,-9.27, +-4.96,-11.36, +-4.96,-8.59, +-4.05,-8.89, +-4.43,-8.83, +-4.78,-8.67, +-4.43,-8.83, +-4.26,-8.91, +-3.63,-8.97, +-4.71,-8.16, +-4.47,-7.74, +-4.47,-7.74, +-4.39,-7.6, +-4.26,-7.38, +-4.59,-6.84, +-4.75,-7.15, +-3.79,-10.41, +-4.09,-11.22, +-4.12,-11.33, +-3.86,-10.61, +-3.92,-8.07, +-3.78,-7, +-2.73,-7.49, +-2.73,-7.49, +-2.48,-6.8, +-1.64,-7.53, +-1.38,-7.85, +-1.38,-7.85, +-0.62,-7.92, +-0,-7.97, +-0,-7.97, +-0,-8.29, +-0,-8.94, +-0,-8.94, +0.25,-9.2, +0.86,-9.84, +0.86,-9.84, +1.38,-10.05, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +2.87,-11.23, +2.35,-11.47, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.02,-11.44, +2.09,-11.87, +2.07,-11.76, +2.07,-11.76, +2.45,-12.06, +4.01,-12.02, +4.34,-11.93, +4.41,-12.12, +4.45,-12.24, +4.68,-12.85, +4.79,-13.15, +4.75,-13.04, +4.68,-12.85, +4.79,-13.15, +4.79,-13.15, +4.62,-12.7, +5.81,-12.5, +7.75,-10.41, +7.82,-9.33, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.8,-9.29, +7.19,-8.57, +7.2,-8.58, +7.12,-8.49, +7.12,-8.49, +7.63,-7.98, +8.49,-7.12, +8.74,-7.33, +8.61,-7.23, +8.49,-7.12, +8.49,-7.12, +8.09,-6.79, +7.59,-6.37, +7.41,-6.22, +6.96,-5.84, +5.99,-6.38, +5.75,-6.85, +6.16,-6.44, +5.33,-6.35, +6.85,-5.75, +6.97,-5.85, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +10.69,-4.63, +9.29,-6.19, +8.49,-7.12, +7.92,-6.64, +7.84,-6.58, +8.61,-7.23, +8.86,-7.43, +8.82,-6.65, +8.79,-5.57, +7.51,-4.34, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.83,2.26, +-12.75,2.5, +-11.79,4.2, +-11.38,4.14, +-11.33,4.12, +-10.98,4.91, +-10.06,5.73, +-9.6,5.54, +-9.6,5.54, +-9.44,5.8, +-8.85,7.3, +-9.03,7.58, +-8.49,7.12, +-8.49,7.12, +-8.4,7.22, +-7.27,8.36, +-7.12,8.49, +-7.12,8.49, +-6.86,8.17, +-6.37,7.59, +-6.37,7.59, +-5.56,8.19, +-4.96,8.59, +-4.96,8.59, +-4.92,8.53, +-4.83,8.36, +-4.51,7.82, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.13,7.15, +-4.01,6.95, +-4.39,6.63, +-5,5.96, +-4.52,5.38, +-4.5,5.36, +-4.75,5.26, +-5.36,4.53, +-5.71,4.79, +-6.42,4.09, +-4.98,0.88, +-5.83,1.43, +-6.58,2.39, +-7.34,2.67, +-7.49,2.73, +-8.4,3.06, +-8.4,3.06, +-8.3,3.02, +-8.1,2.95, +-7.79,2.84, +-7.39,2.69, +-6.78,2.47, +-6.58,2.39, +-6.58,2.39, +-6.42,2.34, +-5.29,2.85, +-5.13,3.14, +-4.43,3.57, +-3.53,3.59, +-3.01,4.04, +-1.97,3.57, +-1.4,3.84, +-0.82,3.88, +-0.51,2.87, +-0.51,2.87, +-0.98,3.55, +-1.72,3.69, +-3.16,2.52, +-3.98,0.39, +-3.87,-2.24, +-4.31,-4.18, +-5.06,-6.15, +-3.55,-4.84, +-2.37,-4.1, +-2.53,-4.38, +-2.69,-4.66, +-3.01,-5.22, +-2.85,-5.29, +-2.99,-5.59, +-2.88,-6.35, +-2.64,-7.26, +-3.21,-8.83, +-3.32,-9.13, +-4.98,-11.99, +-3.1,-8.52, +-3.39,-9.32, +-3.39,-9.32, +-3.39,-9.32, +-3.39,-9.32, +-3.34,-9.16, +-3.57,-8.28, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.88,-8.45, +-3.72,-10.23, +-4.12,-11.33, +-4.12,-11.33, +-3.9,-10.72, +-4.08,-8.35, +-3.71,-7.03, +-2.73,-7.49, +-2.69,-7.4, +-2.7,-7.41, +-2,-7.52, +-1.38,-7.85, +-1.38,-7.85, +-0.23,-7.95, +-0,-7.97, +-0,-8.11, +-0,-8.19, +-0,-8.83, +0.39,-8.91, +0.56,-9.43, +0.25,-9.89, +0.57,-9.86, +1.79,-10.15, +1.92,-10.91, +1.92,-10.91, +2.23,-10.83, +3.6,-10.46, +2.55,-10.75, +2.75,-10.69, +3.33,-11.2, +3.22,-11.57, +3.78,-11.42, +2.88,-11.99, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +2.09,-11.87, +3.11,-11.6, +4.2,-11.53, +4.39,-12.07, +4.45,-12.24, +4.5,-12.37, +4.73,-13, +4.79,-13.15, +4.79,-13.15, +4.75,-13.04, +4.79,-13.15, +4.73,-13, +4.79,-13.15, +5.96,-12.49, +6.83,-10.4, +7.8,-9.3, +7.1,-8.46, +7.43,-8.86, +7.75,-9.23, +7.85,-9.36, +8.29,-9.88, +7.85,-9.36, +7.3,-8.7, +7.12,-8.49, +6.92,-7.71, +8.17,-7.11, +8.49,-7.12, +8.61,-7.23, +8.19,-6.87, +8.15,-6.84, +8.49,-7.12, +8.19,-6.87, +7.59,-6.37, +7.53,-6.32, +6.85,-5.75, +5.73,-6.11, +5.75,-6.85, +5.36,-6.38, +5.39,-6.07, +6.85,-5.75, +6.97,-5.85, +8.59,-4.96, +8.92,-5.15, +8.87,-5.12, +9.49,-5.3, +10.35,-5.98, +8.49,-7.12, +8.49,-7.12, +7.71,-6.47, +8.49,-7.12, +9.23,-7.75, +8.49,-7.12, +8.37,-6.28, +9.31,-5.37, +1.67,-0.96, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.29,2.34, +-12.67,2.99, +-12.24,4.45, +-12.04,4.38, +-11.43,4.16, +-11.33,4.12, +-10.27,5.56, +-9.74,5.62, +-9.6,5.54, +-9.48,5.73, +-8.7,7.13, +-9.02,7.57, +-8.49,7.12, +-8.49,7.12, +-8.46,7.15, +-7.75,7.9, +-6.82,8.12, +-6.7,7.99, +-6.5,7.74, +-6.37,7.59, +-6.37,7.59, +-5.9,7.95, +-5.38,8.31, +-4.95,8.57, +-4.62,8.01, +-4.47,7.74, +-4.47,7.74, +-4.46,7.73, +-4.45,7.72, +-4.24,7.35, +-3.98,6.9, +-3.61,6.25, +-4.13,6.13, +-4.64,5.52, +-4.5,5.36, +-4.53,5.39, +-5.11,5.5, +-5.36,4.5, +-5.36,4.5, +-5.95,3.67, +-4.98,0.88, +-6.07,1.07, +-6.79,1.61, +-7.57,2.43, +-7.59,2.76, +-8.4,3.06, +-8.4,3.06, +-8.24,2.82, +-8.06,2.26, +-7.73,1.83, +-7.73,1.83, +-7.58,1.78, +-6.67,2.06, +-6.58,2.39, +-6.58,2.39, +-5.82,2.24, +-5.22,3.01, +-4.7,3.19, +-3.66,3.46, +-3.04,4.02, +-1.72,3.69, +-1.17,3.9, +-0.57,3.25, +-0.13,2.9, +-0.17,2.9, +-0.45,3.52, +-1.01,3.94, +-2.06,3.41, +-3.7,-0.35, +-2.5,-3.12, +-2.14,-3.47, +-2.04,-3.54, +-1.72,-3.69, +-1.72,-3.69, +-1.89,-4.54, +-1.99,-4.63, +-1.93,-5.01, +-2.06,-5.66, +-2.19,-6.03, +-2.39,-6.58, +-2.73,-7.49, +-2.95,-8.1, +-3.06,-8.4, +-2.28,-9.61, +-3.06,-8.4, +-2.63,-8.74, +-1.67,-9.44, +-1.85,-9.39, +-2.72,-9.05, +-3.1,-8.5, +-3.06,-8.4, +-4.24,-7.85, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.69,-8.12, +-4.26,-9.48, +-3.9,-10.72, +-4.09,-11.22, +-3.83,-10.53, +-4.24,-8.63, +-4.07,-7.04, +-2.94,-7.39, +-2.73,-7.49, +-2.73,-7.49, +-1.74,-7.42, +-1.38,-7.85, +-1.38,-7.85, +-1.08,-7.88, +-0.15,-7.96, +-0,-7.97, +-0,-7.97, +0.19,-8.32, +0.26,-8.92, +0.78,-8.87, +0.65,-9.86, +1.34,-9.8, +1.86,-10.53, +1.92,-10.91, +1.92,-10.91, +2.19,-10.84, +2.46,-10.77, +2.86,-10.66, +2.23,-10.83, +3.76,-11.26, +4.12,-11.33, +4.31,-11.83, +3.51,-12.09, +2.09,-11.87, +2.37,-12.02, +3.16,-11.75, +4.12,-11.33, +4.12,-11.33, +4.18,-11.48, +4.29,-11.78, +4.45,-12.24, +4.54,-12.47, +4.75,-13.05, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +6.18,-12.32, +6.3,-10.39, +7.66,-9.13, +6.94,-8.27, +6.79,-8.09, +7.11,-8.47, +7.57,-9.02, +8.28,-9.87, +7.85,-9.36, +7.54,-8.98, +7.66,-7.68, +7.71,-6.47, +7.72,-6.48, +7.99,-6.71, +8.19,-6.87, +8.29,-6.96, +8.04,-6.75, +8.49,-7.12, +8.19,-6.87, +7.59,-6.37, +7.04,-5.9, +6.85,-5.75, +5.6,-6.31, +5.4,-6.44, +5.12,-6.11, +5.57,-6.34, +6.85,-5.75, +7.1,-5.96, +7.85,-4.53, +8.95,-5.17, +9.43,-5.44, +10,-5.77, +10.44,-6.03, +9.09,-7.02, +8.82,-7.4, +8.8,-7.39, +8.34,-7, +9.11,-7.64, +8.67,-7.28, +8.49,-7.12, +6.23,-3.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.04,2.5, +-12.72,4.16, +-12.24,4.45, +-12.24,4.45, +-11.92,4.34, +-11.33,4.12, +-10.6,5.72, +-10.21,5.9, +-9.71,5.6, +-9.55,5.66, +-8.58,7, +-8.55,7.18, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-8.03,7.52, +-6.64,7.92, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.33,7.63, +-5.39,8.26, +-4.67,8.08, +-4.48,7.76, +-4.47,7.74, +-4.47,7.74, +-4.33,7.49, +-4.14,7.16, +-4,6.92, +-3.68,6.37, +-3.5,6.06, +-4.42,6.01, +-4.54,5.41, +-4.4,5.24, +-4.5,5.36, +-4.8,5.21, +-5.36,4.5, +-5.4,4.45, +-5.95,3.67, +-5.3,0.93, +-6.25,1.1, +-6.89,1.22, +-7.85,1.38, +-8.17,1.44, +-8.58,2.41, +-8.4,3.06, +-8.71,3.17, +-9.14,2.19, +-7.96,1.4, +-7.85,1.38, +-7.85,1.38, +-7.1,1.25, +-6.86,1.35, +-6.61,2.26, +-6.35,2.31, +-5.29,2.85, +-4.77,3.09, +-3.64,3.48, +-2.77,3.82, +-1.61,3.74, +-0.94,3.96, +-0.25,3.02, +0.11,2.91, +0.11,2.91, +0.07,2.91, +0,4.08, +-0.12,3.84, +-0.98,-1.99, +-0.6,-4.01, +-1.17,-3.9, +-1.17,-3.9, +-1.17,-3.9, +-1.17,-3.9, +-1.32,-3.86, +-1.6,-4.41, +-1.73,-4.75, +-1.97,-5.4, +-2.2,-6.05, +-2.39,-6.58, +-2.73,-7.49, +-2.91,-8, +-3.06,-8.4, +-2.61,-8.3, +-2.56,-8.54, +-1.55,-8.81, +-1.55,-8.81, +-1.55,-8.81, +-1.55,-8.81, +-2.81,-8.47, +-3.06,-8.4, +-3.2,-8.34, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.7,-8.71, +-3.79,-10.41, +-3.79,-10.41, +-3.79,-10.41, +-4.43,-8.52, +-4.22,-7.62, +-2.94,-7.39, +-2.73,-7.49, +-2.73,-7.49, +-2.5,-7.55, +-1.38,-7.85, +-1.38,-7.85, +-0.69,-7.91, +-0.23,-8.27, +-0,-8.29, +-0,-8.36, +0.66,-8.18, +1.04,-8.85, +1.11,-8.84, +1.72,-9.76, +1.72,-9.76, +1.76,-9.95, +1.92,-10.91, +2.2,-10.64, +2.2,-10.64, +1.92,-10.91, +2.36,-10.91, +3.28,-10.88, +4.12,-11.33, +3.78,-11.42, +4.23,-11.63, +3.27,-12.05, +2.09,-11.87, +3.78,-11.42, +4.12,-11.33, +4.16,-11.43, +4.16,-11.43, +4.37,-11.33, +4.33,-11.9, +4.45,-12.24, +4.4,-12.09, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.75,-13.05, +4.71,-12.95, +5.11,-12.33, +6.76,-11.7, +6.15,-10.65, +6.41,-9.77, +6.87,-8.19, +6.37,-7.59, +6.98,-8.31, +7.64,-9.11, +7.68,-9.15, +7.75,-9.23, +7.75,-9.23, +8.19,-6.87, +7.59,-6.37, +7.62,-6.39, +7.89,-6.62, +8.74,-7.33, +8.58,-7.2, +8.49,-7.12, +8.65,-7.26, +8.26,-6.93, +7.22,-6.06, +6.85,-5.75, +6.38,-6.44, +5.54,-6.6, +5.3,-6.32, +5.66,-6.74, +5.75,-6.85, +6.61,-5.99, +6.79,-5.7, +7.18,-4.15, +9.12,-5.26, +10.02,-5.78, +10.44,-6.03, +10.44,-6.03, +9.83,-6.89, +9.23,-7.75, +9.23,-7.75, +8.65,-7.26, +8.36,-7.02, +9.05,-7.59, +8.49,-7.12, +3.34,-1.93, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.36,2.59, +-12.66,4.36, +-12.24,4.45, +-12.24,4.45, +-11.72,4.26, +-11.43,4.16, +-10.86,5.82, +-10.44,6.03, +-10.38,5.99, +-10.05,5.96, +-8.63,6.98, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-7.53,7.72, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.36,7.61, +-5.51,8.05, +-4.47,7.74, +-4.47,7.74, +-4.46,7.72, +-4.31,7.46, +-4.04,7, +-3.98,6.9, +-3.98,6.9, +-3.76,6.51, +-3.51,6.05, +-4.09,5.66, +-4.39,5.24, +-4.21,5.01, +-4.5,5.36, +-4.71,5.17, +-5.36,4.5, +-5.64,4.13, +-6.05,3.51, +-5.66,1.81, +-6.25,1.1, +-7.21,1.27, +-7.85,1.38, +-8.33,1.47, +-8.81,1.55, +-8.76,1.72, +-9.51,2.24, +-9.45,2.05, +-9.23,1.63, +-7.85,1.38, +-7.85,1.38, +-7.53,1.33, +-6.89,1.22, +-6.68,2, +-6.37,3.56, +-5.7,4.16, +-4.41,3.15, +-3.53,3.59, +-2.82,3.91, +-1.61,3.74, +-0.79,4, +0.08,2.91, +0.51,2.87, +0.51,2.87, +0.69,2.82, +1.13,3.63, +2.03,2.6, +2.09,-1.64, +0.52,-3.36, +-0.1,-3.8, +-0.51,-4.04, +-0.71,-4.02, +-0.71,-4.02, +-0.71,-4.02, +-1.05,-4.07, +-1.56,-4.7, +-1.83,-5.02, +-2.1,-5.76, +-2.39,-6.58, +-2.65,-7.29, +-2.84,-7.79, +-2.56,-8.54, +-2.73,-7.49, +-1.86,-7.89, +-1.55,-8.81, +-1.55,-8.81, +-1.55,-8.81, +-1.55,-8.81, +-1.89,-8.72, +-3.06,-8.4, +-3.06,-8.4, +-3.76,-8.07, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.47,-7.74, +-4.96,-8.59, +-4.26,-9.48, +-3.98,-10.32, +-4.74,-9.65, +-4.88,-8.45, +-4.12,-7.91, +-2.73,-7.49, +-2.73,-7.49, +-2.73,-7.49, +-2.73,-7.49, +-1.68,-7.77, +-1.38,-7.85, +-0.94,-8.21, +-0,-8.94, +0,-8.94, +0.19,-8.93, +1.27,-8.67, +1.04,-8.85, +1.21,-9.08, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.85,-10.48, +1.92,-10.91, +1.92,-10.91, +2.06,-11.71, +3,-11.13, +4.01,-11.02, +4.12,-11.33, +4.12,-11.33, +4.24,-11.66, +3.51,-12.09, +2.77,-11.69, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +5.08,-11.77, +4.67,-11.42, +4.16,-11.43, +4.69,-11.53, +4.23,-11.63, +4.69,-12.89, +4.79,-13.15, +4.79,-13.15, +4.62,-12.7, +4.57,-12.54, +5.14,-11.92, +6.65,-11.52, +6.61,-11.45, +6.47,-11.2, +6.46,-10.14, +7.23,-8.61, +6.87,-8.19, +7.23,-8.61, +7.67,-9.14, +7.75,-9.23, +8.06,-9.6, +7.82,-9.32, +8.71,-7.31, +8.08,-6.78, +7.89,-6.62, +8.34,-7, +8.98,-7.54, +9.23,-7.75, +9.11,-7.64, +8.61,-7.23, +8.36,-7.02, +7.16,-6.01, +6.36,-6.24, +6.1,-7.28, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +6.67,-5.93, +6.85,-5.75, +7.25,-5.18, +9.78,-5.65, +10.44,-6.03, +10.33,-5.97, +10.44,-6.03, +10.44,-6.03, +9.23,-7.75, +9.23,-7.75, +8.61,-7.23, +8.09,-6.79, +8.89,-7.46, +9.02,-7.57, +3.2,-1.85, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.43,3.9, +-13.15,4.7, +-12.81,4.66, +-12.28,4.47, +-11.89,4.33, +-12.04,4.38, +-11.32,5.93, +-10.44,6.03, +-10.44,6.03, +-10.41,6.03, +-9.67,6.43, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-7.99,7.18, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-5,8.26, +-4.47,7.74, +-4.47,7.74, +-4.32,7.48, +-3.99,6.92, +-4.02,6.96, +-3.98,6.9, +-3.98,6.9, +-3.76,6.51, +-3.62,5.99, +-4.27,5.54, +-4.4,5.27, +-4.41,5.26, +-4.5,5.36, +-4.69,5.19, +-5.04,4.21, +-5.1,3.62, +-5.89,3.4, +-5.82,1.47, +-6.57,1.16, +-7.21,1.27, +-7.85,1.38, +-8.49,1.5, +-8.81,1.55, +-9.13,1.61, +-9.76,1.72, +-9.64,1.7, +-9.31,1.38, +-7.85,1.38, +-7.85,1.38, +-7.64,1.35, +-6.89,1.22, +-6.58,2.59, +-5.67,5.03, +-5.31,3.59, +-4.66,3.39, +-3.56,3.56, +-2.77,4.21, +-1.28,3.87, +-0.3,4.06, +0.28,2.89, +0.67,2.83, +0.89,2.77, +1.25,2.62, +1.82,2.25, +2.61,1.18, +2.8,-1.56, +1.55,-3.1, +0.82,-3.99, +0.18,-4.07, +-0.08,-3.82, +-0.24,-3.93, +-0.58,-4.03, +-0.71,-4.02, +-0.88,-4.35, +-1.59,-4.79, +-2.01,-5.51, +-2.28,-6.27, +-2.5,-6.88, +-2.73,-7.49, +-2.62,-7.18, +-2.73,-7.49, +-1.53,-7.81, +-1.44,-8.17, +-1.55,-8.81, +-1.55,-8.81, +-1.55,-8.81, +-1.77,-8.75, +-3.06,-8.4, +-3.06,-8.4, +-3.22,-8.33, +-4.16,-7.89, +-4.47,-7.74, +-4.47,-7.74, +-4.71,-8.16, +-4.96,-8.59, +-4.96,-8.59, +-5.25,-9.09, +-4.96,-8.59, +-5.01,-8.68, +-4.62,-8.31, +-3.2,-8.22, +-2.76,-7.59, +-2.73,-7.49, +-2.73,-7.49, +-1.83,-7.73, +-1.41,-8.01, +-0.52,-8.9, +-0,-8.94, +-0,-8.94, +0.52,-8.9, +1.21,-8.84, +1.65,-9.35, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.92,-10.89, +2,-11.35, +2.32,-11.81, +3.9,-11.39, +4.07,-11.19, +4.07,-11.17, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.29,-11.78, +4.12,-11.33, +4.2,-11.53, +4.34,-11.93, +4.41,-12.12, +4.45,-12.24, +4.41,-12.12, +4.49,-11.33, +4.29,-11.78, +4.34,-11.93, +4.58,-12.58, +4.75,-13.06, +4.79,-13.15, +5.3,-12.78, +6.02,-12.04, +6.51,-11.28, +6.67,-11.56, +6.57,-11.38, +6.51,-11.28, +6.9,-11.35, +7.14,-10.97, +7.32,-10.32, +7.75,-9.23, +7.75,-9.23, +8.27,-9.85, +8.16,-9.73, +8.37,-9.98, +9.03,-8.29, +8.49,-7.12, +8.82,-7.4, +9.15,-7.68, +9.32,-7.82, +9.23,-7.75, +9.23,-7.75, +8.49,-7.12, +8.49,-7.12, +7.32,-6.14, +6.43,-6.85, +6.22,-7.41, +6.16,-7.35, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +6.48,-6.12, +6.85,-5.75, +7.12,-5.64, +9.74,-5.62, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +9.99,-6.67, +9.31,-7.36, +8.49,-7.12, +7.94,-6.38, +8.49,-7.12, +8.49,-7.12, +2.45,-2.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.24,4.51, +-13.15,4.79, +-13.09,4.76, +-12.32,4.48, +-12.38,4.51, +-12.26,4.46, +-10.87,5.66, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.12,6.38, +-8.83,7.14, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-7.63,6.78, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.32,7.53, +-5.85,7.69, +-4.74,8.03, +-4.47,7.74, +-4.47,7.74, +-4.37,7.58, +-4.26,7.39, +-4.23,7.32, +-3.98,6.9, +-3.97,6.88, +-3.71,6.43, +-3.51,6.05, +-3.81,5.86, +-4.24,5.2, +-4.3,5.13, +-4.5,5.36, +-4.5,5.06, +-4.89,4.05, +-5.34,3.49, +-5.47,3.1, +-5.84,0.93, +-6.62,0.66, +-7.42,1.31, +-7.85,1.38, +-8.81,1.55, +-8.81,1.55, +-9.44,1.67, +-9.76,1.72, +-9.29,0.74, +-9.03,0.19, +-7.92,0.62, +-7.85,1.38, +-7.66,1.35, +-6.89,1.22, +-6.09,3.84, +-5.12,6.11, +-5.14,5.59, +-4.38,3.48, +-3.46,3.66, +-2.77,4.21, +-1.17,3.9, +-0.16,4.07, +0.56,2.86, +0.94,2.76, +1.15,2.67, +1.6,2.43, +2.27,1.8, +2.86,0.44, +2.71,-1.27, +1.78,-2.29, +1.28,-3.03, +0.87,-3.18, +0.45,-2.88, +0.06,-3.35, +-0,-4.08, +-0.47,-4.04, +-0.71,-4.02, +-1.12,-4.69, +-1.88,-5.16, +-2.17,-5.97, +-2.43,-6.68, +-2.58,-7.08, +-2.39,-6.58, +-2.67,-7.34, +-1.83,-7.73, +-1.38,-7.85, +-1.49,-8.45, +-1.55,-8.81, +-1.55,-8.81, +-2.31,-8.6, +-3.06,-8.4, +-3.06,-8.4, +-3.06,-8.4, +-3.06,-8.4, +-3.78,-8.78, +-4.51,-8.79, +-4.78,-8.67, +-4.43,-8.83, +-4.26,-9.48, +-4.76,-9.1, +-4.96,-8.59, +-5.12,-9.42, +-4.4,-9.48, +-3.34,-9.16, +-3.06,-8.4, +-2.87,-7.9, +-2.84,-7.79, +-1.94,-8.14, +-1.5,-8.49, +-0.89,-8.86, +-0.19,-8.93, +-0,-8.94, +1.06,-9.01, +1.72,-9.76, +1.82,-10.34, +1.86,-10.53, +1.79,-10.15, +1.79,-10.15, +1.9,-10.75, +1.99,-11.27, +2.09,-11.87, +4.12,-11.33, +3.9,-10.72, +4.12,-11.33, +4.01,-11.02, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.18,-11.48, +4.12,-11.33, +4.26,-11.72, +4.45,-12.24, +4.34,-11.93, +4.37,-11.33, +4.29,-11.78, +4.45,-12.24, +4.57,-12.54, +4.79,-12.35, +6.26,-12.46, +6.58,-11.4, +6.4,-11.09, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.84,-11.84, +7,-12.12, +8.23,-11.06, +8.58,-10.23, +8.23,-9.81, +8.86,-10.56, +8.14,-9.7, +8.37,-9.98, +8.57,-8.58, +8.36,-7.93, +8.98,-7.99, +9.23,-7.75, +9.23,-7.75, +8.79,-7.37, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +7.52,-6.31, +6.1,-6.5, +6.27,-7.47, +6.27,-7.47, +5.75,-6.85, +5.75,-6.85, +6.03,-7.18, +5.75,-6.85, +6.91,-6.08, +7.51,-5.93, +9.6,-5.54, +11.1,-4.6, +10.73,-5.39, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +9.65,-6.31, +8.19,-6.87, +7.52,-6.31, +8.49,-7.12, +8.61,-7.23, +2.12,-1.78, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.3,4.84, +-13.15,4.79, +-12.81,4.66, +-12.26,4.46, +-12.71,4.62, +-12.36,4.5, +-11.43,4.74, +-10.47,5.96, +-10.44,6.03, +-10.44,6.03, +-10.26,6.27, +-8.91,7.06, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-8.03,7.04, +-6.66,7.94, +-6.45,7.69, +-6.37,7.59, +-6.37,7.59, +-6.3,7.51, +-5.38,7.45, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.23,7.33, +-3.97,6.88, +-3.77,6.53, +-3.51,6.08, +-3.6,6, +-4.35,5.48, +-4.12,4.91, +-4,4.77, +-4.27,5.09, +-4.07,4.56, +-4.63,3.86, +-5.18,3.43, +-5.25,2.96, +-5.76,1.16, +-7,0, +-7.62,0.79, +-7.85,1.38, +-8.81,1.55, +-8.81,1.55, +-9.44,1.67, +-9.76,1.72, +-9.34,1.02, +-8.79,0.62, +-8.17,1.02, +-7.85,1.38, +-7.85,1.38, +-6.79,1.61, +-5.49,5.18, +-5.12,6.11, +-5.07,5.86, +-4.62,3.87, +-3.17,3.93, +-2.26,4.5, +-0.99,4.11, +0.12,4.07, +0.74,3.21, +1,2.74, +1.3,2.6, +1.82,2.26, +2.44,1.54, +2.84,0.5, +2.74,-0.94, +2.21,-1.86, +1.6,-2.43, +1.72,-4.3, +0.81,-2.79, +0.51,-2.87, +0.23,-3.54, +-0,-4.08, +-0.47,-4.04, +-0.77,-4.34, +-1.59,-4.79, +-2.06,-5.66, +-2.39,-6.58, +-2.56,-7.03, +-2.39,-6.58, +-2.43,-6.68, +-2.39,-7.47, +-1.38,-7.85, +-1.38,-7.85, +-1.53,-8.69, +-1.55,-8.81, +-2.81,-8.47, +-3.06,-8.4, +-3.06,-8.4, +-3.06,-8.4, +-3.24,-8.91, +-3.39,-9.32, +-3.39,-9.32, +-3.46,-9.5, +-3.72,-10.23, +-3.79,-10.41, +-3.59,-9.86, +-4.78,-8.67, +-4.9,-8.49, +-3.79,-10.41, +-3.57,-9.8, +-3.35,-9.2, +-3.11,-8.55, +-3.06,-8.4, +-1.8,-8.74, +-1.55,-8.81, +-0.86,-8.87, +-0,-9.38, +0.19,-9.47, +1.53,-9.78, +1.9,-10.75, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.99,-11.26, +2.28,-11.71, +3.11,-11.26, +4.12,-11.33, +4.07,-11.17, +4.12,-11.33, +4.07,-11.17, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.45,-12.24, +4.34,-11.93, +4.76,-11.03, +4.75,-11.63, +5.37,-11.81, +6.15,-11.56, +6.16,-11.84, +6.18,-12.32, +6.51,-11.28, +6.11,-10.58, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.67,-11.56, +6.94,-12.03, +7.33,-11.89, +8.91,-10.62, +9,-10.72, +8.66,-10.95, +9,-10.72, +7.84,-9.34, +7.69,-9.16, +7.62,-9.08, +7.75,-9.23, +8.99,-8.45, +9.6,-8.06, +9.33,-7.82, +8.74,-7.33, +8.49,-7.12, +8.49,-7.12, +7.67,-6.44, +7.41,-6.22, +6.43,-6.85, +6.37,-7.59, +5.75,-6.85, +5.75,-6.85, +5.85,-6.97, +6.37,-7.59, +5.96,-7.1, +7.33,-7.18, +8.39,-6.25, +9.6,-5.54, +11.33,-4.12, +11.33,-4.12, +11.03,-4.76, +10.44,-6.03, +10.12,-5.84, +9.37,-5.41, +7.59,-6.37, +7.22,-6.06, +8.34,-7, +8.49,-7.12, +2.12,-1.78, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.61,4.95, +-13.21,4.81, +-12.85,4.68, +-12.3,4.48, +-12.29,4.47, +-12.73,4.63, +-11.45,5.7, +-10.49,6.03, +-10.44,6.03, +-10.44,6.03, +-10.16,6.15, +-8.71,6.86, +-8.49,7.12, +-8.49,7.12, +-8.49,7.12, +-7.85,7.39, +-6.75,8.04, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-6.17,7.75, +-5.11,8.17, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-4.45,7.71, +-4.33,7.5, +-4.03,6.98, +-3.8,6.58, +-3.51,6.08, +-3.5,6.06, +-3.59,5.97, +-4.3,5.39, +-4.3,5.12, +-3.87,4.62, +-3.9,4.65, +-4.38,4.13, +-4.62,3.87, +-5.14,3.14, +-5.31,2.84, +-5.88,0.75, +-7,0, +-7.95,0.17, +-8.15,0.46, +-8.85,1.04, +-8.81,1.55, +-9.5,1.68, +-9.76,1.72, +-9.28,1.64, +-8.83,1.29, +-8.33,1.47, +-7.85,1.38, +-7.85,1.38, +-6.82,1.89, +-6.2,3.34, +-5.23,5.82, +-5.03,4.08, +-4.24,4.24, +-3.22,4.29, +-2.13,4.56, +-0.78,4.32, +0.24,4.06, +0.85,3.18, +1.05,2.72, +1.5,2.49, +1.96,2.14, +2.5,1.44, +2.84,0.5, +2.81,-0.75, +2.36,-1.69, +1.9,-2.19, +2.55,-4.2, +2.82,-5.57, +0.99,-2.73, +0.59,-2.85, +0.25,-3.48, +-0,-4.08, +-0.77,-4.34, +-1.26,-4.88, +-2.02,-5.56, +-2.25,-6.17, +-2.39,-6.58, +-2.36,-6.47, +-2.39,-6.58, +-2.45,-6.73, +-2.53,-7.54, +-1.38,-7.85, +-1.59,-8.13, +-2.56,-8.54, +-3.06,-8.4, +-3.06,-8.4, +-3.06,-8.4, +-3.06,-8.4, +-3.34,-9.16, +-3.39,-9.32, +-3.45,-9.47, +-3.75,-10.29, +-3.79,-10.41, +-3.79,-10.41, +-3.63,-9.97, +-3.52,-9.68, +-3.59,-9.86, +-3.79,-10.41, +-3.72,-10.23, +-3.45,-9.47, +-3.39,-9.32, +-2.02,-9.46, +-1.67,-9.44, +-1.72,-9.76, +-0.43,-9.63, +0.32,-9.76, +0.64,-11.02, +1.92,-10.91, +1.75,-9.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +2.18,-10.97, +2.38,-11.79, +2.63,-11.06, +4.01,-11.02, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.01,-11.02, +3.96,-10.87, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.45,-12.24, +4.45,-12.24, +5.07,-10.88, +5.75,-11.46, +5.83,-11.6, +6.63,-11.49, +6.89,-11.93, +6.62,-11.47, +6.51,-11.28, +6.1,-10.56, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.92,-11.98, +7.89,-11.5, +8.94,-10.66, +8.92,-10.63, +8,-11.42, +9,-10.72, +7.64,-9.11, +7.43,-8.86, +7.43,-8.86, +7.75,-9.23, +9,-9.5, +10.14,-8.51, +9.42,-7.9, +8.98,-7.54, +8.49,-7.12, +8.49,-7.12, +8.04,-6.75, +7.42,-6.55, +6.71,-7.1, +6.3,-7.51, +6.37,-7.59, +6.37,-7.59, +6.16,-7.35, +6.37,-7.59, +6.06,-7.22, +8.03,-7.58, +8.86,-6.59, +10.01,-4.87, +11.21,-4.08, +11.48,-4.18, +11.78,-4.29, +11.21,-4.36, +10.02,-5.38, +8.92,-5.15, +7.35,-6.16, +7.26,-6.3, +7.4,-6.21, +6.92,-5.81, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-13.76,5.01, +-13.15,4.79, +-12.36,4.5, +-12.54,4.56, +-12.83,4.67, +-11.6,5.74, +-10.51,6.07, +-10.44,6.03, +-10.44,6.03, +-10.17,5.87, +-8.81,6.68, +-8.49,7.12, +-8.49,7.12, +-8.44,7.08, +-7.65,7.43, +-6.57,7.83, +-6.37,7.59, +-6.37,7.59, +-6.37,7.59, +-5.8,7.82, +-4.54,7.86, +-4.47,7.74, +-4.44,7.69, +-4.18,7.31, +-3.46,7.5, +-3.68,7.06, +-3.96,6.89, +-3.6,6.23, +-3.5,6.06, +-3.5,6.06, +-3.43,5.95, +-3.54,5.18, +-4.1,4.88, +-3.87,4.62, +-3.87,4.62, +-4.27,4.04, +-4.12,3.46, +-5.04,3.11, +-5.44,2.14, +-6.35,0, +-7.21,0, +-7.97,0, +-8.36,0, +-8.94,0, +-8.86,0.89, +-9.6,1.69, +-9.28,1.64, +-8.81,1.55, +-8.53,1.04, +-8.09,1.11, +-7.81,1.53, +-7.81,1.53, +-6.92,2.52, +-6,3.46, +-5.12,6.11, +-5.04,4.23, +-3.96,4.53, +-3.1,4.9, +-1.72,4.73, +-0.47,4.58, +0.47,4.04, +0.9,3.17, +1.15,2.67, +1.6,2.43, +2.13,1.98, +2.6,1.3, +2.89,0.3, +2.81,-0.73, +2.52,-1.45, +2.22,-1.87, +1.87,-2.23, +2.6,-4.17, +1.38,-2.56, +0.94,-2.75, +0.62,-2.84, +0.19,-3.81, +-0.24,-4.06, +-0.97,-4.95, +-1.89,-5.21, +-2.12,-5.81, +-2.39,-6.58, +-2.74,-7.52, +-2.39,-6.58, +-2.39,-6.58, +-2.69,-7.4, +-2.73,-7.49, +-2.73,-7.49, +-2.89,-7.95, +-3.06,-8.4, +-3.06,-8.4, +-3.06,-8.4, +-3.06,-8.4, +-3.28,-9.01, +-3.39,-9.32, +-3.52,-9.68, +-3.79,-10.41, +-3.79,-10.41, +-3.79,-10.41, +-3.59,-9.86, +-3.66,-10.05, +-3.79,-10.41, +-3.79,-10.41, +-3.79,-10.41, +-3.48,-10.5, +-2.03,-9.88, +-1.72,-9.76, +-1.76,-9.95, +-1.21,-9.81, +-0,-9.91, +-0,-9.91, +1.26,-10.58, +1.5,-10.12, +1.72,-9.76, +1.82,-10.34, +1.92,-10.91, +2.46,-10.77, +3.26,-10.65, +3.9,-10.72, +4.09,-11.22, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.05,-11.12, +4.05,-11.12, +3.9,-10.72, +4.01,-11.02, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.29,-11.78, +4.45,-12.24, +5.33,-11.24, +6.18,-11.2, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.43,-11.14, +6.03,-10.44, +6.2,-10.74, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.59,-11.42, +7.89,-11.3, +8.2,-10.69, +8.28,-10.38, +7.32,-11.1, +8.29,-10.3, +7.12,-8.49, +7.36,-8.77, +7.43,-8.86, +7.33,-8.74, +9.38,-8.74, +9.73,-8.16, +9.15,-7.68, +8.74,-7.33, +8.49,-7.12, +8.49,-7.12, +8.36,-7.02, +7.59,-6.37, +7.39,-6.58, +6.58,-7.39, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +5.92,-7.05, +8.03,-7.58, +8.86,-6.59, +9.8,-5.1, +11.43,-4.16, +12.14,-4.42, +12.24,-4.45, +11.33,-4.12, +10.29,-4.43, +8.75,-5.05, +7.18,-5.49, +6.14,-6.91, +7.5,-6.29, +4.3,-3.61, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-13.94,5.08, +-13.15,4.79, +-12.99,4.73, +-13.07,4.76, +-12.49,5.4, +-11.32,6.4, +-10.81,6.24, +-10.44,6.03, +-10.44,6.03, +-10.27,6.17, +-9.13,7.28, +-8.54,7.16, +-8.49,7.12, +-8.41,7.08, +-7.38,7.41, +-6.53,7.78, +-6.37,7.59, +-6.37,7.59, +-5.75,8.06, +-5.51,7.81, +-4.47,7.74, +-4.47,7.74, +-4.27,7.53, +-3.57,7.11, +-2.86,7.44, +-2.88,7.43, +-3.68,6.55, +-3.52,6.1, +-3.5,6.06, +-3.41,5.91, +-3.16,5.48, +-3.26,5.06, +-3.81,4.67, +-3.87,4.62, +-3.87,4.62, +-3.93,3.67, +-3.91,3.19, +-4.53,2.77, +-5.6,1.79, +-6.51,0, +-7.58,0, +-7.97,0, +-8.62,0, +-8.94,0, +-8.94,0, +-8.82,1.38, +-8.81,1.55, +-8.81,1.55, +-8.14,0.52, +-7.86,1.21, +-7.61,2.28, +-7.49,2.73, +-6.55,3.3, +-5.44,4.39, +-5.44,4.39, +-4.88,4.98, +-3.63,5.05, +-2.59,5.42, +-1.41,5.17, +-0.29,4.87, +0.59,4.03, +1.07,3.39, +1.15,2.67, +1.6,2.43, +2.09,2.02, +2.65,1.16, +2.89,0.25, +2.83,-0.67, +2.63,-1.23, +2.42,-1.61, +2.15,-1.95, +1.95,-2.15, +1.78,-2.3, +1.45,-2.52, +1.13,-2.68, +0.79,-3.8, +0.12,-4.07, +-0.44,-4.69, +-1.46,-5.16, +-2.06,-5.66, +-3.08,-8.46, +-2.88,-7.91, +-2.32,-6.37, +-2.39,-6.58, +-2.45,-6.73, +-2.73,-7.49, +-2.73,-7.49, +-2.73,-7.49, +-2.84,-7.79, +-3.06,-8.4, +-3.06,-8.4, +-3.06,-8.4, +-3.17,-8.71, +-3.39,-9.32, +-3.39,-9.32, +-3.79,-10.41, +-3.79,-10.41, +-3.79,-10.41, +-3.49,-9.59, +-3.52,-9.68, +-3.79,-10.41, +-3.79,-10.41, +-3.13,-10.39, +-2.02,-10.22, +-1.74,-9.89, +-1.74,-9.88, +-1.72,-9.76, +-0.57,-9.86, +-0,-9.91, +0.29,-9.89, +1.43,-9.63, +1.77,-10.05, +1.79,-10.15, +1.72,-9.76, +1.92,-10.91, +2.16,-10.85, +3.22,-10.73, +4.01,-11.02, +4.01,-11.02, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +3.96,-10.87, +3.83,-10.53, +3.84,-10.56, +3.79,-10.41, +3.79,-10.41, +3.84,-10.56, +4.09,-11.22, +4.09,-11.23, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.23,-11.63, +4.92,-11.49, +6.43,-11.14, +6.51,-11.28, +5.86,-11.7, +5.9,-11.8, +6.22,-11.59, +5.57,-12.08, +5.61,-11.34, +6.73,-10.09, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.93,-11.43, +7.86,-10.54, +7.52,-10.77, +7.63,-10.89, +8.37,-9.98, +7.24,-8.62, +7.47,-8.9, +7.59,-9.05, +7.23,-8.61, +9.38,-8.29, +9.23,-7.75, +7.77,-6.52, +8.34,-7, +8.77,-7.36, +8.49,-7.12, +8.49,-7.12, +8.04,-6.75, +7.59,-6.37, +7.29,-6.68, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.41,-7.64, +8.26,-7.35, +8.86,-6.59, +9.6,-5.54, +11.48,-4.18, +12.24,-4.45, +12.14,-4.42, +11.24,-4.09, +10.23,-3.72, +8.45,-4.88, +7.07,-5.43, +7.22,-6.06, +7.54,-6.7, +3.8,-3.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-13.94,5.08, +-13.21,4.81, +-12.95,4.71, +-12.74,5.06, +-11.28,6.51, +-11.28,6.51, +-10.97,6.33, +-10.47,6.04, +-10.44,6.03, +-9.93,6.78, +-9.15,7.68, +-8.65,7.26, +-8.49,7.12, +-7.97,7.64, +-6.95,8.16, +-6.37,7.59, +-6.37,7.59, +-6.33,7.54, +-5.38,7.75, +-4.5,7.8, +-4.46,7.73, +-4.22,7.31, +-3.52,7.3, +-2.73,7.49, +-2.73,7.49, +-2.75,7.41, +-2.95,6.42, +-3.33,6.15, +-3.36,5.87, +-3.07,5.31, +-3.03,5.24, +-3.01,5.22, +-3.37,4.99, +-3.86,4.62, +-3.87,4.62, +-3.87,3.67, +-4.19,2.88, +-4.81,2.48, +-5.74,0.91, +-7,0, +-7.97,0, +-7.97,0, +-8.62,0, +-8.94,0, +-8.94,0, +-8.87,0.86, +-8.81,1.55, +-8.81,1.55, +-6.89,1.22, +-7.54,2.53, +-7.36,3.01, +-7.16,3.43, +-6.86,3.76, +-5.95,4.59, +-5.15,4.71, +-4.33,5.48, +-3.42,5.92, +-1.89,5.71, +-0.99,5.49, +0.1,5.05, +0.75,4.23, +1.4,3.84, +1.22,2.76, +1.58,2.44, +2.11,1.99, +2.6,1.3, +2.87,0.39, +2.89,-0.34, +2.78,-0.83, +2.6,-1.3, +2.42,-1.61, +2.23,-1.87, +1.93,-2.17, +1.8,-2.28, +1.54,-2.97, +1.32,-3.86, +0.71,-4.01, +-0.02,-4.71, +-0.78,-4.99, +-1.89,-5.71, +-2.16,-5.92, +-2.45,-6.73, +-2.27,-6.23, +-2.39,-6.58, +-2.39,-6.58, +-2.62,-7.18, +-2.73,-7.49, +-2.73,-7.49, +-2.73,-7.49, +-2.78,-7.64, +-3.01,-8.27, +-2.89,-8.45, +-2.39,-8.58, +-2.17,-9.34, +-2.39,-9.58, +-2.69,-9.9, +-3.31,-9.94, +-3.52,-9.68, +-3.39,-9.32, +-3.11,-9.66, +-3.48,-9.56, +-3.61,-9.92, +-2.09,-9.66, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-1.41,-9.47, +-0,-9.48, +-0,-9.16, +-0,-9.16, +0,-8.94, +0.57,-9.86, +1.64,-9.28, +1.86,-10.53, +1.85,-10.48, +2.96,-10.63, +3.79,-10.41, +3.79,-10.41, +4.08,-11.21, +4.12,-11.33, +4.12,-11.33, +4.01,-11.02, +4.04,-11.1, +3.83,-10.51, +3.79,-10.41, +3.79,-10.41, +4.03,-11.06, +3.96,-10.87, +3.9,-10.72, +4.08,-11.21, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +5.89,-10.2, +6.23,-10.8, +6.51,-11.28, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +5.28,-12.07, +7.26,-9.58, +6.64,-10.8, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.35,-11, +6.72,-11.13, +7.52,-10.72, +8.17,-10.32, +8.37,-9.98, +8.76,-10.44, +9,-10.72, +9.16,-8.6, +8.74,-8.24, +9.5,-7.97, +8.68,-7.28, +6.97,-5.85, +7.89,-6.62, +8.61,-7.23, +9.23,-7.75, +8.49,-7.12, +8.49,-7.12, +7.69,-6.46, +7.59,-6.37, +7.19,-6.78, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.75,-8.04, +8.09,-6.79, +8.65,-6.9, +9.6,-5.54, +10.9,-4.65, +12.24,-4.45, +11.9,-4.33, +10.87,-3.96, +9.68,-3.52, +8.1,-4.48, +7.28,-5.35, +7.47,-6.27, +7.74,-6.5, +4.02,-3.37, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-13.79,5.02, +-13.15,4.79, +-13.09,4.76, +-11.98,6.1, +-11.38,6.57, +-11.28,6.51, +-11.19,6.46, +-10.49,6.06, +-10.43,6.05, +-9.88,6.86, +-9.13,7.66, +-8.49,7.12, +-8.49,7.12, +-7.69,7.86, +-6.54,7.8, +-6.37,7.59, +-6.37,7.59, +-6.1,7.56, +-4.59,7.67, +-4.47,7.74, +-4.43,7.67, +-4.03,6.99, +-3.03,7.36, +-2.73,7.49, +-2.73,7.49, +-2.68,7.37, +-2.43,6.68, +-2.49,6.52, +-2.65,5.89, +-3.32,5.79, +-3.17,5.48, +-3.01,5.22, +-3.39,4.9, +-3.74,4.5, +-3.73,4.03, +-3.64,3.09, +-4.21,2.73, +-4.97,2.35, +-6.16,0.51, +-7.21,0, +-7.97,0, +-7.97,0, +-8.83,0, +-8.94,0, +-8.94,0, +-8.46,0, +-8.81,1.55, +-7.11,1.14, +-6.85,1.36, +-7.42,2.87, +-6.9,3.98, +-6.9,3.98, +-6.77,4.17, +-5.82,5.18, +-4.59,5.47, +-3.72,5.9, +-2.76,6.4, +-1.68,5.99, +-0.69,5.84, +-0.46,5.14, +0.98,4.38, +1.4,3.84, +1.26,2.62, +1.6,2.43, +2.03,2.07, +2.42,1.59, +2.78,0.83, +2.9,0, +2.87,-0.51, +2.76,-0.92, +2.62,-1.26, +2.4,-1.64, +2.19,-1.91, +1.91,-2.19, +2.3,-3.35, +1.86,-3.62, +1.28,-3.87, +0.73,-4.99, +-0.15,-5.18, +-1.25,-5.88, +-1.86,-5.11, +-2.06,-5.66, +-2.06,-5.66, +-2.39,-6.58, +-2.39,-6.58, +-2.39,-6.58, +-2.63,-7.23, +-2.73,-7.49, +-2.73,-7.49, +-2.73,-7.49, +-2.13,-7.65, +-1.52,-8.65, +-1.55,-8.81, +-1.58,-8.94, +-1.7,-9.63, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-1.72,-9.76, +-1.69,-9.6, +-3.08,-9.23, +-3.39,-9.32, +-2.19,-9.3, +-1.7,-9.67, +-1.71,-9.68, +-1.61,-9.13, +-0.69,-9.3, +-0,-8.94, +-0,-8.94, +-0,-9.27, +0,-9.22, +0.86,-8.97, +0.75,-9.74, +1.82,-10.34, +1.79,-10.15, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.88,-10.67, +4.09,-11.22, +4.12,-11.33, +3.99,-10.96, +3.93,-10.8, +3.79,-10.41, +3.79,-10.41, +3.84,-10.56, +3.79,-10.41, +3.86,-10.61, +4,-10.99, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +5.66,-9.81, +5.99,-10.37, +6.35,-11, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +5.2,-12.25, +5.61,-11.34, +6.02,-11.51, +6.51,-11.28, +6.51,-11.28, +6.15,-10.65, +6.11,-10.58, +6.51,-11.28, +7.75,-10.75, +8.44,-10.06, +8.68,-10.35, +9,-10.72, +8.89,-10.6, +8.93,-8.51, +9.23,-7.75, +9.23,-7.75, +8.98,-7.54, +8.47,-7.11, +8.59,-7.21, +9.13,-7.66, +9.34,-7.84, +8.49,-7.12, +8.49,-7.12, +7.85,-6.59, +7.59,-6.37, +7.59,-6.37, +6.98,-6.98, +6.37,-7.59, +6.37,-7.59, +6.62,-7.89, +7.74,-6.5, +8.59,-6.98, +9.47,-5.72, +10.75,-4.59, +12.24,-4.45, +12.01,-4.37, +10.72,-3.9, +9.16,-3.34, +7.98,-4.21, +8.16,-5.37, +7.59,-6.37, +7.59,-6.37, +7.51,-6.87, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-13.78,5.02, +-13.15,4.79, +-13.15,4.79, +-12.38,6.49, +-11.66,6.73, +-11.28,6.51, +-10.83,6.25, +-10.44,6.03, +-10.37,6.15, +-9.35,7.6, +-9.16,7.68, +-8.49,7.12, +-8.49,7.12, +-7.76,7.78, +-6.48,7.73, +-6.37,7.59, +-6.37,7.59, +-5.8,7.27, +-4.53,7.71, +-4.44,7.69, +-4.12,7.16, +-3.58,7.13, +-2.95,7.39, +-2.73,7.49, +-2.61,7.53, +-2.2,7.11, +-2.39,6.58, +-2.37,6.51, +-2.16,5.69, +-3,5.42, +-3.1,5.37, +-2.89,5, +-2.92,4.72, +-3.15,3.95, +-3.22,3.81, +-3.16,2.78, +-3.93,2.58, +-4.74,2.28, +-6.3,0.64, +-7.65,0, +-7.97,0, +-8.11,0, +-8.94,0, +-8.94,0, +-8.94,0, +-8.91,0.35, +-7.66,1.06, +-5.93,1.05, +-6.36,1.94, +-7.29,3.15, +-6.9,3.98, +-6.77,4.17, +-6.19,5, +-4.98,5.49, +-4.57,5.44, +-3.94,5.75, +-2.39,6.55, +-1.12,6.34, +-1.92,5.7, +-0.66,4.98, +1.22,4.22, +1.4,3.84, +1.39,2.99, +1.46,2.53, +1.87,2.23, +2.28,1.8, +2.63,1.23, +2.87,0.38, +2.9,-0.06, +2.87,-0.39, +2.78,-0.83, +2.67,-1.15, +2.76,-1.79, +2.75,-2.46, +2.62,-3.13, +2.26,-3.38, +1.83,-3.87, +1.26,-4.87, +0.18,-5.04, +-0.78,-5.96, +-1.73,-4.75, +-1.95,-5.36, +-2.7,-5.37, +-3.28,-5.69, +-3.5,-6.06, +-3.01,-6.29, +-2.39,-6.58, +-2.58,-7.1, +-2.73,-7.49, +-2.28,-7.61, +-1.38,-7.85, +-1.38,-7.85, +-1.49,-8.46, +-1.55,-8.81, +-1.55,-8.81, +-1.64,-9.28, +-1.69,-9.6, +-1.69,-9.6, +-1.63,-9.22, +-1.57,-8.91, +-2.22,-9.3, +-1.89,-9.61, +-1.55,-8.81, +-1.62,-9.17, +-1.72,-9.76, +-1.64,-9.28, +-0.41,-9.27, +-0,-9.05, +-0,-8.94, +0,-9.81, +0,-9.43, +0.26,-8.92, +0.62,-9.13, +2.28,-9.61, +2.04,-10.35, +3.38,-10.52, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.96,-10.87, +4.07,-11.19, +3.83,-10.53, +3.79,-10.41, +3.79,-10.41, +3.97,-10.92, +4.07,-11.17, +3.84,-10.56, +3.79,-10.41, +3.79,-10.41, +4,-10.98, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.59,-10.75, +5.6,-9.7, +6.03,-10.44, +6.11,-10.58, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +5.16,-12.98, +6.84,-11.84, +6.51,-11.28, +6.51,-11.28, +6.59,-11.01, +6.27,-10.86, +6.51,-11.28, +7.86,-10.54, +8.58,-10.23, +8.53,-10.16, +8.51,-10.14, +8.37,-9.98, +8.44,-8.35, +8.7,-7.3, +8.91,-7.48, +8.86,-7.43, +8.61,-7.23, +8.58,-7.2, +8.8,-7.38, +9.13,-7.66, +8.49,-7.12, +8.49,-7.12, +8.04,-6.75, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +6.71,-7.26, +6.37,-7.59, +7.12,-8.49, +8.19,-6.87, +8.49,-7.12, +9.04,-6.33, +10.45,-5.23, +12.24,-4.45, +12.01,-4.37, +11.02,-4.01, +10.05,-3.66, +8.95,-4.17, +8.59,-4.96, +7.72,-6.2, +7.76,-6.51, +7.58,-8.03, +8.74,-7.33, +3.64,-3.05, +1.21,-1.02, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-14.04,5.11, +-13.49,4.91, +-12.9,5.4, +-12.22,6.81, +-11.73,6.77, +-11.28,6.51, +-10.83,6.25, +-10.44,6.03, +-10.4,6.09, +-9.36,7.58, +-9.1,7.64, +-8.52,7.15, +-8.47,7.14, +-7.45,7.6, +-6.41,7.64, +-6.37,7.59, +-6.37,7.59, +-5.49,8.08, +-4.5,7.79, +-4.41,7.63, +-3.96,6.92, +-2.9,7.42, +-2.73,7.49, +-2.62,7.52, +-1.8,7.7, +-1.47,7.17, +-2.31,6.6, +-2.29,6.3, +-2.17,5.62, +-2.98,5.24, +-2.89,5.24, +-2.72,4.71, +-2.61,4.4, +-3.1,3.99, +-3.04,3.57, +-3.15,2.62, +-3.94,2.36, +-4.53,2.23, +-5.81,0.98, +-7.97,0, +-8.13,0, +-8.94,0, +-8.94,0, +-8.94,0, +-8.94,0, +-8.82,1.38, +-7.44,1.31, +-5.47,1.52, +-7.18,2.62, +-7.29,3.15, +-6.9,3.98, +-6.55,4.49, +-6.11,5.12, +-5,6.19, +-4.33,5.48, +-3.23,6.34, +-1.51,6.81, +-0.75,6.28, +-1.95,5.69, +-0.87,4.93, +-0.18,4.72, +0.1,4.49, +0.2,4.01, +0.97,3.04, +1.77,2.31, +2.07,2.03, +2.45,1.55, +2.71,1.05, +2.83,0.67, +2.91,0.08, +2.89,-0.34, +3.02,-0.7, +3.59,-1.62, +3.4,-2.24, +3.07,-2.68, +2.67,-3.08, +2.46,-4.26, +1.44,-4.83, +0.44,-5.02, +-0.6,-5.56, +-1.41,-5.17, +-1.77,-4.85, +-2.66,-5.27, +-3.07,-5.32, +-3.5,-6.06, +-3.5,-6.06, +-2.76,-6.4, +-2.39,-6.58, +-2.43,-6.68, +-1.53,-7.21, +-1.37,-7.76, +-1.38,-7.85, +-0.92,-7.89, +-0.75,-8.23, +-0.89,-8.86, +-1.04,-8.85, +-1.55,-8.81, +-1.55,-8.81, +-1.55,-8.81, +-1.55,-8.81, +-1.67,-9.44, +-1.6,-9.08, +-1.55,-8.81, +-1.46,-8.27, +-1.65,-9.34, +-1.53,-9.78, +-0.29,-9.89, +-0,-9.59, +-0,-8.94, +0,-9.43, +0,-9.05, +0,-8.94, +0.47,-8.9, +1.63,-9.22, +2.32,-9.64, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.9,-10.72, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.12,-11.33, +4.12,-11.33, +3.88,-10.66, +4.01,-11.02, +4.07,-11.17, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.36,-11.43, +4.94,-11.43, +5.24,-11.34, +4.62,-12.7, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +5.69,-12.02, +6.29,-11.54, +6.76,-11.7, +6.19,-10.72, +6.33,-10.96, +7.13,-10.85, +6.51,-11.28, +7.13,-10.85, +8.37,-9.98, +8.45,-10.07, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.12,-8.18, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +9.13,-7.66, +8.59,-7.21, +8.67,-7.28, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.34,-7, +7.59,-6.37, +7.89,-6.62, +7.66,-6.67, +7.56,-7.68, +7.12,-8.49, +8.49,-7.12, +8.49,-7.12, +9.04,-6.33, +10.3,-5.55, +11.63,-4.23, +11.63,-4.23, +11.33,-4.12, +10.72,-3.9, +9.88,-4.59, +9.09,-5.25, +8.58,-5.98, +7.89,-6.62, +8.65,-7.26, +9.23,-7.75, +9.14,-7.67, +5.66,-4.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-14,5.1, +-13.39,4.91, +-12.66,5.91, +-12.12,7, +-11.78,6.8, +-11.28,6.51, +-10.89,6.29, +-10.44,6.03, +-10.39,6.1, +-9.41,7.53, +-9.17,7.69, +-8.54,7.16, +-8.34,7.28, +-6.87,8.05, +-6.37,7.59, +-6.37,7.59, +-6.27,7.68, +-5.28,8.3, +-4.51,7.82, +-4.37,7.58, +-3.86,6.97, +-2.81,7.46, +-2.73,7.49, +-1.81,7.66, +-1.26,7.06, +-1.23,6.91, +-1.89,6.72, +-2.2,6.05, +-2.1,5.65, +-2.81,5.32, +-2.54,5.25, +-2.54,4.4, +-2.6,4.51, +-3.02,4.04, +-3.18,3.31, +-3.2,2.53, +-3.92,2.26, +-4.51,2.26, +-6.13,1.26, +-7.97,0, +-8.83,0, +-8.94,0, +-8.94,0, +-8.94,0, +-8.87,0.78, +-8.65,1.52, +-7.49,1.32, +-7.09,2.15, +-7.49,2.73, +-7.16,3.43, +-6.9,3.98, +-6.22,4.96, +-5.82,5.4, +-5.12,6.11, +-3.75,6, +-3.5,6.06, +-1.22,6.89, +-0.75,6.28, +-1.19,5.87, +-0.88,4.98, +-0.88,4.98, +-0.66,5, +-0.2,5.04, +0.32,4.38, +1.02,3.01, +1.29,2.81, +2.23,1.87, +2.51,1.45, +2.7,1.07, +2.83,0.67, +2.89,0.28, +3.16,-0.21, +3.95,-0.91, +3.66,-1.76, +3.24,-2.46, +2.88,-2.88, +2.44,-3.7, +1.91,-4.67, +0.87,-4.96, +-0.1,-5.05, +-0.88,-4.98, +-1.73,-4.75, +-2.37,-4.98, +-3.01,-5.22, +-3.28,-5.69, +-3.5,-6.06, +-3.13,-6.23, +-2.39,-6.58, +-2.23,-6.62, +-1.22,-6.89, +-1.22,-6.89, +-0.62,-7.7, +-0,-7.97, +-0,-7.97, +-0,-8.08, +-0,-8.62, +-0,-8.94, +-0.78,-8.87, +-1.33,-8.83, +-1.55,-8.81, +-1.52,-9.49, +-1.58,-8.98, +-1.55,-8.81, +-1.5,-8.49, +-1.55,-8.81, +-1.12,-9.65, +-0,-9.16, +-0,-9.05, +-0,-9.05, +-0,-9.64, +0,-8.94, +0.42,-8.77, +1.38,-7.85, +1.53,-8.69, +2.17,-8.98, +3.7,-10.17, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.34,-11.93, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.75,-13.04, +4.79,-13.15, +4.79,-13.15, +5.54,-11.91, +6.51,-11.28, +6.57,-11.38, +6.3,-10.9, +6.03,-10.44, +6.33,-10.96, +7.44,-10.63, +8.06,-10.19, +8.06,-9.6, +8.28,-9.87, +8.06,-9.9, +8.14,-10.14, +8.37,-9.98, +8.48,-10.1, +7.39,-8.81, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.67,-7.28, +8.86,-7.43, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.04,-6.75, +7.71,-6.47, +8.49,-7.12, +7.8,-7.8, +7.12,-8.49, +7.92,-7.69, +8.49,-7.12, +9.23,-6.07, +10.3,-5.94, +11.89,-4.63, +11.78,-4.29, +11.33,-4.12, +11.25,-4.09, +10.3,-5.17, +10.44,-6.03, +9.63,-6.33, +8.86,-7.43, +8.98,-7.54, +9.23,-7.75, +9.16,-7.69, +8.19,-6.87, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-13.58,4.94, +-13.12,4.86, +-12.2,6.67, +-11.97,6.91, +-11.47,6.62, +-11.22,6.48, +-10.58,6.11, +-10.44,6.03, +-10.39,6.11, +-9.42,7.51, +-8.88,7.46, +-8.5,7.14, +-8.34,7.18, +-6.63,7.71, +-6.37,7.59, +-6.37,7.59, +-5.8,8.01, +-4.87,8.44, +-4.47,7.74, +-4.08,7.07, +-3.22,7.28, +-2.73,7.49, +-2.73,7.49, +-2.32,7.55, +-1.42,7.27, +-1.35,6.86, +-1.81,6.74, +-2.11,5.78, +-2.06,5.66, +-2.2,5.53, +-2.79,5.16, +-2.53,4.38, +-2.62,4.5, +-3.02,4.04, +-3.18,3.4, +-3.2,2.53, +-4.02,2.32, +-4.5,2.29, +-5.36,1.95, +-8.17,1.44, +-8.9,0.52, +-8.81,1.55, +-8.81,1.55, +-8.81,1.55, +-8.81,1.55, +-8.17,1.44, +-7.85,1.38, +-7.53,2.58, +-7.49,2.73, +-7.07,3.63, +-6.56,4.47, +-6.11,5.12, +-5.23,6, +-4.54,6.64, +-3.82,6.62, +-3.5,6.06, +-1.41,6.84, +-0.93,6.27, +-0.52,5.98, +-0.88,4.98, +-0.78,4.99, +0,5.05, +0,5.05, +0,5.05, +0,4.89, +0,4.08, +0.74,3.35, +2.36,1.69, +2.55,1.41, +2.69,1.1, +2.78,0.86, +4.2,0.56, +4.06,-0.3, +3.82,-1.39, +3.6,-1.9, +3.26,-2.43, +2.74,-3.02, +1.88,-3.79, +1.3,-4.86, +0.29,-5.03, +-0.66,-5, +-1.2,-4.56, +-1.91,-4.67, +-2.96,-5.13, +-3.01,-5.22, +-3.18,-5.5, +-3.13,-6.23, +-2.39,-6.58, +-1.8,-6.73, +-1.22,-6.89, +-0.95,-6.92, +-0,-7, +-0,-7.65, +-0,-7.97, +-0,-7.97, +-0,-7.97, +-0,-7.97, +-0,-8.29, +-0,-8.33, +-0,-8.8, +-1.16,-9.12, +-1.55,-8.81, +-1.55,-8.81, +-1.59,-9.02, +-1.65,-9.34, +-0.43,-9.88, +-0,-9.1, +-0,-8.94, +-0,-9.1, +0.47,-9.39, +1.24,-8.83, +0.81,-8.33, +0.4,-7.94, +1.15,-7.87, +2.14,-8.82, +3.59,-9.86, +3.79,-10.41, +3.79,-10.41, +3.66,-10.05, +4.04,-10.3, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.83,-10.51, +4.06,-11.14, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.55,-12.5, +4.7,-12.9, +4.76,-12.42, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +5.86,-10.16, +6.33,-10.49, +7.24,-10.11, +7.96,-9.48, +7.85,-9.36, +7.75,-9.23, +6.97,-9.98, +8.19,-9.76, +8.37,-9.98, +8.23,-9.81, +7.28,-8.67, +8.03,-7.58, +8.49,-7.12, +8.49,-7.12, +8.58,-7.2, +8.56,-7.18, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.22,-6.9, +8.49,-7.12, +7.35,-8.26, +7.12,-8.49, +8.05,-8.07, +8.49,-7.12, +9.23,-6.07, +10.32,-5.96, +11.69,-5.63, +12.24,-4.45, +12.24,-4.45, +11.83,-6.18, +10.62,-6.13, +10.44,-6.03, +9.68,-7.1, +9.23,-7.75, +9.36,-7.85, +9.23,-7.75, +6.55,-5.5, +4.05,-3.9, +0.82,-0.98, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-13.33,4.85, +-13.14,4.81, +-11.61,6.31, +-11.32,6.53, +-11.28,6.51, +-11.01,6.36, +-10.47,6.05, +-10.44,6.03, +-9.96,6.75, +-9.2,7.69, +-8.65,7.26, +-8.49,7.12, +-7.89,7.27, +-6.43,7.6, +-6.37,7.59, +-6.3,7.65, +-5.39,8.3, +-4.86,8.42, +-4.44,7.69, +-4.01,6.95, +-3.52,7.14, +-2.74,7.48, +-2.73,7.49, +-2.52,7.06, +-2,6.74, +-2.24,6.63, +-1.97,6.71, +-2.11,5.8, +-2.04,5.6, +-1.77,4.87, +-2.23,4.88, +-2.35,4.47, +-2.63,4.31, +-3.16,3.9, +-3.09,3.22, +-3.36,2.68, +-4.43,2.57, +-4.76,2.49, +-5.46,1.99, +-7.46,1.77, +-8.81,1.55, +-8.81,1.55, +-8.81,1.55, +-8.81,1.55, +-8.23,1.45, +-7.85,1.38, +-7.69,1.96, +-7.49,2.73, +-7.27,3.2, +-6.9,3.98, +-6.28,4.87, +-5.67,5.56, +-5.12,6.11, +-4.17,6.77, +-3.98,6.9, +-3.05,6.73, +-1.22,6.89, +-1.1,6.25, +-0.31,5.78, +-0.88,4.98, +-0.44,5.02, +0,5.05, +0,5.05, +0.1,5.05, +0.59,5, +0.08,4.08, +0.46,3.63, +2.18,1.93, +2.48,1.53, +2.58,1.34, +2.74,1, +3.75,0.72, +3.68,-0.02, +4.01,-0.71, +3.87,-1.28, +3.59,-1.93, +3.04,-2.71, +2.3,-3.35, +1.41,-3.93, +0.51,-4.47, +0.3,-4.36, +-0.47,-4.04, +-1.73,-4.75, +-2.47,-4.58, +-3.01,-5.22, +-3.01,-5.22, +-2.41,-5.5, +-2.06,-5.66, +-1.41,-6, +-1.16,-6.57, +-0.41,-6.96, +-0,-7, +0,-7, +0,-7.32, +0,-7.86, +0,-7.97, +-0,-7.97, +-0,-7.97, +-0,-7.97, +-0,-7.97, +-1.04,-8.53, +-1.55,-8.81, +-1.55,-8.81, +-1.58,-8.97, +-1.55,-8.81, +-0,-9.08, +-0,-8.83, +-0,-8.62, +-0,-8.75, +0.67,-8.88, +0.78,-8.87, +0,-8.46, +0,-7.97, +0.62,-7.92, +1.85,-8.28, +2.93,-9.19, +3.52,-9.68, +3.54,-9.73, +3.39,-9.32, +3.49,-9.59, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.96,-10.87, +4.12,-11.33, +4.16,-11.44, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.44,-11.18, +4.68,-11.98, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.5,-12.35, +5.99,-11.88, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.16,-10.68, +5.89,-10.2, +6.03,-10.44, +7.17,-9.63, +7.82,-9.32, +7.75,-9.23, +7.53,-9.38, +7.46,-9.43, +7.75,-9.23, +8.27,-9.85, +8.19,-9.76, +6.85,-8.16, +7.19,-8.24, +8.74,-7.33, +8.86,-7.43, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.41,-7.36, +7.43,-8.86, +7.48,-8.91, +8.6,-8.38, +8.61,-7.23, +9.04,-6.33, +10.02,-5.78, +11.29,-6.19, +12.54,-4.57, +12.7,-4.62, +11.6,-7.75, +11.33,-7.01, +10.92,-6.3, +9.81,-7.77, +9.85,-8.27, +9.73,-8.16, +9.33,-7.82, +8.04,-6.75, +4.45,-4.86, +0.52,-0.62, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.07,5.12, +-13.38,4.87, +-12.89,5.41, +-11.93,6.68, +-11.28,6.51, +-11.28,6.51, +-10.72,6.19, +-10.44,6.03, +-10.44,6.03, +-9.56,7.14, +-8.59,7.21, +-8.49,7.12, +-8.46,7.15, +-7.51,7.8, +-6.54,7.79, +-6.37,7.59, +-6.1,7.81, +-4.98,8.57, +-4.88,8.45, +-4.45,7.71, +-4.05,7.01, +-3.17,7.3, +-2.73,7.49, +-2.72,7.47, +-2.55,7, +-2.39,6.58, +-2.39,6.58, +-2.31,6.35, +-2.08,5.72, +-2.05,5.64, +-1.9,5.22, +-2.08,4.6, +-2.38,4.45, +-2.53,4.38, +-2.9,4.05, +-2.91,3.1, +-4,2.86, +-4.47,2.59, +-4.84,2.55, +-5.66,2.06, +-6.45,1.93, +-8.81,1.55, +-8.81,1.55, +-8.81,1.55, +-8.38,1.48, +-7.85,1.38, +-7.85,1.38, +-7.57,2.43, +-7.39,2.69, +-6.37,3.34, +-6,3.78, +-6.01,5.04, +-5.29,5.94, +-4.74,6.37, +-3.98,6.9, +-3.57,7.1, +-2.54,6.98, +-1.22,6.89, +-1.12,6.34, +-0.67,5.81, +-0.88,4.98, +-0.1,5.05, +0,5.05, +0.1,5.05, +0.78,4.99, +0.83,4.7, +0.71,4.02, +0.27,3.82, +2.15,1.95, +2.35,1.71, +2.53,1.46, +2.62,1.25, +2.78,0.83, +2.89,0.25, +3.86,-0.35, +4,-0.79, +3.77,-1.54, +3.35,-2.3, +2.74,-3.02, +2.05,-4.6, +0.94,-3.96, +0.94,-3.96, +-0.08,-4.06, +-1.2,-4.22, +-1.89,-4.67, +-2.77,-4.8, +-2.85,-5.29, +-2.06,-5.66, +-1.89,-5.71, +-1.05,-5.93, +-0.93,-5.94, +-0,-6.24, +-0,-6.88, +0,-7, +0,-7, +0,-7, +0,-7.75, +0,-7.97, +-0,-7.97, +-0,-7.97, +-0,-7.97, +-1.04,-8.53, +-1.55,-8.81, +-1.55,-8.81, +-1.53,-8.7, +-1.02,-8.75, +-0,-8.62, +-0,-8.46, +-0,-7.97, +0,-8.66, +0.86,-8.87, +1.21,-8.84, +1.14,-8.6, +0.46,-7.93, +1.15,-7.87, +1.5,-8.49, +2.34,-9.02, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.52,-9.68, +3.79,-10.41, +3.79,-10.41, +3.83,-10.51, +4.12,-11.33, +4.07,-11.17, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.12,-11.33, +4.31,-11.83, +4.99,-11.89, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +5.22,-12.06, +6.17,-11.44, +6.51,-11.28, +6.51,-11.28, +6.03,-10.44, +6.18,-9.81, +6.03,-10.44, +7.46,-9.43, +7.75,-9.23, +7.82,-9.33, +7.75,-9.23, +7.75,-9.23, +7.84,-9.34, +8.37,-9.98, +8.22,-9.79, +7.54,-8.98, +7.12,-8.49, +8.83,-7.99, +9.23,-7.75, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +7.75,-9.23, +7.75,-9.23, +8.74,-8.24, +9.11,-7.64, +8.61,-7.23, +9.67,-5.86, +11.04,-6.37, +11.84,-6.84, +12.46,-6.26, +11.68,-8.75, +10.85,-9.1, +10.47,-8.79, +10.07,-8.45, +10.06,-8.44, +9.98,-8.37, +9.33,-7.82, +7.54,-6.7, +5.95,-7.5, +3.89,-6.32, +1.86,-3.22, +0.55,-0.95, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.8,5.02, +-13.33,4.85, +-13.12,4.86, +-12.04,6.53, +-11.28,6.51, +-10.93,6.31, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-9.41,7.08, +-8.49,7.12, +-8.49,7.12, +-8.3,7.27, +-6.86,7.85, +-6.41,7.64, +-6.37,7.59, +-5.44,8.27, +-4.97,8.58, +-4.89,8.47, +-4.47,7.74, +-4.05,7.01, +-3.85,6.98, +-3.04,7.36, +-2.84,7.34, +-2.48,6.82, +-2.53,6.55, +-2.37,6.51, +-2.11,5.78, +-2.06,5.66, +-2.06,5.66, +-2.06,5.66, +-2.08,5.26, +-2.17,4.17, +-2.34,4, +-3.07,3.96, +-2.92,3.18, +-3.79,3.03, +-4.33,2.55, +-4.64,2.49, +-5.65,2.09, +-5.59,2.03, +-7.95,1.61, +-8.81,1.55, +-8.65,1.52, +-7.85,1.38, +-7.85,1.38, +-7.73,1.83, +-7.49,2.73, +-6.68,2.43, +-6.06,3.5, +-5.71,4, +-5.75,5.08, +-5.12,6.11, +-4.24,6.73, +-3.85,6.97, +-2.87,7.42, +-2.56,7.03, +-1.47,6.99, +-1.09,6.17, +-0.81,5.95, +-0.59,5, +0,5.05, +0,5.05, +0.15,5.04, +0.88,4.98, +0.81,4.59, +0.71,4.02, +0.08,4.08, +0.66,3.43, +0.55,3.1, +0.75,2.78, +2.53,1.46, +2.69,1.1, +2.86,0.56, +2.91,0.11, +3.02,-0.3, +3.74,-1.02, +3.64,-1.83, +2.96,-2.79, +2.95,-4.48, +1.47,-4.49, +0.94,-3.96, +0.27,-4.06, +-0.97,-3.95, +-1.67,-4.6, +-1.84,-4.7, +-1.8,-4.95, +-1.84,-5.05, +-1.22,-5.55, +-1.02,-5.8, +-0.52,-5.98, +-0,-6.03, +0,-6.3, +0,-6.89, +0.81,-6.93, +0.95,-6.92, +0,-7, +0,-7.55, +-0,-7.97, +-0,-7.97, +-0,-7.97, +-1.04,-8.53, +-1.55,-8.81, +-1.55,-8.81, +-1.24,-8.51, +-0.46,-8.25, +-0,-8.73, +-0,-8.15, +0,-7.97, +0,-8.46, +1.04,-8.85, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.46,-8.27, +1.55,-8.81, +2.47,-9.06, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.66,-10.05, +3.79,-10.41, +3.79,-10.41, +3.96,-10.87, +4.07,-11.17, +3.86,-10.61, +4.09,-11.22, +4.12,-11.33, +4.18,-11.48, +4.29,-11.78, +6.21,-11.28, +5.92,-11.55, +5.14,-11.92, +5.83,-11.6, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.41,-12.12, +4.45,-12.24, +6.25,-11.4, +6.51,-11.28, +6.51,-11.28, +6.66,-9.6, +5.86,-10.16, +6.23,-9.9, +6.98,-9.77, +7.75,-9.23, +7.98,-9.51, +7.96,-9.48, +7.85,-9.36, +8.37,-9.98, +8.37,-9.98, +8.65,-10.31, +8.58,-10.23, +7.54,-8.98, +7.63,-7.15, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.24,-7.83, +7.75,-9.23, +7.75,-9.23, +8.9,-8.08, +9.23,-7.75, +8.95,-7.51, +9.69,-5.97, +11.42,-6.59, +12.12,-7, +12.12,-7, +11.78,-7.9, +11.34,-9.52, +10.81,-9.07, +10.72,-9, +10.1,-8.48, +9.79,-8.22, +8.35,-7.01, +6.37,-7.14, +5.43,-8.26, +2.55,-4.42, +5.54,-9.6, +1.65,-2.86, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-12.73,5.6, +-11.57,6.62, +-11.28,6.51, +-10.83,6.25, +-10.44,6.03, +-10.44,6.03, +-10.1,6.2, +-8.82,6.93, +-8.49,7.12, +-8.49,7.12, +-7.86,7.77, +-6.91,8.24, +-6.37,7.59, +-6.18,7.74, +-5.22,8.42, +-4.96,8.59, +-4.89,8.47, +-4.48,7.76, +-4.11,7.11, +-3.98,6.9, +-3.47,7.16, +-3.11,7.33, +-2.92,7.15, +-3.36,6.21, +-3.03,6.22, +-2.15,5.73, +-2.06,5.66, +-2.06,5.66, +-2.02,5.56, +-1.8,4.93, +-1.61,4.42, +-1.86,3.68, +-2.54,3.56, +-3.05,3.06, +-3.29,2.55, +-4.22,2.45, +-4.55,2.62, +-5.12,2.03, +-5.6,2.04, +-5.03,2.06, +-8.02,1.63, +-7.96,1.4, +-7.85,1.38, +-7.77,1.68, +-7.53,2.56, +-7.03,2.56, +-6.4,2.76, +-6.06,3.5, +-5.52,4.28, +-5.09,5.23, +-4.73,6.65, +-3.98,6.9, +-3.78,7, +-2.94,7.39, +-2.73,7.49, +-1.72,7.09, +-1.05,5.93, +-1.05,5.93, +-0.47,5.5, +0,5.05, +0,5.05, +0.44,5.02, +1.13,4.91, +1.2,4, +0.71,4.02, +0.59,4.03, +0,4.08, +-0.18,4.07, +-0.71,4.01, +2.27,1.83, +2.46,1.55, +2.67,1.15, +2.81,0.75, +2.88,0.42, +2.99,-0.44, +3.12,-1.04, +2.74,-1.83, +2.33,-2.76, +1.61,-3.74, +0.79,-4, +-0,-4.08, +-0.71,-4.02, +-1.2,-4, +-1.58,-4.34, +-1.73,-4.75, +-1.02,-4.94, +-0.88,-4.98, +-0.59,-5.25, +-0,-5.81, +-0,-6.03, +0,-6.03, +0.55,-6.14, +1.22,-6.89, +1.22,-6.89, +0,-7, +0,-7, +0,-7.21, +-0,-7.97, +-0,-7.97, +-1.01,-8.37, +-1.5,-8.53, +-1.38,-8.82, +-0.62,-7.92, +-0,-7.97, +-0,-8.58, +-0,-7.97, +0,-7.97, +0.96,-8.1, +1.53,-8.7, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +2.81,-9.07, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.66,-10.05, +3.79,-10.41, +3.82,-10.49, +4.09,-11.22, +4.01,-11.02, +3.79,-10.41, +4.01,-11.02, +4.12,-11.33, +4.23,-11.63, +4.83,-12.06, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +5.48,-11.76, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.29,-11.78, +4.12,-11.33, +5.29,-11.31, +6.3,-10.92, +6.51,-11.28, +6.46,-11.19, +6.19,-10.72, +6.6,-10.04, +6.6,-10.04, +6.6,-10.04, +7.8,-9.3, +8.09,-9.65, +8.06,-9.6, +8.37,-9.98, +8.37,-9.98, +8.76,-10.44, +8.92,-10.63, +8.23,-9.81, +7.66,-9.13, +7.88,-7.45, +8.49,-7.12, +8.16,-7.68, +7.8,-7.8, +8.03,-7.58, +8.21,-7.4, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.24,-7.83, +7.75,-9.23, +7.75,-9.23, +8.74,-8.24, +9.23,-7.75, +9.5,-7.97, +9.18,-7.25, +11.98,-6.92, +12.48,-7.21, +11.08,-7.52, +10.62,-8.91, +11.47,-9.62, +11.19,-9.39, +10.47,-8.79, +9.85,-8.27, +9.05,-7.59, +6.81,-6.96, +5.8,-8.18, +5.46,-9.45, +3.96,-6.85, +0,0, +0.62,-1.07, +0.83,-1.43, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.15,4.79, +-13.15,4.79, +-12.91,4.81, +-11.71,6.05, +-11.28,6.51, +-11.28,6.51, +-10.94,6.32, +-10.41,6.01, +-10.29,5.94, +-9.52,6.05, +-8.49,7.12, +-8.49,7.12, +-8.45,7.17, +-7.77,7.76, +-6.6,7.87, +-6.37,7.59, +-6.04,7.85, +-4.96,8.59, +-4.96,8.59, +-4.92,8.52, +-4.5,7.8, +-4.33,7.5, +-3.98,6.9, +-3.92,6.94, +-3.78,7.01, +-3.75,6.71, +-3.6,6.23, +-3.5,6.06, +-2.68,5.53, +-2.1,5.65, +-2,5.5, +-1.8,4.93, +-1.73,4.75, +-1.55,4.12, +-2.12,3.77, +-2.49,3.23, +-2.93,2.92, +-3.34,2.6, +-4.19,2.5, +-4.38,2.53, +-4.65,1.96, +-5.1,2.24, +-4.92,2.32, +-5.55,2.76, +-6.47,2.33, +-7.23,2.39, +-7.49,2.73, +-7.49,2.73, +-6.68,2.43, +-6.23,3.13, +-5.98,3.61, +-5.16,5.22, +-4.62,6.81, +-4.47,7.74, +-3.14,7.56, +-2.73,7.49, +-2.73,7.49, +-2.58,7.53, +-1.26,7.13, +-1.07,6.09, +-0.9,5.95, +-0.21,5.81, +0,5.05, +0.1,5.05, +0.88,4.98, +1.16,4.9, +1.51,4.14, +0.97,3.95, +0.71,4.02, +0.6,3.84, +0.52,3.85, +-0.3,4.06, +2.23,1.87, +2.23,1.87, +2.4,1.64, +2.62,1.25, +2.76,0.88, +2.89,0.34, +2.86,-0.5, +2.62,-1.26, +2.05,-2.05, +1.45,-2.52, +1.01,-3, +0.65,-3.37, +0.12,-4.07, +-0,-4.08, +-0.69,-3.96, +-1.03,-3.95, +-1.02,-4.18, +-0.62,-4.26, +-0.13,-4.51, +-0.41,-4.86, +0,-5.86, +0.6,-5.97, +1.05,-5.93, +1.1,-6.22, +1.22,-6.89, +0.41,-6.96, +0,-7, +0,-7, +0,-7, +-0,-7.43, +-0.62,-7.81, +-0.64,-8.19, +-0,-8.13, +-0,-7.97, +-0,-7.97, +-0,-8.08, +0,-7.97, +0,-7.97, +1.38,-7.85, +1.41,-8.01, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +2.97,-8.93, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.56,-9.79, +3.79,-10.41, +3.96,-10.87, +4.12,-11.33, +3.9,-10.72, +3.83,-10.51, +3.87,-10.64, +4.12,-11.33, +4.22,-11.59, +5.04,-11.96, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +5.48,-11.76, +6.51,-11.28, +4.41,-12.12, +4.29,-11.78, +4.76,-11.03, +5.39,-10.73, +6.03,-10.44, +6.03,-10.44, +6.33,-10.96, +6.51,-11.28, +6.78,-10.43, +6.41,-10.17, +7.17,-9.63, +7.75,-9.23, +7.48,-8.91, +7.85,-9.36, +8.37,-9.98, +8.37,-9.98, +8.45,-10.07, +9,-10.72, +8.37,-9.98, +7.64,-9.11, +7.46,-8.15, +8.68,-7.5, +8.4,-7.41, +7.12,-8.49, +7.12,-8.49, +7.12,-8.49, +7.27,-8.34, +7.88,-7.73, +8.29,-7.32, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.38,-7.42, +7.76,-8.92, +7.75,-9.23, +7.75,-9.23, +7.99,-8.98, +9.23,-8.14, +9.88,-8.29, +9.56,-8.02, +11.67,-7.88, +13.17,-7.61, +11.33,-8.41, +10.85,-9.1, +11.47,-9.62, +11.47,-9.62, +10.51,-8.82, +9.6,-8.06, +8.39,-7.36, +6.87,-8.19, +6.99,-9.37, +7.13,-9.56, +5.91,-9.13, +0.83,-1.43, +4.34,-7.51, +0.92,-1.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.15,4.79, +-12.97,4.72, +-12.62,5.21, +-11.28,6.51, +-11.28,6.51, +-11.01,6.36, +-10.63,6.14, +-10.31,5.95, +-9.6,5.54, +-9.03,6.38, +-8.49,7.12, +-8.49,7.12, +-8.38,7.24, +-6.92,8.02, +-6.4,7.63, +-6.37,7.59, +-6.11,7.79, +-4.96,8.59, +-4.96,8.59, +-4.93,8.54, +-4.61,7.99, +-4.41,7.63, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.79,6.57, +-3.43,5.95, +-3,5.27, +-2.16,5.62, +-1.91,5.25, +-1.73,4.75, +-1.7,4.67, +-1.5,4, +-1.98,3.57, +-2.46,3.25, +-2.86,2.9, +-3.13,2.62, +-3.6,2.41, +-4.34,2.51, +-4.43,2.43, +-4.48,2.33, +-4.56,2.63, +-5.19,3, +-4.63,2.67, +-5.16,2.75, +-6.32,2.7, +-6.59,2.43, +-6.52,2.52, +-5.92,3.52, +-5.75,5.61, +-4.23,7.32, +-4.04,7, +-3.18,7.4, +-2.73,7.49, +-2.58,7.53, +-1.77,7.75, +-1.38,7.85, +-1.38,7.85, +-1.27,7.18, +-0.35,6, +0,6.03, +0,5.19, +0.73,4.99, +0.88,4.98, +0.92,4.63, +1.4,3.84, +1.4,3.84, +1.14,3.9, +1.21,3.39, +1.92,2.29, +0.57,3.33, +1.87,2.23, +1.99,2.11, +1.99,2.11, +2.22,1.87, +2.51,1.45, +2.75,0.92, +2.9,0, +2.75,-0.89, +2.29,-1.78, +1.87,-2.22, +1.3,-2.6, +0.83,-2.78, +0.5,-3.26, +0.32,-4.05, +-0.49,-3.56, +-1.06,-2.18, +-1.1,-2.52, +-1,-2.74, +-0.66,-3.17, +-0.49,-4.45, +0.04,-5.46, +0.19,-5.84, +0.7,-5.93, +0.35,-5.93, +-0.03,-6.73, +0.68,-6.94, +-0,-7, +0,-7, +0,-7, +-0,-7.32, +-0,-7.97, +-0,-7.97, +-0,-7.97, +-0,-7.87, +-0,-7.97, +-0,-7.97, +0,-7.97, +1.19,-7.87, +1.38,-7.85, +1.37,-7.74, +1.53,-8.69, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +2.59,-8.64, +3.35,-9.2, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.55,-9.75, +3.79,-10.41, +4,-10.98, +3.9,-10.72, +3.96,-10.87, +4.07,-11.17, +3.93,-10.8, +4.12,-11.33, +4.26,-11.69, +5.77,-11.45, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.37,-11.04, +5.95,-10.3, +6.03,-10.44, +6.03,-10.44, +6.13,-10.62, +6.11,-10.59, +6.1,-10.56, +6.43,-11.14, +6.45,-10.53, +6.6,-10.04, +6.98,-9.77, +7.32,-9.24, +7.12,-8.49, +7.75,-9.23, +8.37,-9.98, +8.58,-10.23, +8.83,-10.52, +9,-10.72, +8.27,-9.85, +7.77,-8.76, +8.59,-7.21, +9.13,-7.66, +7.51,-8.95, +7.12,-8.49, +7.39,-8.81, +7.12,-8.49, +7.54,-8.98, +7.75,-9.23, +7.43,-8.86, +7.95,-8.66, +8.29,-7.32, +8.49,-7.12, +7.99,-8.53, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.77,-8.72, +9.98,-8.37, +10.23,-8.58, +13.11,-8.32, +13.73,-7.93, +11.47,-9.62, +11.47,-9.62, +11.56,-9.7, +11.47,-9.62, +10.91,-9.15, +9.65,-8.09, +8.4,-8.19, +7.64,-9.11, +7.75,-9.23, +7.32,-9.53, +5.97,-10.33, +6.03,-10.44, +6.45,-11.17, +1,-1.74, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.09,4.76, +-12.91,4.7, +-11.72,6.02, +-11.17,6.45, +-11.19,6.46, +-10.65,6.15, +-10.44,6.03, +-10.33,5.96, +-9.22,6.11, +-8.53,7.07, +-8.49,7.12, +-8.49,7.12, +-8.39,7.2, +-6.96,8, +-6.37,7.59, +-6.37,7.59, +-6.2,7.73, +-4.96,8.59, +-4.81,8.33, +-4.96,8.59, +-4.74,8.21, +-4.46,7.73, +-4.1,7.11, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.8,6.58, +-3.45,6.04, +-2.65,5.71, +-2.09,5.65, +-1.89,5.19, +-1.73,4.75, +-1.72,4.68, +-1.57,4.09, +-1.96,3.58, +-2.18,3.45, +-2.82,2.94, +-3.13,2.62, +-3.26,2.56, +-4.24,2.54, +-4.38,2.53, +-4.38,2.53, +-4.55,2.62, +-5.13,2.96, +-4.43,2.56, +-4.47,2.58, +-4.62,2.22, +-4.79,1.77, +-4.94,1.77, +-5,2.17, +-4.76,6.62, +-3.98,6.9, +-3.36,7.2, +-2.73,7.49, +-2.34,7.59, +-1.38,7.85, +-1.38,7.85, +-1.38,7.85, +-1.23,7, +-0.41,6.31, +0,6.03, +0,6.03, +0.63,5.61, +0.88,4.98, +1.16,4.9, +1.51,4.14, +1.49,3.79, +1.72,3.69, +1.48,3.25, +1.6,2.65, +2.23,1.87, +2.11,1.99, +1.87,2.23, +1.87,2.23, +1.87,2.23, +1.91,2.19, +2.22,1.86, +2.56,1.35, +2.84,0.57, +2.89,-0.25, +2.65,-1.15, +2.14,-1.96, +1.69,-2.36, +1.25,-2.62, +0.94,-2.76, +0.61,-3.45, +0.25,-3.29, +-0.66,-1.83, +-0.41,-2, +-0.44,-2.42, +-0.55,-3.13, +-0.81,-5.1, +-1.05,-5.93, +-1.05,-5.93, +-1.05,-5.93, +-1.05,-5.93, +-1.06,-6.8, +-0,-7, +-0,-7, +-0,-7, +-0,-7.43, +-0,-7.97, +-0,-7.97, +-0,-7.97, +-0,-8.13, +-0,-8.78, +-0,-8.7, +0,-8.08, +0.92,-7.89, +1.38,-7.85, +1.33,-7.53, +1.41,-8.01, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +2.67,-8.84, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.46,-9.5, +3.79,-10.41, +3.66,-10.05, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.08,-11.21, +4.12,-11.33, +6.46,-11.19, +6.4,-11.09, +6.44,-11.16, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.3,-10.9, +6.72,-9.82, +7.08,-9.87, +6.47,-10.52, +6.76,-9.92, +6.03,-10.44, +5.81,-10.06, +5.92,-10.25, +5.89,-10.2, +6.23,-10.1, +6.1,-10.09, +5.78,-10.02, +5.86,-10.16, +6.96,-10.7, +8.01,-11.24, +8.68,-10.35, +8.68,-10.35, +8.48,-10.1, +8.3,-9.89, +8.08,-8.9, +9.23,-7.75, +8.98,-7.99, +7.23,-8.61, +7.12,-8.49, +7.19,-8.57, +7.62,-9.08, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.14,-9.66, +7.14,-8.87, +7.53,-8.6, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.82,-9.32, +9.17,-9.17, +11.09,-9.31, +13.93,-8.84, +12.54,-10.01, +12.21,-10.25, +12.1,-10.16, +12.21,-10.25, +11.59,-9.73, +11.34,-9.52, +10.35,-8.68, +8.51,-9.39, +7.96,-9.48, +7.9,-9.42, +7.96,-10.66, +7.17,-11.61, +6.67,-11.56, +6.1,-9.44, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.54,4.57, +-12.07,4.9, +-11.3,6.29, +-10.69,6.17, +-10.53,6.08, +-10.46,6.04, +-10.31,5.95, +-10.12,5.92, +-8.91,6.63, +-8.41,7.06, +-8.23,6.9, +-8.26,6.93, +-8.22,6.97, +-6.78,7.77, +-6.37,7.59, +-6.37,7.59, +-5.85,7.98, +-4.89,8.47, +-4.95,8.57, +-4.91,8.51, +-4.58,7.93, +-4.47,7.74, +-4.1,7.09, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.82,6.62, +-3.37,6.04, +-2.35,5.75, +-2.24,5.59, +-2.03,5.58, +-1.82,4.99, +-1.78,4.73, +-1.86,4.36, +-1.81,3.69, +-2.29,3.37, +-2.73,3.03, +-3.11,2.7, +-3.63,3.03, +-4.18,2.82, +-4.38,2.53, +-4.38,2.53, +-4.47,2.58, +-4.81,2.78, +-4.41,2.55, +-4.63,2.67, +-4.57,2.13, +-4.75,1.73, +-4.75,1.73, +-4.67,2.28, +-4.1,6.82, +-3.83,6.98, +-2.73,7.49, +-2.15,7.64, +-1.38,7.85, +-1.38,7.85, +-1.23,7.86, +-1.26,7.13, +-0.61,6.94, +0,6.03, +0,6.03, +0.7,5.96, +1.16,5.9, +1.62,4.78, +2.13,4.56, +1.72,3.69, +2.26,3.38, +2.26,3.06, +2.61,2.78, +2.12,2.32, +2.28,1.8, +2.23,1.87, +1.87,2.23, +1.47,2.28, +1.71,2.11, +1.87,2.23, +1.83,1.83, +2.33,1.74, +2.7,1.05, +2.87,0.39, +2.83,-0.61, +2.56,-1.35, +2.05,-2.05, +1.6,-2.43, +1.06,-2.71, +0.8,-2.93, +0.26,-3.17, +-0.41,-1.89, +-0.17,-1.93, +0.08,-2.3, +-0.27,-3.72, +-0.89,-5.52, +-1.05,-5.93, +-1.05,-5.93, +-1.05,-5.93, +-1.05,-5.93, +-0.55,-6.63, +-0,-7, +-0,-7, +-0,-7, +-0,-7.48, +-0,-7.97, +0,-7.97, +-0,-8.08, +0,-7.97, +0,-8.73, +0,-8.13, +0.69,-7.91, +1.47,-8.33, +1.43,-8.09, +1.46,-8.27, +1.53,-8.7, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.59,-9.02, +2.63,-8.74, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.39,-9.32, +3.44,-9.44, +3.44,-9.45, +3.79,-10.41, +3.66,-10.05, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.08,-11.21, +4.12,-11.33, +6.09,-11.3, +6.35,-11, +6.45,-11.17, +6.51,-11.28, +6.51,-11.28, +6.43,-11.14, +6.03,-10.44, +4.43,-10.23, +2.99,-10.77, +2.17,-11.18, +2.28,-11.68, +0.03,-12.35, +0.32,-11.9, +1.05,-12.45, +2.05,-12.54, +1.89,-12.07, +3.18,-11.43, +3.5,-13.5, +5.34,-14.67, +7.55,-14, +8.1,-13.45, +8.36,-12.24, +8.79,-10.47, +8.58,-10.23, +8.48,-10.1, +7.84,-9.34, +8.56,-8.42, +7.54,-8.58, +7.12,-8.49, +7.12,-8.49, +6.84,-8.15, +7.66,-9.13, +7.75,-9.23, +7.75,-9.23, +6.67,-9.99, +5.95,-10.3, +6.89,-9.83, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.72,-10.39, +11.84,-9.93, +12.95,-10.87, +12.46,-10.45, +12.21,-10.25, +12.21,-10.25, +12.21,-10.25, +12.09,-10.14, +11.59,-9.73, +10.97,-9.6, +8.37,-9.98, +8.44,-10.06, +8.72,-10.39, +9.41,-11.22, +9.52,-11.34, +8.59,-10.71, +5,-5.96, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.52,6.04, +-11.41,6.26, +-10.72,6.19, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.32,5.96, +-9.84,5.79, +-8.59,6.99, +-8.31,6.97, +-7.62,6.4, +-7.97,6.68, +-7.68,6.89, +-6.69,7.98, +-6.37,7.59, +-6.34,7.56, +-5.74,7.82, +-4.73,8.12, +-4.96,8.59, +-4.86,8.42, +-4.47,7.74, +-4.47,7.74, +-4.15,7.18, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.84,6.65, +-3.5,6.06, +-3.27,5.99, +-2.44,5.65, +-2.33,5.53, +-1.87,4.93, +-1.73,4.75, +-1.64,4.44, +-1.56,3.82, +-2.07,3.52, +-2.89,3.58, +-3.37,3.25, +-3.87,3.25, +-4,3.08, +-4.36,2.55, +-4.38,2.53, +-4.38,2.53, +-4.38,2.53, +-4.38,2.53, +-4.38,2.53, +-4.49,2.32, +-4.75,1.74, +-4.75,1.73, +-4.74,2.12, +-3.98,6.9, +-2.88,7.42, +-2.58,7.53, +-1.53,7.81, +-1.38,7.85, +-0.59,7.92, +0,7.97, +-0.17,7.26, +0,7, +0,6.13, +0.7,5.96, +1.05,5.93, +1.89,5.71, +2.53,4.68, +2.93,4.1, +2.43,3.26, +2.89,2.66, +2.68,2, +2.38,1.67, +2.67,1.83, +2.99,1.81, +2.35,1.71, +2.03,2.07, +1.7,2.02, +1.35,1.61, +1.7,2.02, +1.65,1.7, +1.55,1.16, +1.76,0.8, +2.29,0.53, +2.25,-0.2, +2.26,-0.85, +2.03,-1.58, +1.5,-2.1, +1.1,-2.58, +1,-2.74, +0.55,-2.46, +-0.25,-1.92, +0.17,-1.92, +0.41,-2.44, +0.23,-3.41, +-0.7,-5.32, +-1.05,-5.93, +-1.05,-5.93, +-1.05,-5.93, +-0.47,-5.99, +-0,-6.67, +-0,-7, +-0,-7, +-0,-7.48, +0,-7.97, +0,-7.97, +0,-7.97, +0,-8.29, +0.46,-7.93, +0,-8.29, +0.77,-7.9, +1.38,-7.85, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +2.05,-8.67, +2.31,-8.6, +2.31,-8.6, +2.08,-9, +2,-9.69, +3.39,-9.32, +3.24,-9.36, +2.97,-9.43, +2.56,-9.54, +2.9,-9.65, +3.5,-9.63, +3.52,-9.68, +3.48,-9.56, +3.62,-9.94, +3.79,-10.41, +3.79,-10.41, +3.88,-10.67, +4.12,-11.33, +6.13,-10.62, +6.51,-11.28, +6.51,-11.28, +6.45,-11.17, +6.24,-10.29, +5.07,-10.48, +1.23,-10.83, +-2.8,-10.45, +-4.8,-10.3, +-5.14,-11.65, +-4.45,-12.24, +-4.64,-12.75, +-4.4,-13.11, +-2.57,-14.58, +-2.19,-15.1, +-0,-16.3, +2.23,-16.28, +3.76,-15.99, +6.1,-14.52, +7.97,-13.8, +6.84,-13.8, +6.12,-14.13, +5.85,-13.6, +6.76,-12.89, +7.85,-12.03, +7.09,-11.71, +8.06,-9.6, +7.66,-9.13, +7.4,-8.82, +6.87,-8.19, +6.37,-7.59, +7.54,-8.98, +7.75,-9.23, +7.32,-9.7, +6.03,-10.44, +6.03,-10.44, +7.75,-9.23, +7.64,-9.11, +7.36,-8.77, +7.68,-9.15, +7.53,-9.38, +7.17,-9.63, +6.89,-9.83, +7.96,-9.48, +9.31,-11.09, +12.55,-11.52, +13.34,-11.19, +12.95,-10.87, +12.95,-10.87, +12.95,-10.87, +12.68,-10.64, +12.3,-10.32, +12.21,-10.25, +10.15,-10.71, +9,-10.72, +9.35,-11.15, +9.75,-11.62, +9.71,-11.57, +9.62,-11.47, +8.92,-10.63, +3.51,-4.68, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.28,6.51, +-11.21,6.47, +-10.57,6.1, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-9.82,5.97, +-9.11,6.29, +-8.49,7.12, +-8.17,6.86, +-7.59,6.37, +-7.96,6.68, +-7.6,6.75, +-6.4,7.63, +-6.37,7.59, +-6.29,7.5, +-6.3,7.6, +-5.44,7.64, +-4.93,8.49, +-4.58,7.93, +-4.42,7.65, +-4.32,7.48, +-4.09,7.09, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.88,6.72, +-3.5,6.06, +-3.5,6.06, +-2.86,5.85, +-2.16,5.62, +-1.81,4.98, +-1.77,4.71, +-1.75,4.09, +-1.86,3.63, +-2.05,3.53, +-2.77,3.46, +-2.75,3.08, +-3.37,2.88, +-3.84,3.2, +-4.25,2.73, +-4.35,2.51, +-4.26,2.46, +-4.38,2.53, +-4.36,2.52, +-4.08,2.35, +-4.34,2.36, +-4.74,1.74, +-4.75,1.73, +-4.87,1.24, +-3.43,7.16, +-2.73,7.49, +-1.68,7.77, +-1.38,7.85, +-0.46,7.93, +0,7.97, +0,8.08, +0.33,7.83, +0.14,6.99, +1.04,6.91, +1.05,5.93, +1.81,5.73, +2.38,5.51, +3.19,4.31, +3.66,3.46, +3.2,2.53, +2.75,1.82, +2.69,1.11, +2.69,1.09, +2.91,1.19, +3.55,1.67, +3.2,1.85, +2.22,1.86, +1.56,1.86, +1.16,1.55, +1.28,1.46, +1.83,1.59, +1.65,1.02, +1.8,0.72, +2.17,0.21, +2.13,-0.32, +1.88,-0.8, +1.56,-1.15, +1.16,-1.55, +0.82,-1.76, +1,-2.74, +0.33,-2.26, +0.11,-1.93, +0.52,-1.86, +0.53,-2.7, +0.87,-2.98, +-0.46,-5.3, +-1.05,-5.93, +-0.93,-5.94, +-0.15,-6.01, +-0,-6.03, +-0,-6.67, +-0,-7, +-0,-7.35, +0,-7.97, +0,-7.97, +0,-7.97, +0,-7.97, +0.92,-7.89, +1.38,-7.85, +1.45,-8.21, +1.38,-7.85, +1.44,-8.17, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.55,-8.81, +1.8,-8.74, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +2.76,-7.59, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.86,-10.53, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +6.03,-10.44, +6.46,-11.19, +6.51,-11.28, +6.27,-10.86, +4.6,-10.51, +0.31,-10.94, +-2.87,-11.23, +-3.8,-10.48, +-5.14,-10.49, +-4.64,-12.76, +-5.01,-13.76, +-3.78,-14.09, +-2.87,-14.54, +-2.6,-14.74, +-0.26,-15.63, +0,-16.26, +2.8,-15.86, +3.58,-14.98, +4.26,-14.55, +3.99,-14.46, +4.59,-14.92, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +5.12,-14.07, +4.83,-13.27, +6.55,-12.15, +6.92,-11.98, +6.84,-11.84, +6.61,-9.98, +6.51,-9.65, +7.13,-10.26, +7.96,-10.07, +7.82,-9.33, +6.6,-10.04, +6.03,-10.44, +7.22,-9.01, +7.21,-8.59, +6.67,-8.8, +5.8,-9.41, +5.61,-9.72, +5.99,-10.37, +6.03,-10.44, +6.93,-11.19, +10.91,-13, +13.01,-12.46, +13.3,-11.62, +13.18,-11.06, +12.95,-10.87, +12.95,-10.87, +12.95,-10.87, +12.95,-10.87, +12.95,-10.87, +9.93,-11.84, +9.93,-11.84, +10.17,-12.12, +10.25,-12.21, +10.17,-12.12, +9.76,-11.18, +8.48,-10.1, +2.48,-2.95, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.28,6.51, +-10.77,6.22, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.35,5.98, +-9.51,6.06, +-8.59,6.99, +-8.49,7.12, +-8.24,6.91, +-7.59,6.37, +-7.94,6.66, +-7.69,6.75, +-6.4,7.57, +-6.36,7.58, +-6.09,7.26, +-6.25,7.45, +-5.77,7.08, +-4.76,7.95, +-4.49,7.74, +-4.38,7.59, +-4.04,7, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.88,6.72, +-3.5,6.06, +-3.5,6.06, +-3.39,6.08, +-2.68,5.68, +-2.37,4.94, +-2.16,4.49, +-1.51,3.88, +-1.85,3.63, +-2.08,3.51, +-2.57,3.17, +-2.72,3.18, +-3.16,2.87, +-3.56,2.98, +-4.01,3.07, +-4.17,2.66, +-3.7,2.15, +-4.21,2.43, +-4.36,2.52, +-4.04,2.33, +-3.88,2.21, +-4.63,1.95, +-4.75,1.73, +-4.65,1.68, +-3.1,5.36, +-2.53,7.54, +-1.38,7.85, +-0.69,7.91, +0,7.97, +0.15,7.96, +1.21,8.73, +1.52,8.65, +1.04,7.07, +1.22,6.89, +1.5,5.81, +2.17,5.61, +3.2,5.08, +3.78,3.73, +4.07,2.69, +3.69,1.72, +3.43,1.07, +2.84,0.62, +2.87,0.51, +2.92,1.06, +3.64,1.43, +2.94,1.19, +2.45,1.55, +1.68,1.5, +1.39,1.35, +1.45,1.29, +1.69,1.2, +1.85,0.87, +2.4,0.59, +2.58,-0.06, +2.03,-0.53, +1.73,-0.87, +1.43,-1.31, +1.14,-1.57, +0.91,-1.82, +1.17,-2.66, +0.41,-2.17, +0.5,-1.87, +0.86,-1.87, +1,-2.74, +1.15,-2.67, +-0.04,-4.91, +-1.05,-5.93, +-0.35,-6, +-0,-6.39, +-0,-6.13, +-0,-6.71, +-0,-7, +0,-7.65, +0,-7.97, +0.46,-7.93, +0.77,-7.9, +0.86,-7.89, +1.38,-7.85, +1.38,-7.85, +1.38,-7.85, +1.42,-8.06, +1.42,-8.04, +1.53,-8.7, +1.55,-8.81, +1.55,-8.81, +2.49,-8.55, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +2.89,-7.95, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.78,-10.11, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +6.03,-10.44, +6.03,-10.44, +6.5,-10.5, +5.16,-10.68, +1.06,-10.95, +-1.12,-11.12, +-2.09,-11.87, +-2.82,-11.24, +-4.54,-12.47, +-4.77,-13.94, +-3.86,-14.4, +-2.6,-14.74, +-2.6,-14.74, +-0.32,-14.94, +-0,-15.33, +0.29,-15.48, +2.62,-14.88, +2.54,-14.42, +2.6,-14.74, +2.62,-14.86, +2.71,-15.38, +3.73,-15.31, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.12,-14.07, +4.9,-13.46, +7,-12.12, +7,-12.12, +7.21,-12.48, +7.57,-13.1, +7.21,-12.5, +6.82,-11.81, +7.75,-10.12, +7.75,-9.23, +6.39,-10.38, +6.18,-10.16, +6.48,-9.83, +5.54,-9.6, +5.54,-9.6, +5.78,-10.02, +6.03,-10.44, +6.24,-10.81, +7.9,-12.84, +11.69,-13.93, +11.71,-13.95, +11.62,-13.85, +13.21,-12.26, +13.85,-11.62, +13.7,-11.5, +13.4,-11.25, +13.34,-11.19, +11.39,-12.43, +11.06,-12.16, +10.66,-12.71, +10.87,-12.95, +11,-11.68, +12.21,-10.25, +10.9,-9.44, +7.93,-9.45, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.55,6.09, +-10.47,6.05, +-10.44,6.03, +-10.42,6.01, +-10.27,5.93, +-9.96,6.21, +-8.7,6.85, +-8.47,7.05, +-8.19,6.87, +-8.23,6.91, +-7.59,6.37, +-7.64,6.41, +-7.53,6.54, +-6.45,7.53, +-6.19,7.38, +-6.19,7.38, +-6.16,7.34, +-5.73,6.86, +-5.6,6.97, +-4.91,7.2, +-4.19,7.23, +-4,6.93, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.66,6.33, +-3.5,6.06, +-3.5,6.06, +-3.34,5.8, +-2.97,5.28, +-2.57,4.74, +-2.1,4.35, +-1.88,4.02, +-2.38,4.21, +-2.28,3.74, +-2.69,3.21, +-2.87,3.42, +-3.61,3.43, +-3.85,3.23, +-3.87,3.25, +-3.75,3.06, +-3.34,2.42, +-3.99,2.41, +-4.38,2.53, +-4.38,2.53, +-4.13,2.31, +-4.11,1.59, +-4.42,1.61, +-4.44,1.51, +-3.01,4.85, +-1.72,7.76, +-1.08,7.88, +0,7.97, +0.15,7.96, +1.24,8.35, +1.55,8.81, +1.53,8.7, +1.72,7.76, +1.94,7.1, +2.22,5.72, +3,5.2, +4.09,4.01, +4.21,2.77, +4.63,1.99, +3.96,0.94, +4.05,0.35, +3.69,0, +3.63,0.22, +4.04,0.47, +3.26,0.46, +2.81,0.75, +2.7,1.07, +1.93,1.18, +1.88,1.33, +1.88,1.26, +1.73,0.87, +2.2,0.69, +2.89,0.25, +2.57,-0.28, +1.83,-0.63, +1.64,-1.02, +1.38,-1.36, +1.16,-1.55, +0.97,-1.68, +1.46,-2.53, +1.12,-1.93, +1.73,-1.74, +2.44,-2.28, +2.04,-2.83, +1.46,-2.53, +0.79,-4.52, +-0,-6.03, +-0,-6.03, +-0,-6.86, +-0,-7, +-0,-7, +0,-7, +0.29,-7.51, +1.21,-7.86, +1.38,-7.85, +1.38,-7.85, +1.38,-7.85, +1.38,-7.85, +1.4,-7.96, +1.41,-7.99, +1.38,-7.85, +1.38,-7.85, +1.45,-8.21, +1.72,-8.76, +2.72,-8.49, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +2.83,-7.76, +3.02,-8.3, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.9,-10.78, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +5.61,-10.4, +6.03,-10.44, +5.65,-10.43, +1.97,-11.01, +-0,-11.4, +-0.26,-11.79, +-0.87,-12.75, +-1.99,-12.74, +-4,-13.36, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-1.16,-14.87, +-0,-14.97, +-0,-14.97, +0.81,-14.41, +2.43,-13.78, +2.43,-13.78, +2.58,-14.63, +2.68,-15.22, +2.77,-15.7, +2.77,-15.7, +5.45,-14.98, +5.45,-14.98, +5.45,-14.98, +5.29,-14.52, +6.06,-13.09, +7,-12.12, +7,-12.12, +7.4,-12.82, +7.24,-12.54, +7.65,-13.24, +7.29,-12.63, +6.94,-12.03, +7.55,-10.55, +7.44,-10.04, +6.33,-10.96, +5.86,-10.16, +5.54,-9.6, +5.54,-9.6, +5.9,-10.23, +6.19,-10.72, +6.84,-11.84, +9.92,-14.04, +12.07,-14.39, +12.14,-14.47, +11.93,-14.22, +12.7,-13.46, +14.34,-12.04, +14.1,-11.83, +13.85,-11.62, +13.74,-11.53, +12.41,-11.69, +11.94,-12.71, +11.62,-13.4, +12.06,-11.76, +12.71,-10.66, +11.68,-9.8, +10.4,-8.73, +4.69,-5.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.24,5.91, +-9.88,5.7, +-9.87,5.7, +-9.56,6.2, +-9.13,6.31, +-8.67,6.88, +-8.49,7.12, +-8.37,7.02, +-7.66,6.43, +-8.17,6.86, +-7.59,6.37, +-7.59,6.37, +-7.49,6.46, +-6.52,7.38, +-6.37,7.59, +-6.37,7.59, +-6.29,7.5, +-5.75,6.85, +-5.75,6.85, +-5.34,6.43, +-4.72,6.41, +-4.1,6.83, +-3.96,6.85, +-3.75,6.49, +-3.69,6.4, +-3.86,6.69, +-3.53,6.11, +-3.5,6.06, +-3.46,5.99, +-3.08,5.33, +-2.96,5.12, +-2.41,4.47, +-2.4,4.32, +-2.28,3.95, +-2.49,4.32, +-2.52,4.28, +-2.89,3.81, +-3,3.57, +-3.46,3.68, +-3.67,3.46, +-3.82,3.21, +-3.48,2.92, +-3.18,2.56, +-3.75,2.93, +-4.24,2.74, +-4.28,2.47, +-3.67,2.06, +-3.81,1.47, +-3.84,1.4, +-3.89,1.21, +-3.17,2.99, +-1.31,7.42, +-0.31,7.94, +0,7.97, +1.15,7.87, +1.5,8.49, +1.85,8.73, +2.87,8.35, +2.81,7.72, +2.5,6.88, +2.78,5.33, +3.59,4.82, +4.58,3.7, +4.5,2.26, +4.54,1.25, +4.05,0.35, +4.06,-0.24, +4.02,-0.71, +4.03,-0.63, +4.06,-0.3, +2.91,0.08, +2.85,0.58, +2.75,0.94, +2.72,1.05, +2.69,1.1, +2.7,1.07, +2.61,0.86, +2.64,0.34, +2.89,-0.15, +2.43,-0.67, +1.77,-0.8, +1.62,-1.06, +1.49,-1.25, +0.8,-1.68, +0.15,-1.91, +0.66,-1.79, +1.06,-1.62, +1.95,-1.71, +3.13,-2.62, +3.13,-2.62, +2.2,-3.18, +1.29,-4.27, +-0,-6.03, +-0,-6.03, +-0,-6.89, +-0,-7, +0,-7, +0,-7, +1.24,-7.05, +1.38,-7.85, +1.38,-7.85, +1.38,-7.85, +1.38,-7.85, +1.44,-8.17, +1.48,-8.4, +1.4,-7.94, +1.57,-7.35, +1.98,-7.69, +2.72,-8.16, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +2.98,-8.17, +3.06,-8.4, +3.06,-8.4, +2.72,-8.74, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.79,-10.15, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +2.32,-11.14, +2.65,-11.66, +2.87,-11.17, +1.07,-10.99, +0.35,-11.86, +1.07,-12.28, +0.85,-12.95, +-0.83,-12.59, +-2.1,-13.27, +-2.55,-14.47, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-2.6,-14.74, +-1.62,-14.83, +-0,-14.81, +-0,-14.16, +0,-14, +1.95,-14.26, +2.6,-14.74, +2.6,-14.74, +2.6,-14.74, +3.54,-14.49, +5.2,-14.29, +5.23,-14.37, +5.84,-13.88, +7.4,-12.82, +6.09,-12.28, +5.28,-12.47, +4.93,-12.63, +5.3,-12.4, +5.48,-11.76, +6.17,-12.38, +7.48,-12.96, +7.06,-12.23, +6.18,-11.83, +4.71,-12.12, +5.13,-11.03, +6.03,-10.44, +5.82,-10.08, +6.03,-10.44, +6.51,-11.28, +7.24,-12.54, +11.87,-14.15, +12.25,-14.59, +12.25,-14.59, +12.25,-14.59, +13.59,-13.59, +14.59,-12.25, +14.2,-12.64, +14.3,-12, +13.85,-11.62, +13.55,-11.37, +11.62,-13.85, +13.01,-12.46, +13.05,-10.95, +12.74,-10.69, +11.37,-9.54, +9.11,-8.55, +6.87,-8.19, +0.41,-0.7, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.68,5.59, +-9.6,5.54, +-9.6,5.54, +-9.08,6.31, +-8.68,6.87, +-8.58,7, +-8.49,7.12, +-8.31,6.98, +-7.63,6.4, +-7.76,6.51, +-7.59,6.37, +-7.59,6.37, +-7.3,6.12, +-6.54,6.27, +-5.94,7.08, +-5.87,7, +-5.84,6.97, +-5.75,6.85, +-5.52,6.58, +-5.15,6.14, +-5.05,6.17, +-4.58,6.51, +-3.86,6.19, +-3.5,6.06, +-3.5,6.06, +-3.55,6.15, +-3.51,6.08, +-3.38,5.85, +-3.07,5.31, +-3.01,5.22, +-2.96,5.13, +-2.56,4.43, +-2.53,4.38, +-2.53,4.38, +-2.51,4.35, +-2.4,4.15, +-2.38,3.93, +-2.71,3.42, +-3.08,3.67, +-3.31,3.37, +-3.38,2.83, +-3.15,2.64, +-3.13,2.62, +-3.27,2.58, +-3.83,2.34, +-4.14,2.39, +-3.55,2.02, +-3.79,1.5, +-3.84,1.4, +-3.84,1.4, +-3.26,2.59, +-0.81,7.09, +0.2,7.81, +0.69,7.91, +1.38,7.85, +2.37,8.48, +3.06,8.4, +3.06,8.4, +2.64,7.26, +2.67,6.21, +3.2,5.08, +4.15,4.34, +4.74,3.14, +4.69,1.86, +4.96,0.87, +4.06,-0.24, +3.9,-1.17, +3.84,-1.4, +3.84,-1.4, +4,-0.79, +3.28,-0.24, +2.89,0.34, +2.86,0.57, +2.74,1, +2.74,1, +2.78,0.86, +2.88,0.46, +2.92,0, +2.88,-0.42, +2.76,-0.92, +2.2,-1, +1.68,-0.97, +1.49,-1.25, +1.26,-1.43, +0.6,-1.8, +0.77,-1.78, +1.06,-1.62, +2.05,-1.57, +2.83,-2.37, +3.13,-2.62, +2.79,-2.96, +0.76,-4.99, +-0,-6.03, +-0,-6.03, +-0,-6.44, +-0,-7, +0,-7, +0.54,-6.95, +1.22,-6.89, +1.34,-7.61, +1.38,-7.85, +1.38,-7.85, +1.38,-7.85, +1.48,-8.37, +1.55,-8.81, +1.8,-8.74, +2.84,-7.79, +2.84,-7.79, +3.17,-8.71, +3.06,-8.4, +2.98,-8.17, +3,-8.25, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +2.95,-8.93, +2.87,-8.6, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.77,-10.02, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.97,-11.15, +2.09,-11.87, +2.18,-12.35, +2.12,-12.03, +1.95,-12.31, +2.23,-12.67, +2.26,-12.83, +1.13,-12.93, +0.38,-12.99, +-1.3,-14.25, +-2.6,-14.74, +-2.56,-14.53, +-2.54,-14.42, +-3.02,-14.63, +-2.55,-14.47, +-2.6,-14.74, +-1.35,-13.88, +-0,-14.13, +0,-14.81, +2.6,-14.74, +2.67,-15.17, +2.64,-14.98, +2.91,-14.66, +5.12,-14.07, +4.17,-13.82, +3.92,-13.38, +2.61,-13.11, +1.65,-12.96, +-0.29,-12.38, +-1.05,-12.12, +0.28,-12.59, +0.75,-12.88, +1.88,-12.86, +5.14,-13.47, +6.88,-13.52, +5.89,-12.87, +4.62,-12.7, +5.06,-11.78, +6.03,-10.44, +6.27,-10.86, +6.58,-11.4, +6.84,-11.84, +7.35,-12.72, +12.17,-14.5, +12.45,-14.84, +12.87,-15.34, +13.01,-15.5, +13.69,-14.52, +14.39,-13.59, +12.35,-14.72, +14.59,-12.25, +14.51,-12.18, +13.85,-11.62, +13.85,-11.62, +13.59,-11.41, +12.95,-10.87, +12.02,-10.09, +10.8,-9.07, +8.61,-8.59, +4.75,-5.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.6,5.54, +-9.6,5.54, +-9.6,5.54, +-9.6,5.54, +-9.54,5.51, +-9.31,5.87, +-8.75,6.47, +-7.84,6.58, +-7.61,6.39, +-7.59,6.37, +-7.59,6.37, +-7.5,6.29, +-6.98,5.86, +-6.58,6.04, +-5.79,6.81, +-5.76,6.84, +-5.75,6.85, +-5.5,6.55, +-5.15,6.14, +-5.12,6.11, +-5.11,6.09, +-4.8,5.72, +-4.22,5.77, +-3.74,5.9, +-3.5,6.06, +-3.5,6.06, +-3.5,6.06, +-3.18,5.51, +-3.01,5.22, +-3.08,5.18, +-3.05,4.86, +-2.55,4.41, +-2.53,4.38, +-2.53,4.38, +-2.3,3.98, +-2.12,3.67, +-2.12,3.67, +-2.52,3.31, +-2.62,3.13, +-2.91,2.86, +-3.14,2.64, +-3.13,2.62, +-3.16,2.58, +-3.43,2.2, +-3.65,2.11, +-3.6,2.08, +-3.55,2.01, +-3.73,1.64, +-3.84,1.4, +-3.51,1.24, +-2.34,3.56, +0,7, +0.9,7.78, +1.38,7.85, +2.25,7.9, +3.06,8.4, +3.06,8.4, +3.53,8.18, +3.31,6.62, +3.5,6.06, +3.59,4.82, +4.51,3.98, +5.02,2.67, +4.88,1.26, +5.03,0.1, +4.15,-0.85, +3.79,-1.5, +3.65,-1.8, +3.83,-1.67, +3.72,-1.17, +2.89,-0.32, +2.89,0.25, +2.87,0.51, +2.85,0.6, +2.83,0.65, +2.87,0.51, +2.9,0.17, +2.9,-0.17, +2.86,-0.56, +2.74,-1, +2.67,-1.15, +1.96,-1.13, +1.62,-1.06, +1.6,-1.09, +1.25,-1.58, +1,-1.66, +1.11,-1.59, +1.65,-1.28, +2.16,-1.75, +2.13,-1.98, +2.36,-2.73, +2.37,-3.75, +2.39,-4.44, +0.71,-5.54, +0,-6.39, +0,-7, +0.14,-6.99, +1.01,-6.91, +1.22,-6.89, +1.29,-7.3, +1.38,-7.85, +1.38,-7.85, +1.38,-7.85, +1.5,-8.49, +2.81,-8.47, +2.89,-8.45, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.01,-8.27, +2.99,-8.22, +3.06,-8.4, +2.89,-7.95, +3.6,-7.79, +4.07,-7.93, +3.84,-8.04, +3.06,-8.4, +2.56,-8.54, +1.69,-9.6, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.82,-10.34, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +1.92,-10.91, +2.26,-11.11, +2.11,-11.98, +2.26,-12.83, +2.23,-12.67, +2.26,-12.83, +2.29,-12.99, +2.37,-13.46, +2.43,-13.78, +2.26,-12.83, +2.35,-13.3, +-0.71,-14.38, +-2.6,-14.74, +-2.58,-14.63, +-2.49,-14.1, +-2.57,-14.58, +-2.51,-14.26, +-1.46,-13.87, +-0,-14.86, +0.43,-14.93, +2.71,-15.38, +2.77,-15.7, +2.67,-15.12, +2.31,-14.66, +1.44,-14.15, +0.81,-13.28, +-1.13,-12.93, +-1.76,-12.87, +-2.26,-12.83, +-2.26,-12.83, +-2.21,-12.51, +-1.29,-12.91, +-0,-13.02, +0,-13.35, +2.06,-13.54, +3.49,-14.25, +6.52,-13.81, +5.06,-13.91, +5.46,-13.31, +6.94,-12.02, +6.63,-11.49, +7,-12.12, +7.16,-12.4, +8.11,-13.34, +12.38,-14.76, +13.18,-15.71, +13.39,-15.96, +13.49,-16.08, +13.22,-15.75, +12.96,-15.44, +14.1,-14.1, +15.34,-12.87, +14.84,-12.45, +14.03,-11.78, +13.76,-11.55, +12.95,-10.87, +12.58,-10.56, +11.28,-9.47, +9.73,-8.47, +7.78,-8.81, +5.89,-7.02, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.6,5.54, +-9.6,5.54, +-9.55,5.51, +-9.11,5.26, +-8.75,5.05, +-8.78,5.07, +-8.74,5.25, +-7.82,6.09, +-7.67,6.28, +-7.59,6.37, +-7.54,6.33, +-7.03,5.9, +-6.85,5.75, +-6.85,5.75, +-6.71,5.9, +-6.11,6.51, +-5.58,6.65, +-5.19,6.19, +-5.12,6.11, +-5.11,6.09, +-4.86,5.79, +-4.52,5.39, +-4.5,5.36, +-4.4,5.44, +-4.2,5.59, +-3.73,5.91, +-3.47,5.81, +-3.44,5, +-3.16,5.13, +-3.26,5.06, +-3.17,4.48, +-2.6,4.33, +-2.56,4.36, +-2.35,4.08, +-2.07,3.59, +-2.07,3.59, +-2.15,3.46, +-2.57,3.13, +-2.56,3.05, +-2.88,2.83, +-3.09,2.55, +-3.3,2.4, +-3.19,2.41, +-3.36,2.08, +-3.42,1.98, +-3.52,2.03, +-3.59,1.94, +-3.84,1.4, +-3.84,1.4, +-3.05,1.09, +-2.03,3.01, +0.54,6.95, +1.31,7.44, +2.05,7.67, +2.85,7.83, +3.22,8.33, +4.16,7.89, +4.9,8.48, +3.82,6.62, +3.93,5.76, +4.04,4.45, +4.82,3.59, +5.11,2.48, +4.9,1.16, +5.03,-0.29, +4.72,-1.41, +3.57,-1.97, +3.63,-2.1, +4.14,-2.39, +3.89,-1.32, +2.9,-0.19, +2.89,0.25, +2.87,0.51, +2.87,0.51, +2.87,0.51, +2.88,0.43, +2.92,0, +2.89,-0.28, +2.84,-0.63, +2.74,-1, +2.92,-1.06, +2.53,-1.46, +2.43,-1.4, +2.18,-1.19, +2.53,-1.46, +1.54,-1.37, +1.31,-1.43, +1.55,-1.17, +1.99,-1.38, +2.37,-1.66, +2.36,-2.29, +2.6,-3.48, +2.53,-4.38, +2.08,-4.73, +0.62,-6.25, +0.61,-6.94, +1.01,-6.91, +1.22,-6.89, +1.22,-6.89, +1.3,-7.37, +1.38,-7.85, +1.38,-7.85, +1.38,-7.85, +2.46,-8.12, +2.98,-8.17, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.06,-8.4, +3.8,-7.91, +3.28,-7.84, +2.95,-8.1, +3.76,-7.48, +4.23,-7.32, +4.15,-7.18, +3.78,-7, +2.98,-8.2, +1.64,-9.33, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.77,-10.05, +1.92,-10.91, +1.92,-10.91, +2.16,-10.85, +2.34,-10.8, +2.86,-10.66, +3.79,-10.41, +3.36,-11.19, +4.14,-12.32, +3.54,-12.48, +2.81,-12.68, +2.32,-13.15, +2.43,-13.78, +2.43,-13.78, +2.58,-13.08, +2.35,-13.3, +1.71,-13.92, +-0.92,-14.42, +-2.6,-14.74, +-2.51,-14.21, +-1.93,-14.04, +-1.52,-13.86, +-0,-14, +0,-14.64, +1.73,-14.82, +2.69,-15.27, +1.62,-14.83, +1.3,-14.85, +2.73,-14.65, +0.45,-14.16, +-1.53,-13.32, +-2.3,-13.07, +-2.37,-13.46, +-2.29,-12.99, +-2.26,-12.83, +-2.26,-12.83, +-0.85,-12.95, +-0,-13.51, +0.41,-13.96, +2.51,-14.26, +2.6,-14.74, +3.58,-15.15, +7.71,-14.28, +8.05,-13.94, +7.39,-12.79, +7.12,-12.33, +7.16,-12.4, +7.48,-12.96, +8.63,-13.49, +13.19,-15.01, +14.41,-15.68, +13.6,-16.21, +13.49,-16.08, +13.49,-16.08, +14.14,-15.44, +15.86,-13.31, +15.59,-13.08, +14.84,-12.45, +14.34,-12.04, +13.58,-11.4, +12.85,-10.78, +11.96,-10.04, +10.72,-9, +8.84,-8.82, +7.51,-8.95, +4.34,-5.47, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.22,5.32, +-9.23,5.33, +-8.94,5.16, +-8.59,4.96, +-8.59,4.96, +-8.59,4.96, +-8.59,4.96, +-8.57,4.99, +-8.12,5.66, +-7.59,6.37, +-7.45,6.25, +-6.9,5.79, +-6.85,5.75, +-6.85,5.75, +-6.85,5.75, +-6.31,6.1, +-5.48,5.95, +-5.12,6.11, +-5.12,6.11, +-4.91,5.85, +-4.69,5.59, +-4.55,5.43, +-4.5,5.36, +-4.5,5.36, +-4.36,5.19, +-4.25,5.06, +-3.88,4.8, +-3.85,4.64, +-3.85,4.64, +-3.87,4.62, +-3.36,4.43, +-3.14,3.95, +-2.92,4.06, +-2.24,3.82, +-2.16,3.74, +-2.04,3.54, +-2.16,3.43, +-2.21,2.86, +-2.36,2.76, +-2.41,2.31, +-2.94,2.34, +-2.99,1.97, +-2.88,1.74, +-2.71,1.57, +-2.62,1.51, +-3.39,1.96, +-3.65,1.81, +-3.84,1.4, +-3.84,1.4, +-3.39,1.23, +-2.59,2.15, +-1.37,5.09, +-0.35,6.04, +0.79,6.72, +1.7,7.52, +2.37,7.89, +3.23,7.88, +4.05,8.25, +4.43,7.74, +4.5,5.36, +4.45,4.04, +5.02,3.3, +5.36,2.32, +4.96,0.87, +5,-0.44, +4.7,-1.8, +4.38,-2.53, +4.38,-2.53, +4.38,-2.53, +3.54,-1.11, +2.92,0, +2.91,0.07, +2.87,0.51, +2.87,0.51, +2.87,0.51, +2.9,0.17, +2.92,0, +2.87,-0.51, +2.82,-0.72, +2.74,-1, +2.74,-1, +2.66,-1.17, +2.56,-1.38, +2.7,-1.07, +2.74,-1, +2.63,-0.97, +1.54,-1.18, +1.62,-1.06, +2.2,-1.37, +2.37,-1.46, +2.29,-1.78, +2.54,-2.85, +2.78,-4, +2.73,-4.43, +1.15,-6.53, +1.22,-6.89, +1.22,-6.89, +1.22,-6.89, +1.26,-7.13, +1.55,-7.8, +2.13,-7.65, +2.73,-7.49, +2.73,-7.49, +2.89,-7.95, +2.77,-7.62, +2.84,-7.79, +2.95,-8.1, +3.06,-8.4, +3,-8.25, +3.23,-8.32, +4.19,-7.88, +3.76,-7.13, +3.99,-6.9, +3.57,-7.1, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.67,-7.05, +2.6,-8.28, +1.67,-9.44, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.81,-10.26, +2.39,-10.79, +3.52,-10.48, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.97,-10.92, +4.16,-11.44, +4.45,-12.24, +4.45,-12.24, +4.45,-12.24, +4.43,-12.91, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.49,-13.23, +3.83,-12.85, +3.65,-12.58, +0.81,-14.1, +-0.41,-13.96, +-0,-14.12, +-0,-14.38, +0,-14.55, +1.3,-14.85, +2.68,-15.22, +2.74,-15.56, +2.92,-14.2, +4.66,-13.9, +5.23,-14.37, +5.29,-14.52, +5.26,-14.46, +4.87,-14.64, +3.07,-14.29, +-0,-14, +-0.38,-13.96, +-0.25,-13.43, +-0,-13.46, +0,-14, +1.68,-14.17, +2.62,-14.88, +3.12,-15.6, +5.54,-16.11, +6.39,-16.34, +8.26,-15.34, +7.85,-13.59, +7.48,-12.96, +7.48,-12.96, +7.88,-12.86, +9.88,-12.97, +12.45,-14.84, +16.72,-14.03, +16.16,-14.91, +14.22,-16.95, +14.43,-17.2, +14.01,-16.7, +15.43,-14.42, +15.9,-13.34, +14.84,-12.45, +14.22,-11.93, +13.25,-11.12, +12.49,-10.48, +11.55,-9.69, +10.37,-9.15, +8.27,-9.85, +7.28,-8.68, +3.25,-4.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.64,4.99, +-8.59,4.96, +-8.59,4.96, +-8.59,4.96, +-8.38,4.84, +-8.08,4.67, +-8.47,4.89, +-8.56,4.94, +-8.38,5.27, +-7.72,5.44, +-7.02,5.82, +-6.85,5.75, +-6.85,5.75, +-6.85,5.75, +-6.54,5.48, +-6.25,5.25, +-6.04,5.19, +-5.54,5.71, +-5.16,6.08, +-4.71,5.5, +-4.58,5.29, +-4.52,5.34, +-4.5,5.36, +-4.35,5.19, +-3.92,4.67, +-3.87,4.62, +-3.61,4.3, +-3.55,4.23, +-3.52,4.19, +-3.75,4.47, +-3.37,4.02, +-3.17,3.86, +-2.42,3.63, +-2.04,3.54, +-2.07,3.58, +-1.95,3.39, +-1.64,2.84, +-1.8,2.37, +-2.02,2.33, +-2.22,1.93, +-2.62,1.85, +-2.5,1.6, +-2.9,1.68, +-2.53,1.45, +-2.56,1.47, +-3.48,1.83, +-3.8,1.48, +-3.84,1.4, +-3.84,1.4, +-3.66,1.46, +-2.58,2.52, +-1.12,4.21, +-0.1,5.67, +0.79,6.5, +1.22,6.89, +2.1,7.51, +2.73,7.49, +2.73,7.49, +4.48,7.8, +4.23,7.32, +3.99,6.51, +4.54,4.28, +5.06,2.24, +5.02,0.35, +4.9,-1.16, +4.63,-1.99, +4.38,-2.53, +4.38,-2.53, +4.38,-2.53, +4.63,-1.99, +3.11,0, +2.92,0, +2.91,0.11, +2.89,0.34, +2.9,0.14, +3.11,0, +3.49,-0.09, +3.51,-0.62, +2.97,-0.64, +2.74,-1, +2.74,-1, +2.74,-1, +2.72,-1.04, +3.29,-1.2, +2.76,-0.93, +2.01,-0.61, +1.74,-0.84, +1.88,-0.29, +2.03,-0.58, +2.02,-0.48, +2.85,-1.29, +3.25,-2.42, +3.15,-2.9, +3.29,-4.1, +2.1,-6.11, +1.22,-6.89, +1.22,-6.89, +1.22,-6.89, +1.59,-7.24, +2.53,-7.54, +2.73,-7.49, +2.73,-7.49, +2.62,-7.18, +2.62,-7.18, +2.73,-7.49, +2.73,-7.49, +2.73,-7.49, +2.86,-7.85, +3.2,-8.34, +4.27,-7.84, +4.47,-7.74, +3.99,-6.9, +3.67,-7.05, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.29,-7.23, +2.52,-8.44, +1.66,-9.4, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +3.31,-9.79, +3.48,-10.5, +3.79,-10.41, +3.79,-10.41, +3.91,-10.75, +4.07,-11.17, +4.18,-11.48, +4.42,-12.14, +4.45,-12.24, +4.45,-12.24, +4.57,-12.54, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.79,-13.15, +4.51,-12.39, +4.45,-12.24, +4,-12.58, +4.19,-13.26, +2.16,-14.38, +2.6,-14.74, +2.98,-14.89, +3.83,-14.86, +4.11,-14.76, +4.79,-13.15, +4.45,-12.24, +4.62,-12.7, +4.79,-13.15, +4.95,-13.61, +4.7,-12.92, +4.68,-12.85, +4.75,-13.04, +4.29,-13.46, +0.73,-14.7, +0.73,-14.01, +2.56,-14.03, +1.24,-14.01, +2.58,-14.62, +3.12,-15.6, +5.38,-16.2, +6.74,-16.91, +8.57,-16.41, +9.22,-15.98, +8.57,-14.84, +7.81,-13.52, +8.19,-13.26, +9.65,-12.53, +10.87,-12.95, +12.14,-14.47, +15.47,-14.57, +17.24,-14.47, +17.57,-14.74, +15.21,-17.1, +14.85,-16.95, +15.88,-14.15, +15.55,-13.05, +14.84,-12.45, +14.22,-11.93, +13.25,-11.12, +12.46,-10.45, +10.97,-9.84, +8.91,-10.62, +8.27,-9.85, +4.44,-5.29, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.59,4.96, +-8.59,4.96, +-8.59,4.96, +-8.47,4.89, +-7.89,4.56, +-7.74,4.47, +-7.79,4.5, +-8.2,4.73, +-7.91,4.57, +-7.75,4.5, +-7.44,4.93, +-7.17,5.31, +-6.85,5.75, +-6.55,5.5, +-6.12,5.14, +-6.11,5.12, +-6.11,5.12, +-5.98,5.02, +-5.41,4.77, +-5.44,4.73, +-5.33,4.57, +-4.91,4.93, +-4.51,4.9, +-4.09,4.65, +-4.04,4.46, +-3.87,4.62, +-3.64,4.33, +-3.31,3.95, +-3.64,4.34, +-3.25,3.87, +-2.98,3.56, +-2.75,3.7, +-2.04,3.54, +-2.04,3.54, +-2.04,3.54, +-1.65,2.86, +-1.47,2.55, +-1.75,2.33, +-1.88,2.23, +-2.1,2.02, +-2.47,1.54, +-2.67,1.16, +-2.66,1.33, +-2.68,1.13, +-2.67,1.17, +-3.35,1.32, +-3.8,1.37, +-3.5,1.44, +-4.18,1.63, +-2.57,1.96, +-0.97,3.08, +0.29,4.47, +0.93,5.3, +1.5,6.32, +2.13,6.65, +2.64,6.82, +3.29,7.23, +3.81,6.99, +4.79,7.01, +4.33,6.66, +4.37,5.45, +4.27,4.06, +4.88,1.89, +4.33,1.06, +4.55,-0.31, +4.4,-0.78, +4.32,-2.09, +4.38,-2.53, +4.32,-2.22, +4.27,-1.55, +3.95,0, +3.18,0, +2.92,0, +2.92,0, +3.06,0, +4.08,0, +4.06,-0.24, +4.02,-0.71, +3.86,-0.68, +2.86,-1.04, +2.74,-1, +2.74,-1, +2.74,-1, +2.74,-1, +3,-0.59, +2.69,-0.47, +1.91,-0.26, +1.91,0.23, +1.93,0.11, +2.53,-0.19, +3.08,-0.65, +3.28,-1.36, +3.33,-2.33, +3.78,-3.53, +1.95,-6.69, +1.33,-6.86, +1.48,-6.82, +1.61,-6.79, +2.5,-6.88, +2.73,-7.49, +2.73,-7.49, +2.58,-7.08, +2.39,-6.58, +2.62,-7.18, +2.91,-7.41, +3.48,-7.14, +3.86,-6.96, +3.78,-7, +3.78,-7, +4.17,-7.22, +4.31,-7.46, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.98,-6.9, +3.99,-6.9, +3.15,-7.29, +2.84,-7.79, +1.67,-9.49, +1.72,-9.76, +1.72,-9.76, +1.72,-9.76, +1.96,-9.7, +3.21,-9.37, +3.52,-9.68, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.88,-10.67, +4.12,-11.33, +4.26,-11.69, +4.45,-12.24, +4.45,-12.24, +4.5,-12.35, +4.75,-13.05, +4.79,-13.15, +4.79,-13.15, +5.42,-12.86, +5.89,-12.64, +6.01,-11.99, +5.48,-11.76, +5.83,-11.6, +5.92,-11.55, +5.24,-12.41, +5.36,-12.53, +4.79,-13.15, +5.81,-12.5, +6.31,-11.73, +6.17,-11.54, +5.75,-11.1, +5.4,-11.62, +6.51,-11.28, +6.51,-11.28, +6.55,-11.35, +6.3,-10.92, +6.43,-11.14, +5.72,-11.47, +5.61,-11.17, +5.66,-12.3, +5.09,-12.25, +5.51,-11.92, +4.15,-14.01, +4.05,-15.58, +5.3,-16.18, +6.59,-16.58, +9.12,-15.8, +9.39,-16.26, +9.69,-16.78, +9.71,-16.81, +8.39,-14.53, +10.01,-12.54, +10.78,-12.85, +10.87,-12.95, +11.31,-13.48, +15.23,-14.51, +17.45,-14.64, +17.94,-15.06, +17.26,-15.06, +16.78,-15.23, +16.58,-13.91, +15.46,-12.97, +15.26,-12.8, +14.41,-12.09, +13.4,-11.25, +12.53,-10.51, +9.81,-10.5, +6.04,-7.2, +3.23,-3.85, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.59,4.96, +-8.59,4.96, +-8.25,4.76, +-7.91,4.57, +-7.74,4.47, +-7.74,4.47, +-7.74,4.47, +-7.74,4.47, +-7.73,4.46, +-7.41,4.28, +-7.7,4.44, +-7.64,4.64, +-7.05,5.4, +-6.24,5.14, +-6.02,5.05, +-5.89,4.94, +-5.89,4.94, +-5.51,4.62, +-5.56,4.66, +-5.55,4.66, +-5.31,4.46, +-4.78,4.01, +-4.57,3.93, +-4.49,4.01, +-4.57,3.93, +-4.41,4.09, +-4.21,3.98, +-3.44,3.84, +-3.25,3.87, +-3.08,3.67, +-2.62,3.13, +-2.53,3.54, +-2.08,3.61, +-1.87,3.62, +-1.81,3.34, +-1.53,2.67, +-1.46,2.53, +-1.53,2.48, +-1.7,2.01, +-2.12,1.83, +-2.56,1.36, +-2.58,0.66, +-2.7,0.92, +-2.74,1, +-2.74,1, +-2.9,1.05, +-3.78,1.36, +-2.9,1.24, +-2.96,2.15, +-1.28,2.55, +-0.04,3.73, +1.25,4.54, +1.86,5.11, +2.46,5.8, +3.06,6.27, +3.9,6.76, +3.99,6.9, +4.36,6.64, +5.19,6.19, +4.91,5.86, +4.45,4.49, +4.24,3.56, +3.85,2.34, +3.72,2.15, +4.56,1.87, +3.84,1.4, +3.9,1.17, +4.02,-0.71, +4.05,-0.39, +4.08,-0.08, +4.08,0, +4.08,0, +3.43,0, +3.82,0, +4.08,0, +4.08,0, +4.04,-0.51, +4.02,-0.71, +4.23,-0.75, +4.09,-0.9, +2.92,-1.06, +2.74,-1, +2.75,-0.94, +2.86,-0.56, +2.87,-0.51, +3.45,-0.44, +2.4,0.31, +2.79,0.11, +2.41,0.52, +3,0.59, +3.06,-0.08, +3.05,-1.11, +3.6,-1.91, +3.74,-3.39, +2.39,-6.58, +2.39,-6.58, +2.39,-6.58, +2.39,-6.58, +2.39,-6.58, +2.54,-6.97, +2.45,-6.73, +2.39,-6.58, +2.64,-6.46, +3.37,-6.87, +3.85,-6.97, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.86,-6.96, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +2.87,-7.42, +2.87,-7.9, +1.86,-9.06, +1.72,-9.76, +1.72,-9.76, +2.03,-9.68, +3.02,-9.42, +3.52,-9.68, +3.72,-10.23, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.9,-10.72, +4.12,-11.33, +4.76,-11.43, +5.37,-11.81, +4.75,-12.1, +4.73,-13, +5.89,-12.64, +6.51,-12.35, +6.8,-12.21, +7,-12.12, +7,-12.12, +6.84,-11.84, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.45,-11.17, +6.03,-10.44, +6.23,-10.8, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.44,-11.16, +6.03,-10.44, +6.13,-10.62, +6.27,-10.86, +6.03,-10.44, +6.03,-10.44, +6.03,-10.44, +6.21,-10.75, +6.92,-11.98, +7.58,-13.85, +8.5,-14.78, +9.04,-15.66, +9.04,-15.66, +9.34,-16.18, +10.09,-17.48, +11.96,-16.65, +9.96,-14.67, +10.4,-12.4, +10.87,-12.95, +11,-13.1, +11.14,-13.27, +14.7,-14.88, +17.82,-14.95, +18.32,-15.37, +17.66,-14.82, +17.32,-14.54, +16.58,-13.91, +15.34,-12.87, +15.62,-13.1, +15.02,-12.6, +13.82,-11.59, +11.44,-10.84, +1.5,-1.79, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.59,4.96, +-8.27,4.77, +-7.78,4.49, +-7.74,4.47, +-7.6,4.39, +-7.3,4.22, +-7.19,4.15, +-7.54,4.35, +-7.24,4.18, +-6.93,4, +-7.44,4.3, +-7.67,4.43, +-7.09,4.23, +-6.46,4.31, +-5.56,4.45, +-5.48,4.6, +-5.67,4.76, +-5.36,4.5, +-5.39,4.53, +-5.36,4.5, +-5.34,4.48, +-4.91,4.12, +-4.62,3.87, +-4.47,3.75, +-4.47,3.75, +-4.45,3.73, +-4.29,3.6, +-3.64,3.24, +-3.2,3.72, +-3.02,3.6, +-2.56,3.18, +-2.36,3.35, +-2.05,3.55, +-1.91,3.47, +-1.23,2.89, +-1.21,2.65, +-1.34,2.54, +-1.16,1.92, +-1.29,1.55, +-1.71,1.38, +-2.52,1, +-2.05,0.42, +-2.09,0.54, +-2.62,0.8, +-2.68,0.97, +-1.04,1.32, +2.28,0.37, +-2.25,2.5, +-1.83,2.63, +-0.36,3.37, +1.05,4.03, +2.35,4.46, +2.85,4.94, +3.15,5.46, +3.67,5.94, +4.62,6.46, +5,6.19, +5.12,6.11, +5.12,6.11, +4.89,5.2, +4.2,3.83, +3.87,3.25, +4.02,2.66, +4.36,2.18, +4.75,1.73, +4.24,1.54, +3.9,1.17, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.03,-0.63, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +3.76,-0.66, +3.47,-0.78, +3.02,-0.53, +2.87,-0.51, +3.04,-0.54, +3.26,-0.52, +3.11,0.41, +3.02,0.61, +4.03,0.59, +3.98,0.79, +4.32,0.03, +4.17,-0.98, +3.69,-1.72, +3.94,-4.29, +2.67,-6.45, +2.39,-6.58, +2.39,-6.58, +2.39,-6.58, +2.39,-6.58, +2.39,-6.58, +2.64,-6.46, +3.25,-6.18, +3.5,-6.06, +3.92,-6.78, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.67,-7.05, +3.99,-6.9, +3.81,-6.99, +3.71,-7.03, +3.71,-7.03, +3.15,-7.29, +2.78,-7.64, +3.06,-8.4, +2.28,-8.95, +1.72,-9.76, +1.72,-9.76, +2.28,-9.61, +3.68,-10.1, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.02,-10.5, +5.21,-10.82, +5.87,-10.87, +6.51,-11.28, +6.51,-11.28, +7,-12.12, +7,-12.12, +7,-12.12, +7,-12.12, +7,-12.12, +7,-12.12, +7,-12.12, +6.51,-11.28, +6.98,-10.95, +7.13,-10.85, +7.31,-10.72, +7.44,-10.63, +8.06,-10.19, +7.42,-10.38, +6.42,-10.28, +6.27,-10.86, +6.51,-11.28, +6.51,-11.28, +7.13,-10.85, +6.88,-11.02, +6.51,-11.28, +6.51,-11.28, +6.51,-11.28, +6.2,-10.74, +5.97,-10.33, +6.03,-10.44, +6.03,-10.44, +6.84,-10.55, +8.31,-10.81, +9.28,-12.49, +9.14,-14.91, +9.04,-15.66, +9.28,-16.08, +10.11,-16.74, +12.6,-15.92, +13.18,-15.71, +10.92,-13.01, +10.25,-12.21, +10.56,-12.58, +10.77,-12.83, +11.14,-13.27, +15.9,-14.53, +18.26,-15.32, +18.32,-15.37, +17.85,-14.98, +17.32,-14.54, +16.33,-13.7, +15.75,-13.22, +15.46,-12.97, +14.72,-12.35, +13.36,-11.21, +5.46,-5.22, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.28,4.78, +-7.74,4.47, +-7.35,4.12, +-7.56,3.72, +-7.26,3.37, +-7.18,3.41, +-7.1,3.59, +-6.96,3.88, +-6.9,3.98, +-6.9,3.98, +-6.96,4.02, +-7.11,4.1, +-6.9,3.98, +-6.51,3.76, +-5.97,3.65, +-5.74,3.97, +-5.66,4.1, +-5.42,4.43, +-5.56,4.24, +-5.7,4.03, +-5.49,3.81, +-5.04,3.4, +-4.96,3.32, +-4.41,2.96, +-3.93,3.17, +-3.85,3.21, +-3.44,2.89, +-3.21,2.74, +-2.75,3.2, +-2.61,3.11, +-2.29,3.29, +-2.02,3.5, +-1.96,3.45, +-1.27,3.02, +-1.01,2.76, +-1,2.74, +-0.87,2.53, +-0.74,1.78, +-1.23,1.51, +-1.49,1.18, +-1.88,0.45, +-1.88,0.47, +-1.84,0.64, +-2.24,0.63, +0.7,-0.16, +2.85,-0.61, +-0.5,1.6, +-2.33,3.33, +-1.18,3.43, +0.08,4.05, +1.37,4.16, +2.53,4.38, +3.2,4.5, +4,4.97, +4.64,5.53, +5.23,6, +5.4,5.82, +5.78,5.45, +5.89,5.34, +5.03,4.22, +4.12,3.46, +3.87,3.25, +4.29,2.65, +4.63,1.99, +4.75,1.73, +4.34,1.58, +3.9,1.17, +4.07,0.12, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.08,0, +4.06,-0.3, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.13,-0.73, +4.16,-0.73, +4.02,-0.71, +3.36,-0.59, +2.87,-0.51, +2.87,-0.51, +3.04,-0.54, +3.55,0.2, +4.01,0.71, +4.02,0.71, +4.03,1.07, +4.87,-0.26, +4.12,-1.84, +4.77,-2.46, +4.17,-5.59, +3.5,-6.06, +2.58,-6.49, +2.39,-6.58, +2.39,-6.58, +2.39,-6.58, +2.81,-6.38, +3.5,-6.06, +3.5,-6.06, +3.5,-6.06, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.81,-6.99, +3.36,-7.2, +2.94,-7.39, +2.8,-7.69, +3.02,-8.3, +3.06,-8.4, +2.65,-8.65, +1.72,-9.76, +1.72,-9.76, +2.28,-9.61, +3.66,-10.05, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.29,-10.18, +5.25,-9.73, +6.27,-10.86, +6.46,-11.19, +6.51,-11.28, +6.67,-11.56, +7,-12.12, +7,-12.12, +7,-12.12, +7,-12.12, +7,-12.12, +6.84,-11.84, +7.3,-10.84, +8.17,-10.12, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.28,-9.87, +8.37,-9.98, +7.75,-10.41, +8.14,-10.14, +8.37,-9.98, +8.37,-9.98, +8.64,-10.3, +8.79,-10.47, +8.91,-10.62, +8.68,-10.35, +8.45,-10.07, +7.32,-9.53, +7.1,-9.68, +7.77,-9.81, +8.61,-10.26, +9.31,-11.09, +10.18,-12.13, +9.76,-14.38, +9.18,-15.9, +11.35,-15.73, +12.87,-15.34, +12.87,-15.34, +12.87,-15.34, +10.84,-12.92, +9,-10.72, +9.55,-11.38, +10.25,-12.21, +11.08,-13.21, +14.29,-15.06, +18.49,-15.51, +18.48,-15.5, +17.94,-15.06, +17.32,-14.54, +16.33,-13.7, +15.74,-13.21, +15.23,-12.78, +14.34,-12.04, +11,-9.23, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.91,4.57, +-7.61,4.4, +-7.12,3.79, +-7.43,2.96, +-7.45,2.83, +-7.49,2.73, +-7.47,2.72, +-7.35,2.74, +-7.43,2.88, +-7.35,3.05, +-7.07,3.67, +-7.04,3.71, +-6.92,3.95, +-6.82,3.94, +-6.21,3.59, +-6.06,3.5, +-6.06,3.5, +-6.04,3.53, +-5.94,3.69, +-5.97,3.65, +-6.04,3.49, +-5.56,3.21, +-5.22,3.01, +-5.07,2.93, +-4.43,2.56, +-4.33,2.61, +-3.85,2.55, +-3.17,2.57, +-3.05,2.67, +-2.56,2.95, +-2.31,2.96, +-1.61,2.66, +-1.74,3.15, +-1.19,2.88, +-0.64,2.88, +-0.54,2.86, +-0.69,2.34, +-0.36,2.02, +-0.46,1.88, +-0.93,1.58, +-1.18,0.82, +-1.93,0.04, +-1.85,0.54, +-1.3,1.04, +1.36,0.42, +2.88,-0.36, +2.87,-0.51, +-2.54,3.19, +-1.83,3.64, +-0.7,3.99, +0.53,4.04, +1.83,4.17, +2.71,4.25, +3.8,4.53, +4.49,4.76, +5.21,4.99, +5.94,5.29, +6.11,5.12, +6.44,5.4, +6.11,5.12, +5.04,4.23, +4.37,3.1, +4.19,2.8, +4.38,2.53, +4.64,1.97, +4.75,1.73, +3.99,1.45, +3.93,1.05, +4.07,0.14, +4.08,0, +4.08,0, +4.08,0, +4.08,-0.08, +4.06,-0.24, +4.05,-0.41, +4.04,-0.47, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.05,-0.41, +4.05,-0.35, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +3.64,-0.64, +2.87,-0.51, +2.87,-0.51, +3.4,-0.28, +4.02,0.71, +4.02,0.71, +4.41,0.65, +4.59,-0.32, +3.7,-1.6, +4.86,-2.8, +4.95,-4.68, +3.64,-5.96, +3.5,-6.06, +3.25,-6.18, +2.95,-6.32, +3.36,-6.12, +3.5,-6.06, +3.5,-6.06, +3.38,-5.85, +3.5,-6.06, +3.74,-6.48, +3.82,-6.62, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.98,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.36,-7.2, +2.73,-7.49, +2.73,-7.49, +2.89,-7.95, +3.06,-8.4, +3.12,-8.59, +3.19,-8.77, +1.72,-9.76, +1.72,-9.76, +2.28,-9.61, +3.52,-9.68, +3.79,-10.41, +3.79,-10.41, +3.79,-10.41, +4.57,-10.05, +5.54,-9.6, +5.54,-9.6, +6.27,-10.86, +6.51,-11.28, +6.57,-11.38, +6.78,-11.74, +7,-12.12, +7,-12.12, +7,-12.12, +7,-12.12, +7.25,-11.95, +8.14,-10.33, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +8.48,-10.1, +9,-10.72, +9.2,-10.97, +9.47,-11.28, +9.22,-10.99, +8.58,-10.23, +8.06,-9.6, +8.28,-9.87, +8.62,-10.27, +9,-10.72, +9.31,-11.09, +10.04,-11.96, +10.9,-12.99, +11.65,-14.4, +12.77,-15.21, +12.87,-15.34, +12.84,-14.45, +12.56,-14.59, +10.62,-12.66, +9.86,-9.86, +10.07,-10.11, +10.13,-12.08, +11.72,-12.57, +14.06,-14.14, +18.24,-15.31, +18.61,-15.62, +18.13,-15.21, +17.32,-14.54, +15.94,-13.97, +15.59,-13.08, +14.87,-12.48, +14.06,-11.8, +8.9,-7.47, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.74,4.47, +-7.71,4.22, +-7.37,3, +-7.24,2.63, +-7.5,2.7, +-7.52,2.09, +-7.19,1.83, +-6.86,2.15, +-7.41,2.7, +-7.49,2.73, +-7.49,2.73, +-7.48,2.74, +-7.21,3.36, +-6.95,3.76, +-6.12,3.53, +-6.06,3.5, +-6.06,3.5, +-6.06,3.5, +-6.06,3.5, +-6.06,3.5, +-5.73,3.25, +-5.25,3.03, +-5.16,2.98, +-4.67,2.46, +-4.41,2.46, +-4.34,2.49, +-3.93,2.3, +-3.33,2.34, +-2.85,2.69, +-1.9,2.3, +-1.55,2.47, +-1.42,2.98, +-1,2.83, +-0.55,2.86, +-0.33,2.89, +-0.24,2.9, +-0.25,2.2, +-0.09,1.93, +-0.15,1.93, +-0.55,1.59, +0.97,0.7, +0.56,-0.25, +1.93,-0.15, +2.12,0, +2.74,-0.13, +2.89,-0.37, +1.58,0.38, +-2.11,3.49, +-1.24,3.88, +-0.1,4.06, +1.25,4.02, +2.31,4.36, +3.31,4.09, +4.45,4.19, +5.36,4.5, +5.68,4.77, +6.11,5.12, +6.19,5, +6.3,4.84, +5.98,5.02, +4.9,3.84, +4.47,2.58, +4.38,2.53, +4.48,2.3, +4.75,1.73, +4.75,1.73, +4.16,1.43, +4,0.79, +4.02,0.71, +4.06,0.2, +4.08,0, +4.07,-0.16, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.04,-0.47, +4.08,0, +4.08,0, +4.08,0, +4.03,-0.61, +4.02,-0.71, +3.56,-0.63, +3,-0.53, +2.87,-0.51, +2.85,-0.59, +3.99,-1.43, +4.35,-1.15, +4.68,-1.95, +5.22,-3.01, +4.38,-2.53, +4.05,-2.57, +4.69,-4.11, +4.37,-5.45, +4.16,-6.39, +3.62,-6.27, +3.5,-6.06, +3.5,-6.06, +3.5,-6.06, +3.26,-5.64, +3.26,-5.05, +3.59,-4.82, +4.32,-5.36, +3.75,-6.08, +3.9,-6.76, +3.99,-6.9, +3.98,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +4.39,-7.6, +3.41,-8.08, +3.06,-8.4, +3.06,-8.4, +3.11,-8.55, +3.49,-8.74, +2.61,-9.41, +1.72,-9.76, +2.2,-9.64, +3.52,-9.68, +3.79,-10.41, +4.11,-10.26, +4.37,-10.14, +5.54,-9.6, +5.54,-9.6, +5.78,-10.02, +6.35,-11, +6.51,-11.28, +6.58,-11.4, +6.65,-11.52, +7,-12.12, +7,-12.12, +7.33,-11.89, +8.33,-11.19, +9,-10.72, +8.54,-10.18, +8.37,-9.98, +8.37,-9.98, +9.44,-8.91, +8.91,-9.44, +9.17,-9.17, +8.87,-9.65, +10.21,-9.06, +10.07,-8.87, +9.86,-9.25, +9.89,-9.63, +9.49,-9.89, +8.68,-10.35, +8.79,-10.47, +9.23,-11, +9.62,-11.47, +9.62,-11.47, +9.62,-11.47, +9.27,-11.05, +8.37,-9.98, +8.37,-9.98, +8.84,-10.54, +9,-10.72, +9.35,-11.15, +10.04,-11.96, +10.87,-12.95, +12.09,-14.41, +12.87,-15.34, +12.45,-14.84, +12.15,-11.99, +12.33,-14.08, +12.15,-14.48, +10.54,-10.94, +10.45,-9.61, +10.25,-12.21, +12.51,-11.93, +14.92,-13.12, +18.2,-15.27, +18.76,-15.74, +18.1,-15.19, +17.24,-14.47, +15.94,-13.97, +15.59,-13.08, +14.84,-12.45, +12.89,-10.81, +6.03,-5.97, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.15,3.35, +-7.72,3.04, +-7.21,2.56, +-7.34,2.33, +-7.34,1.46, +-7.22,1.01, +-7.39,1.19, +-6.95,1.35, +-7.52,1.66, +-7.41,1.92, +-7.26,2.39, +-7.46,2.72, +-7.49,2.73, +-7.02,2.73, +-6.42,2.81, +-6.14,3.34, +-6.12,3.39, +-5.9,3.21, +-5.47,2.94, +-5.52,2.7, +-5.66,2.25, +-5.46,2.34, +-4.92,2.03, +-4.71,1.82, +-4.61,1.85, +-3.77,2.07, +-3.39,1.95, +-2.58,1.91, +-2.09,2.27, +-1.67,2.38, +-1.36,2.63, +-0.97,3.37, +-0.55,2.95, +-0.47,2.88, +-0.19,2.9, +-0.12,2.76, +0.14,2.04, +0.24,1.92, +0.23,1.93, +0.44,1.83, +1.48,0.37, +1.9,0.23, +1.93,-0.15, +2.04,-0.01, +2.65,-0.36, +2.87,-0.51, +-0.45,1.98, +-2.02,4.08, +-1.08,4.09, +0.4,4.41, +1.92,4.42, +2.85,4.15, +3.82,4.08, +5.11,4.29, +5.44,4.39, +6.58,4.44, +7.28,4.38, +7.04,4.07, +6.59,3.8, +5.67,3.55, +4.93,2.62, +4.7,1.84, +4.63,1.99, +4.71,1.82, +4.75,1.73, +4.75,1.73, +4.86,1.3, +4.57,0.81, +4.45,0.78, +4.04,0.47, +4.08,0, +4.05,-0.35, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.02,-0.71, +4.04,-0.43, +4.08,0, +4.08,0, +4.06,-0.2, +4.13,-0.73, +4.26,-1.05, +4.44,-1.29, +4.38,-2.09, +3.98,-2.08, +4.38,-2.53, +4.38,-2.53, +4.66,-2.69, +5.22,-3.01, +5.22,-3.01, +4.1,-2.36, +4.83,-3.14, +4.72,-3.96, +3.95,-4.71, +4.69,-5.58, +4.55,-6.11, +4,-5.71, +3.67,-5.94, +3.75,-5.89, +3.67,-5.15, +3.87,-4.62, +3.87,-4.62, +4.29,-5.11, +4.39,-5.44, +4.05,-6.32, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +4.11,-7.11, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.12,-7.91, +3.46,-8.21, +4.12,-7.91, +4.16,-7.89, +4.53,-7.85, +4.47,-7.74, +4.96,-8.59, +2.96,-9.39, +4.68,-8.84, +5.32,-9.7, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.66,-9.81, +5.95,-10.3, +6.35,-11, +6.51,-11.28, +6.51,-11.28, +6.92,-11.98, +7,-12.12, +8.14,-11.32, +9,-10.72, +9,-10.72, +9,-10.72, +9,-10.72, +8.62,-9.88, +9.78,-8.57, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +10.31,-8.65, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +9.57,-10.15, +9,-10.72, +9.53,-11.36, +9.68,-11.54, +9.71,-11.57, +9.73,-11.59, +9.62,-11.47, +9,-10.72, +8.58,-10.23, +9,-10.72, +9,-10.72, +9.15,-10.91, +9.7,-11.56, +10.65,-12.15, +13.18,-11.74, +13.02,-13.43, +12.25,-14.59, +12.64,-15.06, +12.88,-14.59, +12.87,-15.34, +11.27,-10.73, +10.45,-9.61, +10.59,-11.86, +13.25,-11.12, +15.24,-12.79, +17.05,-15.36, +19.21,-16.12, +18.07,-15.16, +16.74,-14.05, +16.08,-13.49, +15.46,-12.97, +14.25,-11.96, +10.49,-9.62, +6.56,-9.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.67,2.79, +-7.46,2.66, +-7.42,1.88, +-7.47,1.26, +-7.22,0.32, +-7.87,0, +-7.92,0.17, +-7.32,0.67, +-7.68,0.79, +-7.53,1.29, +-6.9,1.41, +-6.97,2.36, +-7.47,2.72, +-6.81,2.48, +-6.58,2.39, +-6.53,2.51, +-6.27,2.35, +-5.67,2.09, +-5.66,2.06, +-5.66,2.06, +-5.66,2.06, +-5.36,1.95, +-5,1.81, +-4.75,1.53, +-4.11,1.49, +-3.49,1.61, +-2.61,1.42, +-2.04,2.07, +-1.58,2.44, +-1.29,2.6, +-1.06,2.97, +-0.73,3.33, +-0.36,2.89, +-0.2,2.91, +0.17,2.77, +0.26,2.42, +0.34,1.91, +0.49,1.88, +0.51,1.86, +1.18,1.51, +1.02,0.41, +1.57,0.05, +1.94,-0.11, +2.5,-0.07, +2.85,-0.49, +2.87,-0.51, +-0.85,2.66, +-1.67,4.6, +-0.43,4.69, +1.13,4.89, +2.23,4.52, +3.44,4.33, +4.86,4.23, +5.8,4.37, +6.72,3.88, +7.04,4.07, +7.38,4.26, +6.43,3.71, +5.5,3.17, +5.16,2.57, +4.69,1.86, +4.75,1.73, +4.75,1.73, +4.75,1.73, +4.75,1.73, +4.75,1.73, +4.88,1.26, +4.76,0.84, +4.26,0.75, +4.04,0.47, +4.08,0, +4.03,-0.55, +4.02,-0.71, +4.02,-0.71, +3.96,-0.94, +4.02,-0.71, +4.02,-0.71, +4.45,-0.78, +4.36,-0.5, +4.49,-0.13, +4.68,-0.59, +4.88,-0.78, +4.91,-1.12, +4.75,-1.73, +4.52,-2.23, +4.28,-2.47, +4.17,-2.41, +4.38,-2.53, +4.38,-2.53, +4.38,-2.53, +4.94,-2.85, +5.22,-3.01, +4.5,-2.6, +4.19,-2.8, +4.78,-4.01, +4.83,-4.34, +4.2,-4.28, +3.98,-4.74, +4.5,-5.36, +4.5,-5.36, +4.5,-5.36, +4.19,-4.99, +3.87,-4.62, +3.87,-4.62, +4.44,-5.29, +4.5,-5.36, +5.02,-5.98, +4.49,-6.55, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +4.26,-7.38, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.71,-8.16, +4.96,-8.59, +5.12,-8.87, +5.54,-9.6, +5.54,-9.6, +5.54,-9.6, +5.78,-10.02, +6.03,-10.44, +6.03,-10.44, +6.62,-10.91, +7.13,-10.85, +6.82,-11.06, +7.66,-11.65, +8.75,-10.9, +9,-10.72, +9,-10.72, +9.14,-10.89, +9,-10.72, +9.28,-10.43, +10.6,-8.89, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +9.98,-8.37, +10.35,-8.68, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.8,-9.07, +10.97,-9.2, +11.2,-9.88, +9.85,-11.24, +9.62,-11.47, +10.25,-12.21, +10.04,-11.96, +9.31,-11.09, +9.19,-10.53, +9.43,-10.29, +9.57,-10.15, +9.86,-9.86, +10.23,-9.9, +11.42,-10.01, +12.83,-10.77, +13.43,-11.99, +12.51,-14.33, +12.73,-15.17, +12.87,-15.34, +12.88,-14.59, +11.33,-10.44, +10.48,-9.55, +11.08,-10.79, +12.73,-10.68, +14.46,-12.14, +17.58,-15.35, +19.1,-16.02, +17.78,-14.92, +16.51,-13.85, +15.87,-13.32, +15.01,-12.59, +12.82,-10.76, +8.35,-9.32, +5.8,-9.41, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.69,2.75, +-7.63,2.02, +-7.89,0.79, +-7.82,0.04, +-7.84,-0.69, +-7.91,-0.85, +-7.8,-0.78, +-7.16,-0.18, +-7.58,0.04, +-7.05,0.88, +-6.89,1.2, +-6.87,1.43, +-6.92,1.91, +-6.61,2.4, +-6.58,2.39, +-6.43,2.34, +-5.74,2.09, +-5.66,2.06, +-5.66,2.06, +-5.68,2.02, +-5.7,1.94, +-5.77,1.55, +-5.06,1.09, +-4.82,1.01, +-3.97,1.09, +-3.2,1.12, +-2.51,1.45, +-1.5,2.48, +-1,2.73, +-0.74,2.81, +-0.61,2.89, +-0.34,3.05, +-0.12,2.77, +0.01,2.68, +0.39,2.68, +0.67,2.05, +0.62,1.84, +0.82,1.76, +1.21,1.49, +1.61,1.06, +1.62,0.68, +1.88,-0.15, +1.94,-0.13, +2.18,-0.28, +2.87,-0.51, +2.93,-0.52, +-1,4.2, +-0.35,4.99, +0.77,4.99, +2.24,5.22, +3.2,4.79, +3.87,4.62, +5.16,4.86, +6.21,5.21, +6.77,4.17, +6.9,3.98, +6.53,3.77, +5.85,2.68, +5.59,2.22, +4.75,1.73, +4.75,1.73, +4.85,1.35, +4.78,1.61, +4.75,1.73, +4.9,1.16, +4.98,0.88, +4.98,0.88, +4.5,0.79, +4.02,0.71, +4.05,0.39, +4.28,0, +4.02,-0.71, +3.96,-0.94, +3.84,-1.4, +4.05,-1.36, +4.69,-1.01, +5.62,-0.99, +5.93,-1.05, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.81,-1.52, +4.75,-1.73, +4.42,-2.43, +3.61,-2.22, +3.31,-2.61, +3.99,-2.71, +4.38,-2.53, +4.38,-2.53, +4.86,-2.8, +5.22,-3.01, +4.1,-2.36, +3.54,-2.04, +3.87,-3.25, +4.51,-3.78, +5.11,-4.29, +4.45,-3.73, +4.64,-5.22, +4.5,-5.36, +4.5,-5.36, +4.5,-5.36, +4.08,-4.86, +4.32,-5.15, +4.68,-5.57, +4.57,-5.44, +5.12,-6.11, +5.12,-6.11, +5.12,-6.11, +4.36,-6.64, +4.62,-6.46, +4.86,-7.13, +5.11,-7.3, +4.26,-7.37, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.47,-7.74, +4.09,-7.09, +4.09,-7.09, +4.47,-7.74, +4.47,-7.74, +4.69,-8.12, +4.96,-8.59, +4.96,-8.59, +5.54,-9.6, +5.54,-9.6, +5.86,-10.16, +6.03,-10.44, +6.03,-10.44, +6.52,-10.09, +7.75,-10.02, +8.37,-9.98, +8.37,-9.98, +8.68,-10.35, +9,-10.72, +9,-10.72, +9,-10.72, +9.52,-11.34, +9.28,-10.43, +10.53,-9.19, +10.72,-9, +10.35,-8.68, +9.98,-8.37, +9.98,-8.37, +10.26,-8.61, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +11.09,-9.31, +11.47,-9.62, +11.47,-9.62, +11.47,-9.62, +10.85,-10.24, +9.62,-11.47, +10.17,-12.12, +10.01,-11.93, +9.39,-11.19, +10.15,-9.57, +10.83,-9.09, +11.38,-9.55, +11.28,-9.47, +10.72,-9, +11.15,-9.35, +12.05,-10.11, +13.1,-11, +13.7,-11.5, +14.47,-12.14, +13.85,-13.14, +12.87,-13.01, +11.68,-9.8, +11.22,-9.41, +11.56,-9.7, +12.38,-10.38, +13.81,-11.58, +17.37,-14.58, +18.46,-15.49, +17.27,-14.49, +16.08,-13.49, +15.34,-12.87, +13.59,-11.41, +10.4,-10.27, +6.72,-9.55, +5.44,-9.43, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.92,1.83, +-7.15,0.72, +-7.58,-0.06, +-7.87,-1.25, +-7.86,-1.32, +-7.85,-1.38, +-7.85,-1.38, +-7.58,-1.32, +-7.87,-0.17, +-7.56,0.1, +-7.22,0.79, +-6.89,1.22, +-6.82,1.51, +-6.67,2.08, +-6.64,2.19, +-6.23,1.97, +-5.66,2.06, +-5.66,2.06, +-5.72,1.87, +-5.84,1.46, +-5.93,1.08, +-5.81,1.02, +-5.08,0.9, +-4.7,0.83, +-4.04,0.82, +-3.18,0.88, +-2.3,1.77, +-0.93,2.72, +-0.49,2.87, +-0.48,2.88, +-0.51,2.87, +-0.41,2.89, +-0.07,2.55, +-0,2.17, +0.61,1.91, +0.73,1.8, +0.95,1.68, +1.27,1.44, +1.62,1.03, +1.83,0.61, +1.92,-0.01, +1.92,-0.31, +1.96,-0.33, +2.51,-0.29, +2.87,-0.51, +3.13,-0.55, +-0.2,5.04, +0.5,5.01, +1.65,4.91, +2.75,5.22, +3.4,4.95, +3.87,4.62, +4.71,5.61, +6.57,5.51, +6.28,5.07, +6.21,4.6, +6.08,3.71, +5.66,2.06, +5.16,1.88, +4.8,1.54, +4.92,1.09, +4.98,0.88, +4.83,1.41, +4.85,1.35, +4.98,0.88, +4.98,0.88, +4.98,0.88, +4.98,0.79, +4.65,0.14, +4.19,0.18, +5.38,0, +5.91,-0.08, +4.89,-1.12, +5.75,-1.28, +5.9,-1.16, +5.93,-1.05, +5.93,-1.05, +5.72,-1.01, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.89,-1.2, +4.75,-1.73, +4.48,-2.3, +3.59,-2.53, +3.13,-2.62, +3.55,-2.98, +4.38,-2.53, +4.38,-2.53, +4.66,-2.69, +5.22,-3.01, +4.1,-2.36, +3.85,-2.22, +4.26,-2.69, +4.19,-2.8, +4.66,-2.69, +5.41,-3.59, +5.36,-4.5, +5.1,-4.76, +4.5,-5.36, +4.5,-5.36, +4.5,-5.36, +4.5,-5.36, +4.62,-5.24, +4.19,-4.99, +4.94,-5.89, +5.12,-6.11, +5.12,-6.11, +5.12,-6.11, +5.12,-6.11, +5.62,-6.7, +5.75,-6.85, +4.9,-7.45, +4.47,-7.74, +4.35,-7.53, +4.15,-7.18, +4.33,-7.5, +3.99,-6.9, +3.99,-6.9, +4.08,-7.07, +4.47,-7.74, +4.47,-7.74, +4.96,-8.59, +4.96,-8.59, +5.48,-9.48, +5.54,-9.6, +5.78,-10.02, +6.24,-10.29, +6.03,-10.44, +6.52,-10.09, +7.85,-9.36, +8.37,-9.98, +8.37,-9.98, +8.37,-9.98, +9,-10.72, +9,-10.72, +9,-10.72, +9,-10.72, +10.68,-9.49, +11.47,-9.62, +10.81,-9.07, +10.72,-9, +10.82,-8.02, +11.05,-7.68, +11.12,-8.43, +10.72,-9, +10.81,-9.07, +11.24,-8.49, +11.83,-8.26, +10.97,-9.2, +11.47,-9.62, +11.47,-9.62, +11.47,-9.62, +11.47,-9.62, +11.47,-9.62, +9.93,-11.16, +10.04,-11.96, +9.92,-11.44, +10.34,-9.38, +10.72,-9, +11.34,-9.52, +11.47,-9.62, +11.47,-9.62, +10.85,-9.1, +10.72,-9, +12.09,-8.24, +12.84,-8.88, +13.1,-11, +13.97,-11.72, +13.97,-11.72, +12.43,-10.82, +11.47,-9.62, +11.47,-9.62, +12.32,-10.34, +14.61,-12.26, +16.08,-13.49, +17.6,-14.76, +17.66,-14.82, +16.27,-13.65, +15.62,-13.1, +14.72,-12.35, +10.7,-8.98, +1.11,-1.32, +3.94,-5.81, +3.79,-6.57, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.84,1.41, +-7.75,0.21, +-7.84,-0.02, +-7.87,-1.2, +-7.85,-1.38, +-7.85,-1.38, +-7.85,-1.38, +-7.85,-1.38, +-7.95,-0.34, +-7.97,0, +-7.32,0.06, +-6.94,0.83, +-6.89,1.22, +-6.89,1.22, +-6.84,1.34, +-6.16,1.21, +-5.83,1.48, +-5.74,1.82, +-5.9,1.21, +-5.93,1.05, +-5.84,1.03, +-5.23,0.92, +-4.81,0.8, +-4.11,0.52, +-3.37,0.56, +-2.87,1.04, +-2.01,2.07, +-1.25,2.45, +-0.56,2.64, +-0.52,2.87, +-0.59,2.85, +-0.54,2.86, +-0.65,2.83, +0.07,2.57, +0.96,1.65, +1.29,1.42, +1.56,1.13, +1.68,0.95, +1.92,0.43, +1.97,0.26, +1.94,-0.11, +2.05,-0.36, +2.68,-0.47, +2.85,-0.5, +2.98,-0.53, +3.75,-0.91, +1.71,2.79, +1.3,4.86, +2.08,4.58, +2.89,4.78, +3.87,4.62, +3.87,4.62, +4.71,5.61, +5.54,6.6, +5.99,6.27, +5.53,5.24, +5.28,3.55, +5.51,2.13, +4.88,1.67, +4.98,0.88, +4.98,0.88, +4.98,0.88, +4.95,0.97, +4.92,1.09, +4.98,0.88, +4.98,0.88, +4.98,0.88, +5.01,0.55, +5.05,0, +4.81,0, +5.92,0, +6.35,0, +6.93,-0.81, +6.89,-1.22, +6.57,-1.16, +5.93,-1.05, +5.93,-1.05, +5.62,-0.99, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.91,-1.12, +4.84,-1.76, +4.67,-1.89, +4.13,-2.64, +3.13,-2.62, +3.6,-2.89, +4.32,-2.61, +4.38,-2.53, +4.66,-2.69, +5.27,-2.89, +4.18,-2.19, +3.75,-2.16, +4.38,-2.53, +4.43,-2.41, +4.5,-2.26, +4.54,-2.17, +5.09,-3.68, +5.36,-4.5, +5.04,-3.65, +4.84,-4.08, +4.87,-4.99, +4.5,-5.36, +4.42,-5.27, +4.78,-5.69, +4.73,-5.64, +5.02,-5.98, +5.12,-6.11, +5.12,-6.11, +5.25,-6.25, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.04,-7.35, +4.39,-7.6, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +4.33,-7.5, +4.47,-7.74, +4.65,-8.06, +4.96,-8.59, +5.35,-9.26, +5.54,-9.6, +6.5,-9.27, +7.26,-9.58, +6.89,-9.83, +7.32,-9.53, +7.75,-9.23, +8.37,-9.98, +8.27,-9.85, +8.37,-9.98, +9,-10.72, +9,-10.72, +9,-10.72, +9.98,-9.74, +10.89,-9.14, +11.37,-9.54, +11.28,-9.47, +11.19,-8.33, +12.12,-7, +12.12,-7, +12.12,-7, +12.24,-7.07, +12.13,-8.17, +12.96,-7.48, +12.96,-7.48, +12.21,-8.55, +11.47,-9.62, +11.47,-9.62, +11.47,-9.62, +11.47,-9.62, +11.47,-9.62, +10.58,-10.81, +10.01,-11.93, +10.4,-11.14, +10.97,-9.2, +11.3,-9.48, +11.47,-9.62, +12.03,-8.82, +11.96,-8.91, +11.55,-9.31, +11.64,-7.92, +12.23,-7.06, +13.24,-7.65, +13.18,-10.66, +13.85,-11.62, +13.1,-11, +11.47,-9.62, +11.47,-9.62, +11.89,-9.98, +13.1,-11, +16.33,-13.7, +17.57,-14.74, +17.66,-14.82, +14.82,-12.43, +15.21,-12.77, +14.49,-12.16, +13.29,-11.15, +4.91,-4.12, +0,0, +0,0, +0.45,-0.77, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.64,0.23, +-4.41,-0.02, +-7.16,-0.57, +-7.7,-1.33, +-7.85,-1.38, +-7.87,-1.39, +-7.93,-1.4, +-7.85,-1.38, +-7.95,-0.45, +-7.97,0, +-7.39,0, +-7.1,0.11, +-6.94,0.84, +-6.89,1.22, +-6.65,1.17, +-5.93,1.05, +-5.92,1.04, +-5.65,1.05, +-5.62,0.99, +-5.72,0.99, +-5.27,0.76, +-4.73,0.56, +-4.15,0.33, +-3.77,0.64, +-3.21,0.75, +-2.48,1.49, +-1.9,2.21, +-1.03,2.38, +-1,2.63, +-1.01,2.64, +-0.99,2.71, +-0.92,2.76, +-1.4,2.53, +-0.36,1.77, +1.64,1.32, +1.88,1.24, +1.82,0.69, +1.81,0.7, +2.04,0.56, +2.36,0.21, +2.03,-0.16, +2.68,-0.49, +2.85,-0.62, +2.87,-0.53, +3.11,-0.59, +3.9,-0.97, +2.64,2.29, +2.26,4.5, +2.71,4.25, +3.23,4.08, +3.87,4.62, +4.01,4.78, +4.71,5.61, +5.48,6.53, +5.57,6.64, +5.06,6.03, +4.97,4.22, +4.67,2.19, +4.75,1.73, +4.98,0.88, +4.98,0.88, +5,0.66, +4.98,0.88, +4.98,0.88, +4.98,0.88, +4.98,0.88, +4.98,0.88, +4.84,0.85, +5.05,0.1, +5.05,0, +5.61,0, +6.19,0, +6.94,-0.68, +6.89,-1.22, +6.53,-1.15, +5.93,-1.05, +5.93,-1.05, +5.62,-0.99, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +5.17,-0.91, +5.62,-1.1, +5.71,-1.89, +5.66,-2.06, +5.04,-2.37, +4.38,-2.53, +4.38,-2.53, +4.38,-2.53, +4.56,-2.13, +5.02,-2.16, +5.66,-2.06, +4.75,-1.73, +3.78,-1.83, +4.67,-1.91, +4.75,-1.73, +4.75,-1.73, +4.75,-1.73, +4.92,-2.28, +5.65,-2.75, +5.15,-2.77, +4.92,-3.44, +4.99,-4.19, +4.11,-4.69, +4.53,-5.13, +4.81,-5.73, +4.68,-5.57, +4.75,-5.66, +5.05,-6.02, +5.12,-6.11, +5.39,-6.42, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.2,-6.97, +4.36,-6.64, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +3.99,-6.9, +4.15,-7.18, +4.47,-7.74, +4.54,-7.86, +5.11,-8.48, +5.75,-8.56, +6.67,-8.8, +7.21,-8.59, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.89,-9.4, +8.37,-9.98, +9,-10.72, +9,-10.72, +9,-10.72, +10.43,-9.28, +10.72,-9, +11.14,-9.34, +11.37,-9.54, +11.65,-7.66, +12.12,-7, +12.12,-7, +12.12,-7, +12.36,-7.14, +12.96,-7.48, +12.96,-7.48, +12.96,-7.48, +12.96,-7.48, +11.65,-9.35, +11.47,-9.62, +11.47,-9.62, +11.47,-9.62, +11.84,-9.93, +11.72,-10.74, +11.03,-11.42, +11.22,-10.45, +11.47,-9.62, +11.47,-9.62, +11.94,-8.79, +12.96,-7.48, +12.96,-7.48, +12.96,-7.48, +12.29,-7.1, +12.12,-7, +12.96,-7.48, +14.23,-8.82, +13.94,-11.29, +13.1,-11, +12.09,-10.14, +11.84,-9.93, +12.76,-10.71, +14,-11.75, +16.08,-13.49, +17.94,-15.06, +17.05,-14.31, +11.06,-9.28, +0,0, +6.63,-5.56, +6.95,-5.84, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.87,-0.05, +-0.77,-0.11, +-6.02,-1.06, +-7.85,-1.38, +-8.45,-1.49, +-8.57,-1.51, +-7.91,-1.38, +-7.94,-0.47, +-7.97,0, +-7.97,0, +-7.34,0, +-6.99,0.17, +-6.97,0.49, +-6.39,0.87, +-5.94,1.02, +-5.83,1.02, +-5.06,0.87, +-4.99,0.81, +-5.02,0.43, +-4.84,0.06, +-4.2,0, +-3.85,0.15, +-3.05,0.46, +-2.87,0.51, +-2.5,1.44, +-1.56,2.1, +-1.34,2.29, +-1.46,2.45, +-1.29,1.78, +-1.59,2.2, +-1.73,2.31, +-1.35,1.77, +2.22,-0.17, +2.55,0.97, +2.4,1.23, +1.87,0.68, +2.42,0.78, +2.53,0.46, +2.2,0.09, +2.53,-0.24, +2.93,-0.56, +2.88,-0.99, +2.98,-0.62, +3.83,-0.68, +4.02,-0.71, +3.94,-0.16, +2.84,4.16, +3.25,3.87, +3.44,3.82, +3.8,4.53, +4.03,4.8, +4.86,5.79, +5.75,6.85, +6.06,7.22, +5.21,6.78, +4.68,4.72, +4.82,2.15, +4.75,1.73, +4.98,0.88, +5,0.59, +5.02,0.38, +4.98,0.88, +4.98,0.88, +4.99,0.75, +5.05,0, +5.05,0.1, +4.77,0.74, +4.99,0.77, +5.05,0, +5.78,0, +6.03,0, +6.26,-0.15, +6.43,-0.96, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.39,-0.95, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +5.77,-1.02, +5.93,-1.05, +5.93,-1.05, +5.75,-1.72, +6.05,-2.2, +5.51,-2.01, +4.61,-2.03, +4.71,-1.82, +4.86,-1.77, +5.51,-2.01, +5.66,-2.06, +5.57,-2.03, +5.39,-1.84, +4.88,-1.24, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +5.06,-1.19, +5.82,-2.24, +5.41,-3.12, +4.7,-3.75, +4.12,-4.37, +5.17,-4.34, +5.75,-4.99, +5.68,-4.77, +5.82,-5.18, +4.97,-5.92, +5.12,-6.11, +5.3,-6.32, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.66,-6.74, +5.12,-6.11, +4.81,-6.45, +4.55,-6.5, +3.99,-6.9, +4.55,-6.5, +4.36,-6.64, +5.11,-7.16, +5.32,-7.15, +6.14,-7.32, +6.46,-7.69, +7.04,-8.39, +7.12,-8.49, +7.43,-8.86, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +7.98,-9.51, +8.37,-9.98, +8.79,-10.47, +9,-10.72, +10.48,-9.24, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +11.5,-7.89, +12.12,-7, +12.12,-7, +12.12,-7, +12.12,-7, +12.48,-7.21, +12.96,-7.48, +12.96,-7.48, +12.96,-7.48, +12.32,-8.4, +11.47,-9.62, +10.47,-8.79, +8.9,-7.47, +8.41,-7.05, +9.98,-8.37, +10.72,-9, +11.37,-9.54, +11.47,-9.62, +11.28,-9.47, +12.26,-7.08, +12.86,-7.42, +12.96,-7.48, +12.84,-7.41, +12.12,-7, +12.12,-7, +12.6,-7.28, +14.37,-8.29, +15.07,-10.09, +13.33,-11.18, +13.21,-11.08, +12.33,-10.35, +13.9,-11.67, +14.87,-12.48, +14.32,-12.01, +14.53,-12.19, +8.87,-7.44, +4.97,-4.17, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.74,-0.31, +-7.93,-1.4, +-8.69,-1.53, +-8.72,-1.54, +-8.17,-1.42, +-7.99,-0.18, +-7.97,0, +-7.97,0, +-7.52,0, +-7,0, +-6.89,0, +-6.2,0.09, +-6.04,0.26, +-5.91,0.77, +-5.09,0.23, +-5.05,0.14, +-4.92,0, +-4.23,0, +-4,0, +-3.24,0, +-2.91,0.08, +-2.87,0.5, +-2.58,1.32, +-1.96,2.06, +-1.88,2.19, +-1.62,1.82, +-1.39,1.4, +-2.11,1.78, +-1.76,1.53, +2.3,-0.29, +2.87,-0.46, +2.84,0.53, +2.63,0.97, +2.43,0.76, +2.8,0.8, +2.83,0.38, +2.18,-0.02, +2.84,-0.4, +3.56,-0.64, +3.83,-1.05, +3.72,-0.76, +4.04,-0.71, +4.45,-0.78, +4.84,-0.85, +3.62,3.14, +3.68,3.44, +3.87,3.25, +4.49,4, +4.43,5.08, +4.91,5.86, +5.75,6.85, +6.37,7.59, +5.85,7.28, +5.01,5.76, +4.56,3.32, +4.75,1.73, +4.98,0.88, +4.98,0.88, +5.02,0.39, +5.01,0.55, +4.99,0.73, +5.01,0.55, +5.05,0, +4.95,0, +4.69,0.44, +4.98,0.88, +5.02,0.39, +5.81,0, +6.03,0, +6.03,0, +6.03,0, +5.99,-0.42, +5.98,-0.52, +5.93,-1.05, +5.4,-0.95, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +5.93,-1.05, +6.89,-1.22, +6.34,-1.12, +5.93,-1.05, +5.76,-1.68, +5.72,-1.84, +5.58,-1.89, +5.4,-1.78, +5.7,-1.93, +5.69,-1.96, +5.72,-1.84, +5.69,-1.33, +5.66,-1, +5.3,-0.93, +5.02,-0.44, +5.02,-0.44, +5.04,-0.15, +5.02,-0.39, +5.09,-0.8, +6.42,-1.72, +6.03,-2.81, +4.83,-3.75, +4.39,-4.37, +5.36,-4.5, +5.61,-4.71, +5.53,-4.64, +5.77,-4.85, +5.65,-5, +4.95,-5.59, +5.55,-6.02, +6.36,-6.24, +6.54,-6.06, +6.06,-6.54, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.21,-6.21, +5.12,-6.11, +5.12,-6.11, +5.12,-6.11, +5.57,-6.64, +5.75,-6.85, +6.02,-7.17, +6.37,-7.59, +6.62,-7.89, +7.12,-8.49, +7.12,-8.49, +7.64,-9.11, +7.75,-9.23, +7.75,-9.23, +7.75,-9.23, +8.41,-9.48, +8.48,-10.1, +9,-10.72, +9.64,-10.07, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +11.98,-7.2, +12.12,-7, +12.12,-7, +12.12,-7, +12.12,-7, +12.48,-7.2, +12.96,-7.48, +12.96,-7.48, +12.23,-7.06, +10.81,-9.07, +10.47,-8.79, +8.49,-7.12, +8.38,-7.03, +10.07,-8.45, +10.72,-9, +10.97,-9.2, +11.47,-9.62, +11.96,-8.91, +12.12,-7, +12.26,-7.08, +12.86,-7.42, +12.78,-7.38, +12.12,-7, +12.12,-7, +12.75,-7.36, +14.43,-8.33, +16.18,-9.98, +13.4,-11.25, +13.29,-11.15, +13.13,-11.02, +14.47,-12.14, +15.34,-12.87, +7.3,-6.12, +1.46,-1.22, +2.16,-1.81, +2.04,-1.71, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.36,-0.77, +-8.24,-1.18, +-8.81,-0.74, +-8.41,-1.11, +-8.07,-0.16, +-7.97,0, +-7.84,0, +-7.41,0, +-7.04,0, +-6.87,0, +-6.26,0, +-6.11,0, +-6,0.04, +-5.18,0, +-4.97,0, +-4.35,0, +-4.08,0, +-3.84,0, +-3.02,-0.01, +-2.92,0.03, +-2.78,0.83, +-2.55,1.4, +-2.33,1.75, +-2.28,1.82, +-1.99,1.39, +-1.62,1.06, +-2.38,1.17, +1.33,0.26, +2.89,-0.36, +2.9,0.08, +2.85,0.57, +2.76,0.94, +2.76,0.92, +2.89,0.3, +2.91,0.08, +2.96,-0.18, +3.38,-0.73, +3.92,-1.12, +3.91,-1.16, +4.16,-0.76, +4.51,-0.8, +4.98,-0.87, +5.02,-0.51, +4.92,0.26, +3.77,3.16, +3.87,3.25, +4.62,3.87, +4.95,4.15, +5.35,5.36, +5.75,6.85, +6.37,7.59, +6.71,7.99, +5.57,7.68, +4.35,5, +5.32,2.22, +5.3,0.93, +4.98,0.88, +5.03,0.29, +5.02,0.38, +5.38,0, +5.14,0.2, +5.04,0.13, +5.01,0.5, +4.79,0.59, +4.98,0.79, +4.99,0.75, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.02,-0.12, +5.96,-0.7, +5.8,-1.02, +4.98,-0.88, +4.98,-0.88, +5.3,-0.93, +5.93,-1.05, +6.73,-1.19, +6.41,-1.13, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.84,-1.38, +5.93,-1.05, +6.04,-1.07, +5.96,-0.78, +5.85,-0.17, +5.19,0, +5.05,0, +5.05,0, +5.05,0, +5.03,-0.29, +6.06,-1.33, +6.11,-2.63, +5.09,-3.68, +4.24,-4.24, +5.11,-4.29, +5.03,-4.22, +5.48,-4.6, +5.61,-4.71, +5.83,-4.89, +5.69,-4.78, +5.98,-5.25, +6.74,-5.66, +6.85,-5.75, +6.3,-6.3, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.39,-6.42, +5.12,-6.11, +5.12,-6.11, +5.29,-6.31, +5.66,-6.74, +5.75,-6.85, +5.83,-6.94, +6.37,-7.59, +6.37,-7.59, +7,-8.34, +7.12,-8.49, +7.24,-8.62, +7.75,-9.23, +7.75,-9.23, +9.02,-7.96, +8.95,-8.49, +8.64,-10.3, +9.86,-9.86, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +6.27,-5.26, +6.99,-6.12, +9.65,-6.28, +12.24,-7.07, +12.4,-7.16, +12.12,-7, +12.26,-7.08, +10.77,-6.22, +9.49,-5.48, +9.6,-5.54, +9.88,-5.7, +9.75,-6.29, +9.17,-6.3, +8.7,-7.3, +8.81,-6.94, +10.76,-7.73, +11.03,-8.55, +11.04,-9.26, +12.11,-8.71, +12.96,-7.48, +12.4,-7.16, +12.12,-7, +12.33,-7.12, +12.12,-7, +12.12,-7, +12.12,-7, +13.17,-7.61, +14.81,-8.55, +17.34,-10.01, +14.47,-12.14, +13.81,-11.59, +13.94,-11.7, +14.59,-12.25, +14.72,-12.35, +9.18,-6.68, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.46,-0.08, +-8.3,-0.02, +-8.11,-0.23, +-8.03,0, +-7.97,0, +-7.39,-0.02, +-7.12,-0.06, +-7.02,-0.04, +-6.61,-0.08, +-6.05,0, +-6.03,0, +-5.66,0, +-5.08,0, +-4.64,0, +-4.08,-0.07, +-4.06,-0.38, +-3.59,-0.48, +-2.9,-0.22, +-2.9,0.25, +-2.72,1.03, +-2.53,1.45, +-2.51,1.48, +-2.51,1.48, +-2.2,1.1, +-2,0.68, +2.22,0.01, +2.91,-0.01, +2.89,0.35, +2.87,0.51, +2.81,0.77, +2.85,0.61, +2.86,0.53, +2.92,0.01, +2.99,-0.19, +3.52,-0.53, +3.74,-0.77, +3.87,-1.3, +4.05,-1.05, +4.72,-0.83, +4.98,-0.88, +5.01,-0.55, +5.05,0.03, +5.02,0.51, +4.47,2.11, +4.32,2.61, +4.75,3.12, +5.04,4.23, +6,5.03, +5.99,6.61, +6.37,7.59, +6.8,8.1, +6.62,7.89, +5.38,6, +5.36,2.85, +4.88,1.24, +4.98,0.88, +5.05,0, +5.13,0.19, +5.61,0, +5.61,0, +5.14,0.2, +4.99,0.78, +5,0.63, +5.03,0.29, +4.98,0.88, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6,-0.26, +5.63,-0.87, +5.22,-0.92, +5.77,-1.02, +5.93,-1.05, +6.03,-1.06, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.95,-0.92, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +6,-0.35, +6.16,0, +7,0, +6.44,0.25, +5.05,0, +5.19,0.29, +5.35,0, +5.62,-0.19, +5.73,-1.19, +5.47,-2.47, +5.12,-3.53, +4.37,-4.12, +5.11,-4.29, +5.61,-4.71, +5.77,-4.85, +6.11,-5.12, +5.98,-5.02, +5.89,-4.94, +6.11,-5.12, +6.44,-5.4, +6.6,-5.54, +6.6,-5.54, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.54,-6.6, +5.12,-6.11, +5.12,-6.11, +5.21,-6.21, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +6.37,-7.59, +6.37,-7.59, +6.75,-8.04, +7.12,-8.49, +7.12,-8.49, +7.61,-9.07, +8.3,-8.68, +9.23,-7.75, +9.19,-8.7, +9.99,-9.42, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +6.02,-5.55, +6.12,-6.48, +8.36,-6.11, +9.66,-6.41, +9.53,-6.3, +10.68,-6.7, +9.74,-5.8, +9.9,-5.72, +9.09,-5.25, +9.37,-5.41, +9.6,-5.54, +9.72,-5.61, +9.84,-5.68, +9.26,-6.58, +10.16,-6.18, +11.42,-6.59, +11.88,-6.86, +12.34,-7.91, +12.71,-7.84, +12.96,-7.48, +12.4,-7.16, +12.12,-7, +12.12,-7, +12.12,-7, +12.12,-7, +12.46,-7.19, +13.32,-7.69, +16.13,-9.31, +18.72,-11.41, +15.34,-12.87, +14.59,-12.25, +14.22,-11.93, +14.49,-11.71, +15.05,-10.39, +10.74,-6.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.55,0, +-8.53,0, +-8.14,0, +-7.97,0, +-7.58,-0.11, +-7.87,-0.61, +-7.1,-0.76, +-6.94,-0.54, +-6.11,0, +-6.03,0, +-5.73,0, +-4.94,0, +-4.22,-0.09, +-4.03,-0.63, +-3.89,-0.68, +-3.18,-0.2, +-2.88,0.43, +-2.86,0.57, +-2.74,1, +-2.59,1.34, +-2.56,1.39, +-2.68,1.13, +-0.93,0.27, +2.07,-0.24, +2.91,-0.05, +2.91,0.19, +2.88,0.48, +2.86,0.54, +2.77,0.9, +2.97,0.73, +3.1,0.21, +2.92,0.01, +3.1,-0.25, +3.97,-0.58, +4,-0.82, +3.95,-1.08, +4.54,-1.06, +4.96,-0.87, +5.01,-0.6, +5.02,0.08, +5.03,0.44, +4.53,1.19, +4.42,1.65, +4.59,2.31, +5.22,3.01, +5.61,3.94, +6.11,5.12, +6.85,5.75, +6.58,7.39, +6.46,7.69, +6.18,7.36, +5.72,5.66, +5.36,2.63, +4.98,0.88, +4.98,0.88, +4.99,0.77, +5.05,0, +5.42,0, +5.38,0, +5.05,0, +5.03,0.29, +5.01,0.55, +5.04,0.2, +4.98,0.88, +5.85,0.15, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6,-0.35, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.98,-0.52, +6.03,0, +6.03,0, +5.98,-0.58, +5.93,-1.05, +5.93,-1.05, +6,-0.35, +6.24,0, +8.94,0, +8.04,0.77, +5.74,1.01, +5.64,0.76, +5.54,0, +6.03,0, +5.93,-1.05, +5.61,-2.17, +5.02,-3.3, +4.74,-3.98, +5.61,-4.71, +5.61,-4.71, +6.11,-5.12, +5.89,-4.94, +5.73,-4.81, +5.77,-4.85, +5.98,-5.02, +5.79,-4.86, +5.61,-4.71, +5.92,-4.97, +6.17,-5.51, +5.93,-5.99, +5.75,-6.85, +5.57,-6.34, +5.12,-6.11, +5.12,-6.11, +5.68,-6.77, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.89,-7.02, +6.37,-7.59, +6.37,-7.59, +6.91,-8.23, +7.12,-8.49, +7.12,-8.49, +7.58,-8.49, +9.23,-7.75, +9.23,-7.75, +9.7,-8.14, +10.62,-8.91, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.72,-9, +10.08,-8.46, +6.85,-5.75, +6.12,-6.48, +6.85,-5.75, +6.68,-5.61, +6.48,-5.44, +6.57,-5.51, +8.27,-4.77, +8.59,-4.96, +8.59,-4.96, +8.95,-5.17, +9.39,-5.42, +10.02,-5.78, +10.02,-5.78, +10.3,-5.94, +11.38,-6.57, +12.12,-7, +12.72,-7.35, +12.96,-7.48, +12.96,-7.48, +12.72,-7.35, +12.12,-7, +12.12,-7, +12.12,-7, +12.12,-7, +12.12,-7, +12.75,-7.36, +13.91,-8.03, +17.84,-10.3, +18.18,-11.9, +15.71,-13.18, +15.41,-12.34, +14.59,-11.64, +13.66,-7.89, +4.14,-2.39, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.17,0, +-7.97,0, +-7.97,0, +-7.97,0, +-7.97,0, +-7.85,-0.18, +-7.04,-0.24, +-6.92,-0.03, +-6.33,0, +-6.03,0, +-5.53,0, +-4.59,0, +-4.08,-0.03, +-4.06,-0.37, +-3.96,-0.55, +-3.25,0, +-2.88,0.46, +-2.81,0.77, +-2.69,1.11, +-2.68,1.15, +-2.72,1.05, +-1.02,0.91, +2.91,-0.47, +3.01,-0.07, +3.43,0.13, +2.89,0.39, +2.87,0.49, +2.89,0.56, +2.89,0.94, +3.46,1.15, +3.79,0.83, +3.09,0.27, +3.56,0.06, +4.06,-0.02, +4.07,-0.13, +4.49,-0.5, +4.99,-0.29, +5.04,-0.08, +5.02,0.36, +4.92,1.11, +4.39,1.43, +4.2,1.53, +4.75,1.73, +5.36,1.95, +5.56,2.97, +6.43,3.71, +6.48,5.22, +7.17,6.02, +7.32,6.64, +6.3,7.21, +6.16,6.04, +5.53,4.25, +5.21,2.27, +4.93,1.07, +4.98,0.88, +4.98,0.88, +5.14,0.2, +5.38,0, +5.05,0, +5.05,0, +5.05,0.1, +5.05,0, +5.05,0, +5.38,0, +5.91,0.1, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +5.98,-0.58, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +6.13,-0.7, +6.66,-0.12, +7,0, +6.67,0, +6.03,0, +6.16,-0.97, +6.25,-1.1, +6.29,-0.15, +6.3,0, +7.87,0, +7.95,0.17, +7.21,1.27, +6,0.3, +6.03,0, +6.03,0, +5.93,-1.05, +5.66,-2.06, +5.15,-3.11, +5.03,-4.22, +5.86,-4.91, +5.89,-4.94, +5.96,-5, +5.69,-4.78, +5.86,-4.91, +5.89,-4.94, +6.02,-5.05, +5.55,-4.65, +6.11,-5.12, +5.89,-4.94, +6.11,-5.12, +6.11,-5.12, +6.11,-5.63, +5.63,-6.11, +5.12,-6.11, +5.12,-6.11, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +5.75,-6.85, +6.28,-7.49, +6.37,-7.59, +6.37,-7.59, +6.87,-8.19, +7.12,-8.49, +7.12,-8.49, +8.12,-7.65, +9.23,-7.75, +9.23,-7.75, +9.48,-7.96, +9.79,-8.22, +10.38,-7.8, +10.58,-7.51, +10.51,-6.77, +10.54,-7.14, +10.37,-7.39, +9.97,-6.69, +6.44,-5.2, +6.74,-5.66, +6.35,-5.33, +6.85,-5.75, +6.85,-5.75, +6.85,-5.75, +8.14,-5.03, +8.59,-4.96, +8.7,-5.02, +8.73,-5.04, +9.45,-5.46, +9.9,-5.16, +10.32,-4.23, +10.58,-6.11, +12.48,-7.21, +12.68,-7.32, +12.96,-7.48, +13.52,-7.81, +12.96,-7.48, +12.54,-7.24, +12.44,-7.18, +12.4,-7.16, +12.12,-7, +12.21,-7.05, +12.59,-7.27, +13.8,-7.97, +15.08,-8.71, +19.02,-10.98, +18.05,-12.58, +16.06,-12.68, +17.41,-10.63, +16.35,-9.44, +14.22,-8.21, +2.16,-1.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.28,0, +-7.64,0, +-7.95,0, +-7.8,0, +-7.93,0, +-7.94,0, +-7.31,0, +-7.11,0, +-6.54,0, +-5.97,0, +-5.32,0, +-4.73,0, +-4.08,0, +-3.94,0, +-3.74,-0.02, +-3.28,0.34, +-2.86,0.54, +-2.78,0.88, +-2.7,1.09, +-2.74,0.99, +-2.19,0.59, +3.16,-0.26, +3.3,-0.16, +3.84,0.33, +3.85,0.68, +3.29,0.8, +3.2,0.59, +3.29,0.6, +3.81,1.03, +3.97,0.82, +3.99,0.72, +3.89,0.67, +3.61,0.13, +3.23,-0.17, +3.11,-0.22, +3.49,-0.09, +3.28,-0.01, +3.04,0.54, +4.09,0.83, +3.99,1.2, +4.44,1.62, +4.75,1.73, +5.43,1.98, +5.76,2.1, +6.26,2.57, +6.62,3.82, +7.2,4.83, +6.93,5.82, +7.22,6.06, +6.85,5.75, +6.29,5.28, +5.62,3.56, +5.21,2.17, +4.9,1.16, +4.98,0.88, +4.98,0.88, +5.26,0.44, +5.38,0, +5.05,0, +5.05,0, +5.02,0.44, +5.04,0.15, +5.05,0, +5.54,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +6.15,-1.08, +6.94,-0.68, +7,0, +7,0, +6.97,0.27, +6.79,0.1, +6.3,0, +6.69,-0.35, +6.61,0, +7.86,0, +8.62,0, +8.11,0, +8.28,0.17, +6.24,0, +6.03,0, +5.99,-0.45, +5.93,-1.05, +5.66,-2.06, +5.22,-3.01, +4.82,-3.82, +6.3,-4.07, +6.22,-3.99, +6.65,-4.07, +5.96,-3.64, +5.42,-4.04, +5.68,-4.77, +5.86,-4.91, +5.89,-4.94, +6.11,-5.12, +5.68,-4.77, +6.11,-5.12, +6.01,-5.04, +6.35,-5.33, +6.27,-5.87, +5.12,-6.11, +5.12,-6.11, +5.61,-6.68, +5.75,-6.85, +5.75,-6.85, +5.89,-7.02, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.87,-8.19, +7.12,-8.49, +7.58,-8.03, +8.49,-7.12, +9.1,-7.63, +9.23,-7.75, +9.23,-7.75, +9.58,-7.26, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +10.06,-5.81, +9.6,-5.54, +7.18,-4.15, +7.08,-5.21, +6.85,-5.75, +6.94,-5.83, +7.1,-5.96, +7.18,-6.03, +7.61,-6.39, +7.59,-6.37, +9.02,-5.49, +8.95,-5.17, +9.45,-5.46, +10,-4.66, +10.05,-3.66, +11.1,-5.38, +12.68,-6.95, +13.15,-7.09, +13.44,-7.76, +13.8,-7.97, +13.2,-7.62, +12.96,-7.48, +12.96,-7.48, +12.96,-7.48, +12.96,-7.48, +12.96,-7.48, +13.34,-7.7, +14.53,-8.39, +16.64,-9.61, +20.64,-11.92, +19.58,-11.31, +17.48,-10.09, +17.9,-10.33, +17.34,-10.01, +15.51,-8.95, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.75,0, +-6.12,0, +-7.41,0, +-7.78,0, +-7.44,0, +-7.68,0, +-7.73,0, +-7.11,0, +-6.66,0, +-5.95,0, +-5.33,0, +-4.97,0.02, +-4.39,0.05, +-3.67,0, +-2.91,0.08, +-2.88,0.48, +-2.85,0.59, +-2.74,1.01, +-2.69,1.11, +-2.2,1.17, +2.37,-0.33, +3.99,-0.31, +4.08,0.05, +4.03,0.65, +4.02,0.72, +3.95,1.01, +3.91,1.16, +3.92,1.13, +4.02,0.73, +3.83,0.46, +3.32,-0.28, +2.74,-1, +2.74,-1, +3.11,-0.61, +3.51,-0.62, +3.76,-0.33, +4.08,0, +4.05,0.39, +4.23,0.75, +4.86,0.86, +5.14,0.91, +5.53,1.29, +6.03,1.93, +6.58,2.39, +6.58,2.39, +7,3.51, +7.56,4.36, +7.49,4.84, +6.98,5.57, +7.15,5.32, +6.44,4.65, +5.88,3.22, +5.53,2.01, +5.06,0.99, +4.98,0.88, +4.98,0.88, +4.99,0.75, +5.05,0.1, +5.03,0.26, +5.02,0.38, +5,0.63, +5.05,0, +5.05,0, +5.54,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +5.98,-0.52, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +6.69,-0.72, +7,0, +7,0, +6.93,0.73, +6.48,1.14, +5.93,1.05, +6.51,0.62, +7.11,0, +7.65,0, +8.29,0, +8.4,0, +7.86,0, +8.6,-0.23, +6.93,-0.78, +5.95,-0.81, +5.9,-1.16, +5.78,-1.63, +5.47,-2.47, +5.04,-3.27, +4.62,-3.87, +5.98,-3.85, +6.8,-3.92, +6.9,-3.98, +6.06,-3.5, +5.92,-3.42, +6.1,-3.87, +6.19,-4.06, +6.08,-3.71, +6.2,-4.98, +6.11,-5.12, +5.94,-4.98, +5.86,-4.91, +6.35,-5.33, +6.27,-5.87, +5.12,-6.11, +5.12,-6.11, +5.23,-6.23, +5.75,-6.85, +5.75,-6.85, +5.96,-7.1, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.87,-8.19, +7.12,-8.49, +8.36,-7.25, +8.49,-7.12, +8.49,-7.12, +8.67,-7.28, +8.98,-7.54, +9.63,-7.17, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +10.16,-5.86, +9.69,-5.59, +7.5,-4.33, +7.5,-4.33, +7.05,-5.67, +7.18,-6.03, +7.1,-5.96, +7.47,-6.27, +8.1,-6.8, +8.49,-7.12, +8.99,-5.83, +8.59,-4.96, +9.6,-5.54, +9.9,-4.88, +10.39,-3.78, +11.66,-4.55, +13.35,-4.86, +13.76,-5.01, +14.98,-5.45, +13.55,-7.09, +13.22,-7.63, +12.96,-7.48, +12.96,-7.48, +13.17,-7.61, +13.43,-7.75, +13.68,-7.9, +13.94,-8.05, +15.3,-8.83, +18.1,-10.45, +21.16,-12.22, +19.74,-11.4, +18.09,-10.44, +18.32,-10.58, +18.02,-9.4, +13.39,-7.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.36,0, +-7.06,0, +-7.39,0, +-7.03,0, +-7.06,0, +-7.06,0, +-7,0, +-6.66,0, +-6.03,0, +-5.49,0, +-5.05,0.09, +-4.41,0.34, +-3.64,0.17, +-2.89,0.37, +-2.85,0.59, +-2.77,0.9, +-2.67,1.16, +-2.18,0.83, +3.11,-0.27, +3.76,-0.27, +4.08,-0.01, +4.24,0.26, +4.12,0.73, +4.02,0.71, +3.98,0.89, +3.84,1.37, +3.87,1.38, +3.28,0.03, +2.57,-1.36, +2.93,-1.27, +3.37,-1.23, +3.71,-1.35, +4.17,-0.98, +4.16,-0.73, +4.32,-0.43, +4.62,0, +4.94,0.1, +4.99,0.75, +5.39,0.95, +5.83,1.03, +6.36,1.12, +6.89,1.22, +7.03,1.77, +7.29,2.65, +7.49,2.73, +7.42,4.02, +7.01,4.05, +6.9,3.98, +6.9,3.98, +6.48,3.74, +5.88,2.46, +5.69,1.96, +5.7,1, +5.25,0.93, +5.01,0.49, +4.98,0.88, +5.02,0.44, +4.98,0.88, +5.08,0.9, +5,0.59, +5.05,0, +5.05,0, +5.38,0, +6.01,-0.23, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.01,-0.15, +5.98,-0.52, +5.98,-0.52, +6.66,-0.17, +7,0, +7,0, +6.97,0.35, +6.89,1.22, +6.57,1.16, +5.93,1.05, +6.48,1.14, +7.27,0.61, +7.54,0, +7.02,-0.96, +7.48,0, +7.81,0, +9.49,-0.17, +7.6,-2.3, +5.78,-1.63, +5.66,-2.06, +5.56,-2.27, +5.22,-3.01, +5.14,-3.4, +4.62,-3.87, +5.89,-3.94, +6.66,-3.85, +6.48,-3.74, +5.85,-3.38, +5.78,-3.34, +5.64,-3.26, +5.82,-3.36, +5.78,-3.34, +6.43,-3.71, +6.36,-4.47, +5.98,-4.45, +5.61,-4.71, +6.29,-5.28, +5.61,-5.61, +5.61,-5.61, +5.3,-5.93, +5.12,-6.11, +5.64,-6.73, +5.75,-6.85, +5.75,-6.85, +6.37,-7.59, +6.37,-7.59, +6.37,-7.59, +6.6,-7.86, +7.9,-7.71, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.98,-7.54, +10.2,-6.37, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +9.88,-5.7, +10.3,-5.55, +8.37,-4.83, +7.74,-4.47, +7.3,-5.11, +7.27,-5.43, +6.68,-5.61, +6.64,-5.57, +7.45,-6.25, +8.47,-7.1, +8.22,-6.15, +8.59,-4.96, +9.26,-5.35, +9.9,-4.88, +10.69,-3.89, +11.48,-4.18, +12.7,-4.62, +13.15,-4.79, +12.58,-4.58, +13.77,-5.75, +13.68,-7.9, +13.52,-7.81, +13.66,-7.89, +13.8,-7.97, +14.12,-8.15, +14.57,-8.41, +14.67,-8.47, +16.5,-9.52, +20.17,-11.64, +22,-12.7, +20.31,-11.73, +18.18,-10.5, +18.28,-9.44, +19.73,-7.18, +11.11,-4.04, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.22,0.02, +-6.46,0.08, +-7,0, +-7,0, +-7,0, +-7.2,-0.44, +-7.12,-0.68, +-6.79,-0.91, +-6.41,-0.87, +-5.54,-0.05, +-5.05,0.03, +-4.24,0.56, +-3.51,0.62, +-2.86,0.56, +-2.77,0.9, +-2.74,1, +-2.84,1.04, +-0.75,0.69, +3.78,-0.71, +4.09,-0.29, +4.57,0, +4.84,0.63, +4.72,0.83, +4.12,0.84, +3.92,1.3, +3.46,0.58, +2.96,-0.82, +3.16,-1.33, +3.68,-1.34, +3.84,-1.4, +4.24,-1.54, +4.65,-1.69, +4.94,-1, +4.98,-0.88, +5.01,-0.49, +5.16,0, +5.38,0, +5.86,0.5, +6.04,1.07, +6.79,1.2, +6.89,1.22, +7.49,1.32, +7.85,1.38, +7.8,1.55, +7.49,2.73, +7.49,2.73, +8.13,2.96, +6.87,3.74, +6.37,3.48, +6.37,2.84, +5.97,2.17, +5.84,1.38, +5.66,1, +5.05,1.01, +5.08,0.9, +4.99,0.73, +4.98,0.88, +4.98,0.88, +5.22,0.92, +4.99,0.73, +5.05,0, +5.27,0, +5.17,-0.11, +5.04,-0.15, +5.38,0, +5.7,0, +5.89,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.03,0, +6.15,0, +7,0, +7,0, +6.98,0.2, +6.9,1.06, +5.95,1.4, +5.78,1.63, +5.75,1.72, +6.19,1.49, +6.33,1.23, +7.25,0.35, +7.69,-1.36, +7.45,-0.38, +7.75,0, +9,-0.96, +8.01,-3.55, +5.93,-3.02, +5.49,-2.72, +5.78,-3.34, +5.46,-3.15, +5.25,-3.16, +4.79,-3.63, +5.1,-3.68, +6.72,-3.51, +5.89,-3.4, +5.7,-3.29, +5.88,-3.22, +5.7,-3, +5.59,-3.08, +6.06,-3.5, +6.34,-3.66, +6.62,-3.82, +6.59,-3.8, +6.26,-4.35, +5.36,-4.89, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +5.74,-5.49, +5.3,-6.08, +5.75,-6.85, +5.75,-6.85, +6.16,-7.35, +6.37,-7.59, +6.37,-7.59, +6.55,-7.42, +7.94,-6.94, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.57,-7.19, +9.13,-7.66, +10.27,-6.27, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +9.76,-5.41, +11.33,-4.96, +10.48,-4.55, +7.74,-4.47, +7.93,-4.58, +8.02,-4.63, +6.62,-3.82, +6.03,-4.9, +6.85,-5.75, +7.74,-6.5, +7.93,-6.23, +9.03,-5.22, +8.84,-5.1, +9.83,-5.04, +11.06,-4.03, +11.55,-4.21, +11.93,-4.34, +12.76,-4.64, +13.88,-5.05, +12.89,-4.69, +13.82,-7.55, +14.22,-8.21, +14.49,-8.37, +14.91,-8.61, +15.71,-9.07, +17.02,-9.83, +16.92,-9.77, +19.36,-11.18, +22.84,-13.19, +22.18,-12.81, +20.45,-11.81, +18.47,-10.16, +19.73,-7.18, +20.53,-7.47, +10.96,-3.99, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.75,0.1, +-6.03,0.72, +-6.54,0.19, +-7.05,-0.3, +-8.06,-3.03, +-8.4,-3.06, +-7.49,-2.73, +-6.98,-2.54, +-6.58,-2.39, +-6.25,-3.08, +-5.31,-3.07, +-4.88,-2.93, +-4,-1.92, +-3.59,0.67, +-2.81,0.85, +-2.86,1.04, +-2.2,1.41, +3.09,-0.27, +4,-0.51, +4.54,-0.04, +5.01,0.29, +4.99,0.81, +4.96,0.95, +4.21,0.04, +3.78,-1, +3.41,-1.7, +3.84,-1.4, +3.84,-1.4, +4.29,-1.56, +4.65,-1.69, +5.05,-1.84, +5.49,-1.44, +5.7,-1, +5.83,-1.03, +5.84,-0.91, +6.03,0, +6.03,0, +6.51,0, +6.78,0.87, +7.03,1.24, +7.64,1.35, +7.85,1.38, +8.01,1.41, +8.28,1.66, +7.9,2.87, +7.49,2.73, +7.49,2.73, +7.3,2.84, +6.58,2.39, +6.58,2.39, +7.49,2.73, +6.36,1.94, +6.23,2.02, +5.66,2.06, +4.98,0.88, +5,0.59, +5.43,0.66, +5.25,0.93, +4.98,0.88, +5.21,0.68, +5.54,0.56, +5.05,0, +5.03,-0.25, +4.98,-0.88, +4.98,-0.88, +5.03,-0.29, +5.27,0, +5.54,0, +6.03,0, +6.03,0, +6.01,-0.23, +6.03,0, +6.01,-0.17, +6.03,0, +6.03,0, +6.78,0, +7,0, +7,0, +6.9,1.08, +6.89,1.22, +5.66,2.06, +5.66,2.06, +5.66,2.06, +5.66,2.06, +5.84,1.94, +6.85,0.5, +8.32,-0.45, +7.65,0, +8.23,-0.22, +9.5,-1.48, +8.71,-2.04, +5.61,-4.71, +5.56,-4.21, +5.98,-3.61, +5.96,-3.44, +5.36,-3.09, +5.22,-3.01, +5.22,-3.01, +7.16,-2.77, +6.79,-3.24, +6.18,-3.25, +6.17,-2.41, +6.27,-2.28, +6.05,-2.54, +5.87,-3.39, +5.85,-3.38, +6.34,-3.66, +6.66,-3.85, +6.33,-3.88, +4.51,-4.17, +5.32,-4.99, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +5.94,-5.29, +5.58,-6.41, +5.75,-6.85, +6.02,-7.17, +6.37,-7.59, +6.78,-7.19, +7.39,-6.58, +7.59,-6.37, +8.15,-6.84, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.56,-7.18, +9.92,-6.04, +10.44,-6.03, +10.44,-6.03, +10.44,-6.03, +10.02,-5.78, +10.13,-4.97, +12.83,-4.96, +14.07,-5.12, +9.71,-4.61, +7.74,-4.47, +7.24,-4.18, +6.59,-3.8, +6.9,-3.98, +6.68,-5.15, +7.59,-6.37, +7.59,-6.37, +8.57,-5.55, +8.79,-5.07, +9.31,-5.37, +10.87,-3.96, +11.33,-4.12, +11.93,-4.34, +12.35,-4.5, +12.7,-4.62, +12.47,-4.54, +12.24,-4.45, +11.93,-5.77, +13.4,-7.74, +14.98,-8.65, +16.33,-9.43, +17.76,-10.25, +20.05,-11.58, +22.92,-13.23, +23.68,-13.67, +22.56,-13.02, +20.81,-11.6, +19.05,-9.28, +19.88,-7.24, +20.12,-7.32, +10.76,-3.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.83,1.03, +-5.93,1.05, +-7.47,-2.49, +-7.84,-4.27, +-8.1,-2.95, +-7.42,-2.87, +-6.45,-2.67, +-6.19,-3.22, +-5.78,-3.34, +-5.15,-3.11, +-4.24,-3.2, +-3.79,-3.33, +-3.65,-0.54, +-3.15,1.13, +-3.35,1.22, +1.29,-0.44, +4,-0.79, +4.34,-0.25, +4.99,0.18, +4.99,0.77, +4.57,0.1, +3.51,-1.97, +3.54,-2.04, +3.72,-1.65, +3.94,-1.43, +4.23,-1.54, +4.64,-1.69, +5.11,-1.86, +5.43,-1.98, +5.88,-1.8, +6.33,-1.23, +6.51,-1.15, +6.48,-1.14, +6.34,-1.12, +6.32,-0.39, +6.78,0, +7,0, +7.32,0.48, +7.85,1.38, +7.85,1.38, +8.17,1.44, +8.81,1.55, +9.42,3.02, +9.09,3.31, +7.98,2.9, +7.49,2.73, +7.39,2.69, +6.45,2.35, +6.97,2.54, +7.49,2.73, +6.58,2.39, +6.58,2.39, +5.66,2.06, +5.75,1.72, +5.72,1.01, +4.99,0.73, +5.34,0.94, +5.73,0.89, +5.89,0.11, +6.03,0, +5.44,0, +5,-0.61, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +5.02,-0.44, +5.72,-0.74, +5.94,-0.93, +5.95,-0.92, +5.93,-1.05, +5.94,-0.93, +5.96,-0.7, +6.01,-0.15, +7,0, +7,0, +6.96,0.41, +6.89,1.22, +6.01,1.82, +6.39,2.33, +6.58,2.39, +5.66,2.06, +5.66,2.06, +5.66,2.06, +6.32,1.75, +7.79,0.2, +8.19,0, +8.97,-0.86, +7.69,-1.36, +6.58,-2.39, +6.05,-5.96, +5.38,-5.16, +5.59,-4.17, +6.06,-3.5, +5.78,-2.86, +5.65,-3.06, +5.22,-3.01, +5.72,-2.97, +6.47,-2.36, +6.27,-2.28, +6.32,-2.3, +6.27,-2.28, +6.46,-2.35, +6.4,-2.76, +6.06,-3.5, +6.67,-3.85, +6.54,-3.78, +5.38,-3.91, +4.86,-4.08, +5.25,-4.61, +5.47,-4.85, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.42,-5.39, +6.61,-5.99, +7.06,-6.15, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +8.36,-7.02, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +9.18,-6.13, +10.02,-5.78, +10.44,-6.03, +9.96,-5.75, +10.22,-5.24, +11.45,-4.51, +13.61,-4.95, +14.07,-5.12, +13.76,-5.01, +3.63,-1.32, +0,0, +6.14,-3.54, +6.78,-3.92, +6.9,-3.98, +8.25,-6.6, +8.15,-6.59, +8.59,-5.24, +8.75,-5.05, +9.6,-5.54, +10.98,-4, +11.46,-4.17, +12.12,-4.41, +12.24,-4.45, +12.24,-4.45, +12.09,-4.4, +11.9,-4.33, +11.79,-5.93, +13.24,-7.65, +14.84,-8.57, +16.22,-9.36, +18.5,-10.68, +21.38,-12.34, +23.61,-13.63, +23.71,-13.69, +22.42,-12.26, +21.33,-9.05, +18.81,-6.85, +19.88,-7.24, +19.34,-7.04, +5.01,-1.82, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.16,0.67, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.62,0.99, +-5.93,1.05, +-7.62,-3.1, +-7.74,-4.47, +-7.53,-4.17, +-6.98,-3.83, +-6.06,-3.5, +-5.69,-3.28, +-5.1,-3.19, +-4.72,-3.73, +-3.73,-3.39, +-2.84,-3.35, +-3.56,0.07, +-3.69,1.33, +-2.96,1.16, +3.19,-0.69, +4.07,-0.53, +4.74,-0.02, +4.32,0.37, +3.38,-1.54, +3.36,-2.29, +3.54,-2.04, +3.84,-1.65, +4.34,-1.58, +4.83,-1.44, +4.98,-1.27, +5.53,-1.29, +5.83,-1.43, +6.36,-1.58, +6.76,-1.31, +6.89,-1.22, +7.03,-1.24, +7.27,-1.08, +7.55,-1.14, +7.33,-1.14, +7.32,0, +7,0, +7.86,0, +7.89,0.86, +7.85,1.38, +8.49,1.5, +8.89,2.14, +9.51,2.68, +9.76,3, +9.68,3.52, +9.01,3.28, +8.4,3.06, +7.26,2.64, +7.39,2.69, +7.38,2.68, +6.58,2.39, +6.47,2.36, +5.66,2.06, +5.66,2.06, +5.82,1.48, +5.93,1.05, +5.93,1.05, +5.97,0.6, +6.03,0, +6.03,0, +6.03,0, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +5.4,-0.95, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +6.11,-0.39, +7,0, +7,0, +6.94,0.69, +5.67,1.3, +5.66,2.06, +6.58,2.39, +6.58,2.39, +6.47,2.36, +5.66,2.06, +5.44,2.54, +5.64,2.05, +6.67,1, +8.53,0, +8.22,-0.87, +6.57,-1.16, +6.65,-1.72, +6.59,-3.06, +5.75,-5.29, +6.34,-4.46, +6.12,-3.36, +6.12,-2.23, +5.66,-2.06, +5.66,-2.06, +5.81,-2.12, +6.58,-2.39, +6.17,-2.25, +6.58,-2.39, +6.42,-2.34, +6.35,-2.31, +6.48,-2.36, +7.06,-2.99, +6.72,-3.61, +5.36,-3.94, +5.55,-4.43, +5.36,-4.5, +5.36,-4.5, +5.77,-4.85, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.27,-5.26, +6.85,-5.75, +6.97,-5.85, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +8.34,-7, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +9.18,-6.13, +9.6,-5.54, +9.6,-5.54, +10.45,-5.23, +11.88,-4.93, +13.38,-4.87, +13.61,-4.95, +13.15,-4.79, +2.14,-0.78, +0,0, +5.02,-2.9, +7.01,-4.05, +7.95,-4.59, +8.41,-6.22, +8.25,-6.6, +8.59,-4.96, +8.59,-4.96, +9.4,-5.43, +12.01,-4.37, +11.63,-4.23, +11.63,-4.23, +11.78,-4.29, +11.33,-4.37, +11.2,-4.71, +11.06,-5.35, +12.02,-6.94, +13.17,-7.61, +14.81,-8.55, +16.81,-9.71, +19.11,-11.03, +21.66,-12.51, +23.4,-13.51, +24.17,-11.29, +23.09,-10.01, +21.25,-7.73, +18.21,-6.63, +19.43,-5.8, +18.9,-4.48, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.48,2.01, +-3.12,1.97, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.55,0.1, +-5.65,1, +-5.97,1.05, +-7.47,-3.42, +-7.74,-4.47, +-7,-4.04, +-6.66,-3.85, +-5.98,-3.45, +-5.15,-3.11, +-4.3,-3.61, +-3.82,-3.46, +-3.05,-3.75, +-2.03,-3.52, +-3.84,1.39, +-3.84,1.4, +1.39,-0.08, +4.02,-0.69, +4.49,-0.21, +3.11,0.01, +2.92,0, +3.21,-0.8, +3.54,-2.04, +3.84,-1.65, +4.67,-1.6, +4.9,-1.16, +5.3,-0.93, +5.83,-1.03, +6.29,-1.11, +6.89,-1.22, +6.89,-1.22, +7.21,-1.27, +8.12,-1.43, +7.76,-1.02, +8.7,-1.53, +9.25,-1.44, +8.81,-1.55, +8.67,-0.69, +8.51,0, +8.71,0.48, +8.05,0.62, +9.08,1.6, +9.4,1.66, +10.25,3.2, +9.76,2.86, +9.76,1.72, +9.47,2.83, +8.78,2.89, +8.4,3.06, +7.76,2.83, +7.49,2.73, +6.69,2.44, +6.58,2.39, +5.87,2.13, +5.66,2.06, +5.66,2.06, +5.68,1.59, +5.39,0.95, +5.93,1.05, +5.99,0.47, +6.03,0, +6.03,0, +6.03,0, +5.14,-0.29, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +5.3,-0.93, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +6.31,-0.96, +7,0, +7,0, +6.89,1.22, +5.02,1.36, +6.05,2.2, +6.58,2.39, +6.58,2.39, +6.58,2.39, +6.46,2.35, +6.09,2.92, +5.78,2.79, +6.47,1.55, +6.75,0, +6.92,-0.93, +6.65,-0.8, +6.62,-1.17, +7.18,-1.27, +6.86,-3.81, +6.64,-4.36, +6.36,-3.15, +6.58,-2.39, +5.66,-2.06, +5.66,-2.06, +6.07,-2.21, +5.66,-2.06, +6.42,-2.34, +6.8,-2.48, +6.3,-2.29, +6.58,-2.39, +7.03,-2.56, +6.88,-2.5, +6.07,-3.03, +5.11,-4.29, +5.57,-4.68, +6.11,-5.12, +5.89,-4.94, +6,-5.03, +6.11,-5.12, +5.86,-4.91, +5.07,-4.79, +5.66,-4.98, +6.35,-5.33, +6.85,-5.75, +7.47,-6.27, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +7.99,-6.71, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.65,-6.9, +9.41,-5.8, +9.6,-5.54, +9.84,-5.34, +11.63,-4.61, +13.02,-4.74, +13.22,-4.55, +12.89,-4.69, +1.89,-0.69, +0,0, +7.62,-4.4, +8.59,-4.96, +8.59,-4.96, +8.58,-5.64, +8.26,-5.43, +8.59,-4.96, +8.7,-5.02, +9.68,-5.59, +11.33,-4.12, +11.33,-4.12, +11.44,-4.16, +11.77,-4.69, +10.44,-6.03, +9.91,-5.5, +11,-6.35, +12.12,-7, +13.44,-7.76, +14.89,-8.6, +17.1,-9.87, +19.58,-11.31, +22,-12.7, +23.4,-13.51, +25.16,-9.16, +23.4,-8.52, +20.9,-7.61, +17.6,-6.4, +20.15,-3.93, +18.38,-3.59, +4.54,-1.65, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.75,2.15, +-5.05,3.94, +-2.95,2.48, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.55,0.1, +-4.88,0.86, +-6.3,1.11, +-7.64,-4.61, +-7.64,-4.41, +-6.9,-3.99, +-6.34,-3.66, +-5.78,-3.34, +-4.87,-3.5, +-3.72,-3.4, +-3.25,-3.87, +-2.53,-3.98, +-1.72,-3.69, +-3.84,1.39, +-1.98,0.33, +3.98,-0.87, +3.45,-0.95, +2.44,-0.02, +2.38,0, +2.92,0, +3.97,0.19, +3.94,-1.94, +4.75,-1.73, +4.86,-1.3, +5.62,-0.99, +5.93,-1.05, +6.48,-1.14, +6.89,-1.22, +7.1,-1.25, +7.85,-1.38, +8.97,-1.58, +8.98,-0.99, +8.16,-0.33, +8.22,-0.92, +9.18,-0.51, +9.23,-1.63, +9,-1.59, +9.65,-1.12, +9.91,0, +10.1,0.81, +10.04,1.58, +10.91,1.92, +10.6,3.09, +9.65,2.6, +9.63,1.7, +8.96,2.03, +8.4,3.06, +8.1,2.95, +7.49,2.73, +7.39,2.69, +6.58,2.39, +6.35,2.31, +5.66,2.06, +5.66,2.06, +5.46,1.99, +4.9,1.78, +4.95,0.98, +5.62,0.99, +5.95,0.92, +6,0.35, +6.02,0.12, +6.03,0, +5.61,0, +5,-0.63, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +4.98,-0.88, +5.4,-0.95, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +6.19,-0.5, +6.32,0.29, +6.75,0.11, +6.57,1.16, +4.83,1.44, +5.66,2.06, +6.58,2.39, +6.71,2.44, +6.88,2.5, +6.58,2.39, +6.23,3.13, +5.5,2.91, +5.83,1.44, +5.85,-0.42, +6.25,-0.6, +6.24,0, +7.24,0, +7.65,0, +8,-1.59, +7.43,-4.73, +6.45,-3.32, +6.58,-2.39, +5.97,-2.17, +5.66,-2.06, +6.12,-2.23, +6.69,-2.44, +5.5,-2, +7.23,-2.63, +7.18,-2.62, +6.78,-2.47, +6.92,-2.52, +7.49,-2.73, +6,-3.12, +5.2,-4.36, +5.57,-4.68, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +5.98,-5.02, +5.47,-4.59, +5.36,-4.5, +5.47,-4.59, +5.98,-5.02, +6.85,-5.75, +7.15,-6, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +7.59,-6.37, +8.15,-6.84, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.67,-6.86, +9.41,-5.8, +10.02,-5.48, +11.9,-4.33, +12.61,-3.08, +12.96,-2.29, +12.91,-2.79, +3.78,-1.37, +0,0, +6.21,-3.59, +8.12,-4.69, +7.88,-4.55, +7.54,-5.72, +7.13,-5.73, +8.02,-4.63, +8.47,-4.89, +9.39,-5.42, +9.72,-5.61, +10.3,-4.8, +10.81,-5.55, +10.7,-5.92, +10.2,-5.89, +10.02,-5.78, +11.09,-6.4, +12.4,-7.16, +13.49,-7.79, +15.36,-8.87, +17.22,-9.94, +19.72,-11.39, +22.15,-11.92, +24.86,-9.96, +24.93,-9.07, +23.2,-8.44, +19.73,-7.18, +17.06,-4.82, +20.2,-3.56, +17.14,-4.23, +3.29,-1.2, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.5,3.78, +-5.07,4.45, +-4.35,4.19, +-0.43,0.51, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.98,0.35, +-6.4,1.13, +-7.05,-5.46, +-7.38,-4.26, +-6.9,-3.99, +-6.25,-3.61, +-5.41,-3.58, +-4.69,-3.77, +-3.39,-3.73, +-2.51,-3.6, +-1.95,-4.03, +-1.17,-3.9, +-3.51,0.74, +0.91,-0.9, +1.92,-3.52, +2.57,-3.68, +1.94,0, +2.5,0, +3.06,0, +4.04,0.47, +4.64,-0.61, +5.02,-1.33, +5.83,-1.03, +6.13,-1.08, +6.73,-1.19, +7,-1.23, +7.76,-1.19, +8.81,-1.55, +9.76,-1.72, +9.78,-1.55, +9.78,-1.53, +9.15,-0.39, +8.15,-0.66, +8.6,-1.39, +9.55,-1.68, +9.76,-1.72, +9.85,-0.77, +9.91,0, +9.87,0.49, +9.76,1.72, +10.91,1.92, +10.09,2, +9.6,1.69, +9.28,1.64, +8.46,2.02, +8.4,3.06, +7.95,2.89, +7.49,2.73, +7.18,2.62, +6.58,2.39, +6.18,2.25, +5.66,2.06, +5.66,2.06, +5.36,1.95, +4.75,1.73, +4.77,1.63, +5.08,1.02, +5.93,1.05, +5.93,1.05, +5.93,1.05, +5.98,0.52, +5.27,0, +5.02,-0.44, +4.98,-0.88, +4.88,-1.24, +4.86,-1.3, +4.89,-1.2, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.93,-1.05, +5.45,-0.47, +5,0.59, +5.31,0.73, +4.98,0.88, +5.83,1.03, +5.76,1.24, +5.66,2.06, +6.37,2.32, +6.68,2.43, +7.15,3.45, +6.5,2.85, +6.16,3.28, +5.71,2.95, +5.4,1.07, +5.78,-0.35, +6.08,-0.58, +7.32,0, +8.11,0, +8.08,0, +8.73,-1.24, +8.02,-4.63, +7.16,-3.16, +6.68,-2.43, +6.32,-2.3, +5.92,-2.16, +6.21,-2.26, +6.67,-2.06, +6.58,-2.39, +6.8,-2.48, +7.29,-2.65, +7.03,-2.56, +7.1,-2.58, +7.08,-2.58, +6.15,-2.86, +5.47,-4.59, +6.02,-5.05, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.01,-5.04, +6.19,-5.01, +6.74,-5.66, +7.06,-5.93, +7.59,-6.37, +7.35,-6.16, +7.59,-6.37, +7.59,-6.37, +7.72,-6.2, +7.59,-6.37, +7.93,-6.65, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.63,-6.92, +10.32,-5.62, +12.11,-2.86, +12.23,-2.16, +12.83,-2.26, +12.99,-2.29, +6.64,-1.41, +0,0, +7.34,-3.96, +8.16,-4.71, +5.81,-3.35, +3.42,-2.87, +3.57,-2.66, +7.35,-4.73, +8.49,-4.9, +9.09,-5.25, +9.57,-5.53, +10.08,-5.82, +10.2,-5.89, +10.44,-6.03, +10.44,-6.03, +10.16,-5.86, +11.42,-6.59, +12.58,-7.26, +13.8,-7.97, +15.43,-8.91, +17.55,-10.13, +20.19,-10.48, +23.18,-8.99, +24.74,-9, +24.71,-8.99, +23.02,-8.38, +18.08,-6.58, +16.59,-2.93, +18.76,-3.31, +17.18,-3.97, +2.92,-1.06, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.03,5.24, +-0.65,0.62, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.34,0.77, +-6.85,-5.75, +-7.08,-4.29, +-6.72,-3.88, +-5.89,-3.75, +-4.62,-3.87, +-4.2,-3.53, +-3.03,-4.02, +-2.1,-3.87, +-1.35,-4.18, +-0.63,-4.01, +-0.35,-3.45, +1.66,-4.37, +2.47,-4.03, +3.6,-4.26, +2.56,0.37, +3.03,0.14, +3.89,0.41, +4.77,0.76, +5.57,-0.23, +5.82,-1.11, +6.57,-1.16, +6.94,-0.69, +7.27,-0.54, +8.68,-0.57, +9.8,-1.29, +9.76,-1.72, +9.85,-0.77, +9.83,-0.96, +9.81,-1.23, +9.48,-0.82, +8.83,-1.23, +8.86,-0.97, +9.6,-1.69, +9.76,-1.72, +9.85,-0.77, +9.91,0, +9.86,0.65, +9.76,1.72, +10.1,1.97, +9.76,1.72, +9.51,1.85, +8.49,2.72, +8.27,3.01, +7.75,2.82, +7.49,2.73, +7.49,2.73, +7.49,2.73, +6.58,2.39, +6.42,2.48, +5.6,2.2, +5.47,2.47, +5.27,2.64, +4.5,2.26, +4.75,1.73, +4.78,1.62, +5.5,1.56, +5.9,1.17, +5.93,1.05, +5.74,1.01, +5.01,0.55, +4.99,-0.44, +4.98,-0.88, +4.85,-1.35, +4.78,-1.61, +5.01,-1.29, +5.69,-1.95, +5.63,-1.72, +5.71,-1.89, +5.8,-1.55, +5.9,-1.16, +5.31,-0.3, +5,0.59, +4.98,0.88, +5.3,0.93, +5.93,1.05, +5.8,1.55, +5.85,2.13, +6.37,2.5, +6.42,3.71, +6.9,3.98, +6.8,3.92, +6.06,3.5, +5.71,2.54, +5.32,1.27, +5.19,0, +6.19,0, +7.31,0.14, +8.91,0.35, +9.1,0, +8.11,0, +8.44,-1.93, +7.62,-2.77, +6.58,-2.39, +6.58,-2.39, +6.58,-2.39, +6.73,-1.19, +6.8,-1.55, +6.78,-2.47, +6.27,-2.28, +7.03,-2.56, +7.29,-2.65, +7.15,-2.6, +5.89,-3.03, +5.06,-4.25, +5.55,-4.65, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.7,-5.12, +7.38,-4.26, +6.85,-5.75, +6.87,-5.16, +7.6,-4.39, +7.53,-4.97, +7.59,-6.37, +7.59,-6.37, +8.34,-5.31, +8.59,-4.96, +8.59,-4.96, +8.06,-6.39, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +10.14,-5.56, +11.69,-2.77, +11.87,-2.09, +11.87,-2.09, +12.35,-2.18, +12.47,-2.2, +1.21,-0.21, +0,0, +0,0, +0,0, +0,0, +0,0, +5.73,-4.47, +8.21,-4.74, +8.59,-4.96, +9.43,-5.44, +9.6,-5.54, +9.81,-5.66, +9.69,-6.81, +9.51,-6.87, +10.67,-6.45, +11.88,-6.86, +13.2,-7.62, +14.37,-8.29, +15.77,-9.1, +18.57,-9.28, +21.83,-7.94, +23.41,-8.52, +24.02,-8.74, +24.09,-8.77, +21.68,-7.89, +16.48,-3.91, +15.54,-2.74, +17.63,-3.11, +14.13,-3.27, +3.5,-1.27, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.51,3.51, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.68,0.01, +-6.6,-5.54, +-6.28,-4.69, +-6.08,-3.66, +-4.72,-3.96, +-4.39,-3.69, +-3.46,-3.66, +-2.63,-4.3, +-1.47,-4.03, +-0.74,-4.11, +-0.09,-4.07, +0.78,-4.27, +2.08,-4.58, +3.39,-3.86, +4.62,-3.87, +3.46,0.28, +4.13,0.73, +4.86,0.86, +5.52,0.97, +6.04,0.83, +6.58,-1.02, +7.09,-0.52, +7.73,0, +8.73,0, +10.23,0, +9.91,0, +9.88,-0.38, +9.91,0, +9.89,-0.25, +9.86,-0.65, +9.88,-0.38, +9.9,-0.22, +9.02,-0.67, +9.76,-1.72, +9.8,-1.34, +9.89,-0.25, +9.91,0, +9.88,0.38, +9.4,1.66, +9.61,2.28, +9.76,1.72, +8.91,3.24, +8.27,3.01, +7.49,2.73, +7.49,2.73, +7.41,2.91, +7.32,3.09, +7.1,3.57, +6.29,3.47, +5.82,3.36, +5.27,2.89, +5.05,2.92, +4.5,2.6, +4.38,2.53, +4.42,2.44, +4.5,2.26, +4.69,1.86, +5.32,1.94, +5.32,1.69, +4.85,1.36, +4.94,1, +5.34,1.49, +5.29,1.4, +5.17,0.91, +5.07,1.34, +4.89,-0.67, +5.66,-2.06, +5.46,-1.37, +5.05,0, +5.05,0, +5.05,0, +5.05,0.1, +4.99,0.78, +4.98,0.88, +5.8,1.02, +5.77,1.66, +5.95,1.47, +5.97,2.17, +6.4,2.76, +6.34,3.66, +6.9,3.98, +6.9,3.98, +6.62,3.82, +6.09,2.86, +5.37,1.3, +4.81,0, +5.36,0.2, +6.75,0.98, +8.42,1, +9.02,0.86, +8.21,0, +8.9,-1.13, +8,-2.91, +6.92,-2.52, +6.58,-2.39, +6.49,-2.18, +6.89,-1.22, +6.75,-1.19, +7.09,-2.36, +6.27,-2.28, +6.24,-2.55, +7.38,-2.68, +5.19,-3.13, +4.45,-3.73, +5.36,-4.5, +6.13,-4.85, +6.73,-4.98, +6.34,-5.03, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.11,-5.12, +6.21,-5.21, +6.72,-5.19, +7.32,-4.23, +6.89,-4.28, +6.88,-4.57, +7.22,-4.17, +7.74,-4.47, +7.02,-5.89, +7.18,-6.03, +7.68,-5.69, +7.97,-5.56, +8.59,-4.96, +8.44,-5.16, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +9.48,-6.43, +11.11,-2.85, +11.87,-2.09, +11.97,-0.9, +11.99,-0.76, +12.28,-1.1, +5.77,-1.02, +0,0, +0,0, +0,0, +0,0, +0,0, +4.57,-3.83, +7.1,-5.96, +8.47,-5.58, +8.55,-6.02, +8.65,-6.9, +8.77,-7.36, +9.23,-7.75, +8.98,-7.54, +10.84,-7.56, +12.59,-7.27, +13.8,-7.97, +14.98,-8.65, +16.87,-9.74, +19.99,-7.76, +21.86,-7.96, +22.35,-8.14, +23.25,-8.46, +22.81,-8.3, +18.86,-6.52, +14.33,-2.53, +14.9,-2.63, +16.55,-2.92, +11.86,-3.69, +3.78,-1.37, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.25,7.45, +-1.16,1.36, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.56,0.63, +-6.78,-2.19, +-6.11,-5.12, +-6.17,-4.09, +-6.06,-3.5, +-4.68,-3.78, +-4.37,-3.67, +-3.52,-3.6, +-2.13,-4.56, +-1.03,-4.27, +-0.24,-4.06, +0.51,-4.04, +1.24,-4.32, +2.68,-4.25, +4.12,-3.78, +5.13,-3.56, +4.93,-0.74, +4.64,1.69, +5.17,1.75, +5.84,1.38, +6.4,1.81, +6.82,1.03, +8.09,0, +9.37,0, +9.76,0.41, +9.7,1.46, +9.94,0, +9.91,0, +9.91,0, +9.91,0, +9.91,0, +9.91,0, +9.91,0, +9.48,0, +9.81,0, +9.89,-0.29, +9.81,0, +9.91,0, +9.9,0.22, +8.94,1.58, +9.35,2.16, +9.43,2.97, +8.53,3.11, +7.59,2.76, +7.23,3.29, +6.78,3.92, +6.8,3.92, +6.9,3.98, +6.5,4.13, +6.06,3.5, +5.59,3.23, +4.7,3.19, +4.04,3.01, +4,3.07, +3.73,3.39, +3.72,3.4, +3.87,3.25, +4.21,2.77, +4.26,2.69, +4.56,2.13, +4.61,2.03, +4.54,2.33, +4.71,2.87, +5.15,2.77, +5.27,2.91, +4.88,3.49, +4.78,3.36, +5.32,0.22, +5.02,1.7, +5.22,0.69, +5.05,0, +5.04,0.18, +5,0.66, +4.98,0.88, +5.4,0.95, +5.93,1.05, +5.71,1.89, +5.93,1.05, +5.93,1.91, +6.43,2.71, +6.34,3.66, +6.9,3.98, +6.9,3.98, +6.9,3.98, +6.55,2.78, +5.48,1.2, +4.49,0.13, +4.59,0.58, +6.08,1.62, +6.85,2.31, +7.64,1.55, +8.89,0.58, +9.79,-1.68, +8.4,-3.06, +7.88,-2.87, +7.03,-1.96, +6.8,-1.55, +7.85,-1.38, +7,-1.23, +7.33,-2.09, +6.71,-2.44, +5.83,-2.84, +6.02,-2.81, +4.83,-3.56, +5.11,-4.29, +5.36,-4.5, +6.6,-4.84, +7.74,-4.47, +7.95,-4.59, +7.91,-4.57, +7.62,-4.65, +6.91,-4.93, +6.65,-4.91, +6.11,-5.12, +6.11,-5.12, +6.19,-5, +6.4,-4.7, +7.18,-4.15, +7.41,-4.1, +7.15,-5.32, +6.85,-5.75, +6.85,-5.75, +6.85,-5.75, +7.06,-5.93, +8.59,-4.96, +8.59,-4.96, +8.62,-6.71, +8.49,-7.12, +8.49,-7.12, +8.49,-7.12, +9,-6.68, +10.74,-3.97, +11.12,-0.52, +12.05,0, +12.05,0, +12.05,0, +11.7,-0.85, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.83,-4.89, +7.93,-6.65, +8.49,-7.12, +8.49,-7.12, +8.86,-7.43, +9.23,-7.75, +9.4,-7.89, +11.43,-7.75, +13.56,-7.83, +14.33,-8.27, +15.94,-9.2, +18.73,-8.18, +20.46,-7.45, +21.4,-7.79, +22.47,-8.18, +22.91,-8.34, +21.98,-7.42, +16.98,-5.23, +12.99,-2.58, +13.7,-2.74, +15.46,-2.73, +7.52,-2.74, +1.3,-0.47, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.35,-1.3, +-6.02,-5.05, +-5.96,-3.64, +-5.87,-3.39, +-5.15,-3.11, +-4.33,-3.01, +-3.4,-3.72, +-1.57,-3.86, +-0.41,-4.15, +0.35,-4.05, +0.99,-4.17, +2.06,-4.24, +3.23,-3.85, +4.84,-3.55, +5.69,-3.28, +5.43,-0.58, +4.83,1.89, +5.66,2.06, +5.97,2.17, +7.36,2.68, +7.24,2.15, +8.75,1.07, +9.91,0, +10.17,0.43, +10.66,1.88, +10.07,0.9, +9.91,0, +9.91,0, +9.91,0, +9.91,0, +9.91,0, +9.88,0.34, +9.53,0.22, +9.64,0, +9.37,0, +8.38,0.91, +8.63,1.23, +8.54,1.46, +7.85,1.81, +7.94,2.45, +8.79,3.2, +7.93,3.05, +6.85,3.6, +6.06,3.5, +5.97,3.62, +5.67,4.05, +5.36,4.5, +5.36,4.5, +5.2,4.51, +4.3,4.04, +3.79,3.56, +3.53,3.59, +3.16,3.91, +2.94,4.09, +2.94,4.09, +2.93,4.1, +3.22,3.84, +3.56,3.56, +3.83,3.22, +3.66,3.31, +2.72,3.74, +3.22,4.01, +4.25,3.66, +4.51,3.98, +4.51,3.98, +4.17,4.32, +4.41,4.04, +4.08,3.23, +4.78,1.23, +5.01,0.49, +4.98,0.88, +4.98,0.88, +5.08,0.9, +5.81,1.03, +5.93,1.05, +5.78,1.61, +5.82,1.87, +6.06,1.83, +6.51,2.53, +6.06,3.5, +6.9,3.98, +6.66,3.85, +6.4,3.69, +6.28,2.45, +5.47,0.82, +5.05,0, +5.32,0.62, +6.12,2.23, +6.5,2.55, +7.88,2.52, +8.48,1.7, +8.92,0.26, +8.74,-1.56, +8.57,-1.51, +8.81,-1.55, +7.32,-1.29, +7.24,-1.28, +7.53,-1.33, +7.55,-1.98, +8.2,-2.98, +7.33,-3.06, +5.78,-3.34, +5.88,-3.75, +5.42,-3.98, +5.36,-4.5, +7.11,-4.11, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.64,-4.61, +7.13,-4.72, +7.06,-4.61, +7.57,-3.75, +7.93,-3.76, +7.88,-3.42, +7.62,-4.65, +7.15,-5.32, +6.85,-5.75, +6.85,-5.75, +7.37,-5.94, +8.59,-4.96, +8.73,-5.04, +8.9,-6.53, +8.49,-7.12, +8.49,-7.12, +9.13,-7.66, +9.23,-7.75, +10.77,-3.97, +11.08,0, +11.51,0, +12.05,0, +12.05,0, +11.91,-0.24, +3.76,-0.43, +0,0, +0,0, +0,0, +0,0, +0,0, +2.23,-1.87, +8.04,-6.75, +8.49,-7.12, +8.49,-7.12, +8.7,-8.08, +9.05,-7.93, +10.16,-8.53, +11.36,-8.56, +13.52,-7.81, +14.93,-8.62, +16.75,-9.67, +19.88,-7.24, +20.64,-7.51, +21.35,-7.77, +22.08,-8.03, +22.68,-5.78, +21.73,-4.28, +15.76,-2.78, +12.34,-4.09, +13.44,-3.7, +13.91,-5.06, +8.31,-3.03, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.57,-1.26, +-5.99,-4.8, +-6.06,-3.5, +-5.69,-3.28, +-5.03,-2.91, +-4.38,-2.53, +-3.43,-3.67, +-1.16,-3.86, +-0,-3.9, +0.88,-4.07, +1.83,-3.95, +2.97,-3.9, +4.2,-3.47, +5.7,-2.96, +6.46,-2.64, +5.46,3.79, +5.87,3.39, +6.1,2.96, +6.1,2.65, +6.54,3.24, +7.21,2.59, +8.72,2.33, +9.44,1.23, +9.78,1.53, +9.76,1.72, +9.76,1.72, +9.86,0.57, +9.85,0.77, +9.83,0.98, +9.82,1.08, +9.78,1.53, +9.76,1.72, +9.24,1.4, +8.94,0, +9.03,0.17, +7.95,2.08, +7.63,2.61, +7.79,2.84, +6.58,2.39, +6.41,3.07, +8.34,3.2, +6.98,3.83, +6.2,3.58, +5.59,3.98, +4.86,4.39, +4.39,4.69, +4.26,5.05, +3.99,5.32, +3.58,5.12, +2.62,4.48, +2.5,4.33, +2.17,4.54, +2.13,4.56, +1.62,4.74, +1.44,4.83, +1.73,4.75, +2.03,4.61, +2.26,4.5, +2.7,4.23, +1.73,4.75, +1.72,4.5, +2.17,4.54, +3.03,4.61, +3.73,4.64, +3.68,4.75, +3.75,4.7, +3.86,4.6, +3.45,3.78, +4.05,2.76, +4.98,0.88, +4.98,0.88, +5.72,1.01, +5.93,1.05, +5.93,1.05, +5.93,1.05, +5.9,1.19, +6.18,2.25, +6.58,2.39, +6.58,2.39, +6.18,3.57, +6.34,3.66, +6.06,3.5, +6.06,3.5, +6.51,2.53, +6.22,1.63, +6.12,0.47, +6.29,0.73, +6.88,2.09, +6.73,2.93, +6.82,3.4, +7.85,2.31, +8.85,0.67, +9.42,-0.91, +9.06,-1.38, +8.81,-1.55, +7.85,-1.38, +7.87,-1.13, +8.09,-1.11, +7.53,-2.56, +8.55,-3.11, +7.49,-3.3, +6.53,-3.77, +6.06,-3.5, +6.39,-3.87, +6.24,-3.74, +7.02,-4.05, +7.84,-4.27, +7.96,-4, +7.84,-4.27, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.84,-4.52, +8.63,-4.1, +8.55,-3.11, +8.4,-3.06, +8.53,-3.29, +7.74,-4.47, +7.74,-4.47, +7.07,-5.43, +6.85,-5.75, +7.77,-5.88, +8.59,-4.96, +8.84,-5.1, +9.41,-5.8, +8.49,-7.12, +8.74,-7.33, +9.23,-7.75, +9.68,-7.1, +10.86,-3.07, +11.08,0, +11.24,0, +11.93,0, +12.05,0, +12.05,0, +9.02,-0.24, +1.48,-0.26, +0,0, +0,0, +0,0, +0,0, +1.41,-1.19, +6.5,-5.46, +8.49,-7.12, +8.86,-7.43, +8.29,-8.69, +8.74,-8.24, +9.98,-8.37, +11.44,-8.39, +13.68,-7.9, +15.82,-9.14, +18.04,-9.36, +20.18,-7.35, +20.79,-7.57, +21.55,-7.84, +21.78,-6.25, +22.59,-3.98, +20.67,-3.65, +15.4,-2.72, +12.7,-4.62, +13.46,-4.9, +13.56,-4.93, +10.35,-3.77, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.51,-0.26, +-6.06,-3.5, +-5.61,-2.43, +-5.66,-2.06, +-5.09,-1.99, +-4.54,-2.17, +-3.27,-3.12, +-0.12,-4.05, +0.77,-3.46, +1.75,-3.67, +2.82,-3.87, +3.9,-3.56, +5.25,-3.12, +6.52,-2.52, +5.51,1.91, +5.69,4.78, +6.36,4.2, +5.65,3.61, +5.77,3.51, +6.06,3.5, +6.35,3.44, +7.73,3.18, +8.7,2.8, +8.97,1.76, +9.76,1.72, +9.3,2.2, +8.91,1.57, +8.94,1.58, +9.64,1.7, +9.44,1.67, +9.05,1.59, +9.35,1.65, +9.23,1.63, +8.84,1.21, +8.81,1.55, +7.87,3.03, +7.95,3.26, +7.73,3.52, +6.55,3.6, +5.97,3.63, +6.69,4.44, +6.22,4.39, +5.69,4.83, +4.77,4.48, +3.73,4.72, +2.64,5.23, +2.02,5.72, +1.93,6.24, +1.29,6.32, +0.92,6.1, +0.29,5.85, +0.47,5.66, +0.35,5.41, +-0.15,5.39, +0.17,5.69, +0.64,5.65, +0.64,5.65, +1.1,5.49, +0.87,4.92, +0.96,4.87, +0.65,4.92, +0.73,4.99, +1.72,4.88, +1.77,5.61, +2.85,5.29, +3.23,5.07, +3.2,5.08, +2.53,4.68, +3.22,4.48, +4.64,1.78, +5.22,0.92, +5.93,1.05, +6.03,1.06, +6.25,1.1, +5.93,1.05, +5.9,1.16, +6.47,2.36, +6.58,2.39, +6.58,2.39, +6.4,2.76, +6.06,3.5, +6.06,3.5, +6.06,3.5, +6.19,3.22, +6.63,2.2, +6.87,1.21, +6.89,1.22, +7.5,2.05, +7.61,3.32, +6.82,3.94, +7.23,2.63, +8.56,0.78, +9.91,0, +9.91,0, +9.91,0, +9.4,-0.88, +9.24,0.45, +7.93,-0.46, +7.59,-2.34, +8.13,-3.12, +8.4,-3.06, +7.02,-4.05, +6.67,-3.85, +7.02,-4.05, +6.9,-3.98, +7,-4.04, +7.91,-4.12, +8.55,-3.11, +8.4,-3.06, +8.18,-3.53, +8.07,-3.76, +7.74,-4.47, +7.74,-4.47, +8.31,-4.79, +9.32,-3.39, +9.21,-3.35, +8.66,-3.15, +8.64,-3.7, +7.74,-4.47, +7.74,-4.47, +7.36,-5.02, +6.85,-5.75, +7.72,-6.2, +8.09,-5.66, +8.59,-5.24, +9.6,-5.54, +8.49,-7.12, +8.67,-7.28, +9.23,-7.75, +9.63,-7.17, +10.81,-4.71, +11.47,-0.43, +11.91,0, +11.95,1.2, +11.89,1.86, +12.3,0.3, +13.02,0, +8.83,-0.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.91,-2.44, +2.22,-1.86, +0,0, +3.24,-2.72, +0,0, +3.95,-2.75, +13.9,-8.02, +16.5,-9.52, +18.67,-8.68, +20.38,-7.42, +20.64,-7.51, +21.18,-6.55, +20.67,-3.65, +16.22,-2.86, +20.2,-3.56, +15.6,-3.63, +13.61,-4.95, +13.76,-5.01, +13.76,-5.01, +8.73,-3.18, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3,0.6, +-7.73,0.98, +-6.14,-2.81, +-5.78,-1.61, +-5.76,-1.17, +-5,-1.2, +-4.91,-1.06, +-3,-0.75, +1.65,-3.63, +1.71,-2.95, +2.35,-2.94, +3.78,-3.34, +4.76,-3.11, +3.69,2.97, +5.07,2.62, +4.92,5.36, +5.45,5.78, +6.11,5.12, +5.26,4.39, +5.26,4.59, +5.54,4.25, +6.06,3.5, +6.34,3.66, +6.06,3.5, +6.06,3.5, +7.25,3.09, +6.42,3.49, +8.46,2.84, +8.6,2.31, +8.54,2.56, +8.55,2.49, +8.63,2.2, +8.71,2.66, +8.8,2.97, +8.19,2.92, +8.03,3.07, +7.74,4.47, +7.74,4.47, +7.74,4.47, +7.24,5.2, +6.2,5.2, +5.68,5.54, +5.21,5.99, +4.18,6.32, +3.09,5.31, +1.28,4.94, +0.02,5.72, +-0.44,5.18, +-0.89,5.48, +-1.25,5.64, +-1.58,6.22, +-1.15,6.53, +-1.14,6.47, +-1.11,6.29, +-0.92,6.08, +-0.79,6.5, +-0.52,6.52, +0,6.51, +-2.44,5.81, +-2.47,5.71, +-2.65,5.08, +-1.6,6.05, +0.24,5.53, +0.26,6, +0,6.03, +0.91,5.87, +1.26,5.83, +2.32,4.78, +2.71,5.22, +3.05,5.07, +3.83,4.44, +5.67,1.11, +5.9,1.16, +6.07,1.07, +6.89,1.22, +6.57,1.16, +6.65,1.17, +6.52,1.97, +6.58,2.39, +6.58,2.39, +6.32,2.95, +6.06,3.5, +6.06,3.5, +6.06,3.5, +6.06,3.5, +6.42,2.73, +6.62,2.23, +6.85,1.38, +7.25,2.51, +7.67,3, +6.9,3.98, +6.87,3.3, +7.37,1.76, +9.24,0.26, +9.79,0, +9.91,0, +10.11,0, +9.91,0, +8.89,-0.6, +8.44,-2.39, +8.79,-3.2, +8.88,-3.4, +7.14,-4.12, +7.18,-4.15, +7.84,-4.75, +7.32,-4.23, +6.9,-3.98, +7.84,-3.28, +8.4,-3.06, +8.4,-3.06, +8.4,-3.06, +8.4,-3.06, +7.85,-4.24, +7.74,-4.47, +8.31,-4.79, +9.32,-3.39, +9.32,-3.39, +9.2,-3.76, +7.92,-4.35, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.45,-4.9, +7.28,-6.1, +7.59,-6.37, +8.26,-5.43, +9.6,-5.54, +8.71,-6.81, +8.74,-7.33, +9.23,-7.75, +9.5,-7.36, +10.44,-6.03, +11.32,-2.68, +12.02,0.42, +11.87,2.09, +11.87,2.09, +12.61,0.52, +13.02,0, +13.02,0, +3.87,-0.45, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +13.99,-8.08, +18.4,-7.73, +19.86,-7.23, +20.77,-5.36, +20.54,-3.62, +9.22,-1.63, +14.36,-2.53, +19.84,-3.15, +16.63,-3.9, +14.98,-5.45, +14.79,-6.14, +12.7,-6.61, +1.62,-0.59, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.32,0.58, +-7.54,2.72, +-6.72,-1.32, +-6.08,-0.92, +-5.45,-0.43, +-5.05,-0.1, +-4.67,0.2, +-1.07,-0.08, +3.21,-2.49, +2.58,-2.44, +3.04,-2.42, +4.08,-2.58, +5.23,-2.23, +2.39,6.74, +2.98,7.73, +3.99,6.9, +4.98,6.2, +5.86,5.37, +4.11,5.46, +4.21,5.56, +4.93,4.93, +5.44,4.39, +5.52,4.28, +5.36,4.5, +5.85,3.8, +5.98,3.61, +6.73,3.37, +8.16,3.59, +8.34,3.2, +8.31,3.26, +8.4,3.06, +8.33,3.22, +8.4,3.06, +7.32,4.44, +6.54,5.05, +6.6,5.54, +6.85,5.75, +8.21,4.94, +8.11,5.08, +7.42,6.55, +6.18,7.1, +5.47,6.78, +4.53,6.81, +2.96,5.52, +1.23,5.07, +-0.71,5.17, +-2.27,5.21, +-2.27,5.56, +-2.25,6.17, +-2.34,6.42, +-2.06,7.04, +-1.35,7.64, +-1.36,7.71, +-1.36,7.71, +-1.33,7.53, +-1.36,7.73, +-1.3,7.37, +-1.41,7.05, +-3.82,6.62, +-3.66,6.34, +-3.38,6.47, +-2.19,6.85, +-2.32,6.88, +-1.61,6.26, +0,6.13, +0,5.78, +0.29,6.23, +1.79,5.6, +2.28,5.38, +2.11,4.93, +2.94,4.89, +5.21,1.89, +5.66,2.06, +5.96,1.89, +6.75,1.74, +6.79,1.61, +6.75,1.19, +6.75,1.74, +6.58,2.39, +6.58,2.39, +6.58,2.39, +6.25,3.08, +6.06,3.5, +6.06,3.5, +6.06,3.5, +6.32,2.95, +6.58,2.39, +6.58,2.39, +7.03,2.56, +7.42,3.46, +6.81,3.93, +6.54,3.78, +7.8,1.58, +8.23,0.77, +8.94,0, +9.91,0, +9.62,0, +9.91,0, +9.42,-0.91, +8.63,-2.22, +8.4,-3.06, +9.71,-3.53, +8.33,-4.07, +7.46,-4.31, +7.46,-4.31, +7.38,-4.26, +7.28,-4.2, +7.3,-3.42, +8.27,-3.01, +8.4,-3.06, +8.4,-3.06, +8.4,-3.06, +8.4,-3.06, +7.91,-4.12, +8.16,-4.71, +9.32,-3.39, +9.32,-3.39, +9.47,-3.86, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.64,-5.74, +7.59,-6.37, +8.68,-5.37, +9.6,-5.54, +9.04,-6.33, +8.49,-7.12, +9.46,-7.19, +10.24,-6.31, +10.44,-6.03, +11.13,-4.33, +11.99,0.7, +11.87,2.09, +12.11,2.14, +12.99,0.38, +13.12,0.3, +13.51,0, +6.19,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.83,-1.03, +16.57,-6.03, +19.34,-5.96, +19.88,-3.5, +7.04,-1.24, +0,0, +15.22,-0.43, +19.72,-2.11, +17.47,-3.08, +15.74,-5.73, +14.64,-7.7, +10.63,-6.14, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.98,2.54, +-7.83,0.82, +-6.37,0.26, +-5.88,0.45, +-5.21,0.7, +-4.7,0.83, +-0.86,-0.06, +3.52,-1.76, +3.52,-2.03, +4,-1.81, +4.81,-1.44, +6.21,-1.27, +1.72,8.76, +3.06,8.4, +3.67,7.48, +3.69,6.27, +3.29,6.2, +2.95,6.32, +3.62,5.97, +4.39,5.44, +4.69,5.17, +4.99,4.87, +5.07,4.79, +5.17,4.69, +5.36,4.5, +5.7,4.34, +6.76,4.67, +7.62,4.4, +7.53,4.35, +7.16,5.56, +7.63,4.63, +6.71,5.62, +5.32,7.15, +5.18,7.25, +5.75,6.85, +6.18,6.57, +6.68,6.03, +6.84,6.35, +6.2,7.72, +5.74,8.04, +4.89,8.47, +4.07,7.93, +1.41,6.13, +-0.73,6.28, +-2.64,6.16, +-2.89,6.46, +-2.74,7.13, +-2.73,7.49, +-2.17,7.86, +-1.61,8.23, +-1.48,8.4, +-1.51,8.57, +-1.5,8.52, +-1.48,8.4, +-1.38,7.85, +-1.38,7.85, +-2.49,7.33, +-4.31,7.46, +-4.23,7.32, +-3.82,7.65, +-2.73,7.49, +-3.03,7.47, +-1.96,7.58, +-1.49,6.79, +0.35,5.87, +0.02,5.84, +-0.15,5.85, +0.52,6.32, +1.86,5.49, +2.93,5.49, +5.29,2.22, +5.66,2.06, +6.12,2.23, +6.58,2.39, +6.61,2.26, +6.72,1.87, +6.85,1.38, +6.62,2.25, +6.61,2.26, +6.62,2.23, +6.23,3.08, +6.06,3.5, +6.06,3.5, +6.06,3.5, +5.5,3.17, +5.48,2.79, +6.26,2.68, +6.33,3.17, +6.69,3.86, +6.62,3.82, +6.18,3.17, +7.81,1.53, +7.85,1.38, +8.81,0.31, +9.81,0, +9.43,0, +9.9,-0.22, +9.72,-0.94, +8.74,-1.8, +8.4,-3.06, +8.4,-3.06, +8.85,-3.48, +8.33,-4.81, +7.49,-4.33, +7.22,-4.17, +6.87,-3.74, +7.39,-2.69, +7.49,-2.73, +7.85,-2.61, +8.48,-2.28, +8.55,-2.49, +8.4,-3.06, +8.21,-3.46, +7.91,-4.57, +9.32,-3.39, +9.32,-3.39, +8.93,-4.17, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +7.74,-4.47, +8.47,-4.89, +8.59,-4.96, +7.76,-6.14, +8.43,-5.86, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +10.06,-5.81, +10.44,-6.03, +10.44,-6.03, +10.23,-5.33, +11.71,-1.51, +12.03,2.12, +12.85,1.94, +13,0.25, +13.43,1.71, +14,0, +11.74,-0.26, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +6.47,-1.53, +2.97,-0.52, +9.2,0, +17.03,-0.29, +20.83,0, +19.97,-0.55, +17.9,-3.16, +15.89,-5.78, +14.28,-8.25, +11.65,-6.73, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.52,2.01, +-9.04,3.29, +-7.89,1.59, +-6.47,1.14, +-5.95,1.42, +-5.28,1.34, +-4.82,1.05, +-1.47,0.18, +3.71,-1.1, +3.92,-1.09, +4.65,-0.7, +5.43,-0.42, +6.55,-0.23, +0.41,6.31, +1.47,7.2, +1.45,7.08, +1.35,6.86, +1.51,6.81, +2.23,6.62, +2.88,6.35, +3.49,6.04, +4,5.71, +4.05,5.67, +3.83,5.83, +4.07,5.66, +5.2,5.57, +5.56,5.82, +4.44,5.57, +6.61,5.46, +5.69,7.34, +3.79,10.41, +3.94,8.96, +3.91,9.07, +3.91,9.07, +3.39,9.32, +3.87,8.62, +4.22,8.46, +5.35,8.12, +5.21,8.21, +4.43,8.83, +4.06,9, +3.89,8.28, +3.2,8.34, +1.56,9.01, +-0.8,8.39, +-2.95,8.1, +-2.93,8.06, +-2.2,8.63, +-1.85,8.62, +-1.55,8.81, +-1.55,8.81, +-1.55,8.81, +-1.55,8.81, +-1.55,8.81, +-1.55,8.81, +-1.55,8.81, +-1.55,8.81, +-2.1,6.91, +-3.36,6.36, +-3.69,6.74, +-4.19,7.26, +-3.99,7.26, +-3.71,7.16, +-3,8.25, +-2.26,8.17, +-1.3,7.37, +-1.07,6.09, +-0.12,6.02, +0,6.35, +0.2,6.98, +3.58,3.91, +4.97,2.87, +5.22,3.01, +5.55,2.3, +6.39,2.33, +6.58,2.39, +6.58,2.39, +6.61,2.26, +6.67,2.06, +6.76,1.72, +6.89,1.22, +6.27,2.58, +6.06,3.5, +5.5,3.17, +5.43,3.13, +5.22,3.01, +5.22,3.01, +5.22,3.01, +5.92,3.42, +5.89,3.75, +5.65,4.25, +5.94,3.32, +7.85,1.38, +7.85,1.38, +8.83,1.29, +9.22,0, +9.05,0, +9.46,-0.44, +9.81,-1.15, +8.59,-1.52, +7.6,-2.77, +8.4,-3.06, +8.58,-2.39, +8.47,-3.71, +7.74,-4.47, +6.69,-3.86, +7.16,-3.16, +7.95,-2.89, +7.49,-2.73, +7.81,-1.53, +8.17,-1.44, +8.61,-1.74, +8.13,-2.96, +8.4,-3.06, +7.84,-4.27, +9.21,-3.35, +9.32,-3.39, +9.34,-3.95, +7.82,-4.31, +7.74,-4.47, +7.98,-4.61, +8.25,-4.76, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +7.88,-5.97, +9.04,-6.33, +9.46,-5.74, +9.6,-5.54, +9.6,-5.54, +9.81,-5.66, +9.96,-5.75, +9.74,-5.62, +11.06,-2.76, +12.56,1.13, +12.85,1.98, +13.42,2.37, +13.78,2.43, +14,0, +13.5,-0.95, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.78,-0.49, +7.89,-1.39, +19.26,-0.39, +20.35,0, +20.99,0, +20.02,0, +17.35,-3.06, +15.51,-6.34, +14.22,-8.21, +8.08,-4.67, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.02,1.1, +-9.81,3.57, +-9.09,3.15, +-6.92,2.37, +-5.92,2.16, +-5.26,1.91, +-4.75,1.73, +-3.25,0.7, +3.91,-0.47, +4.17,-0.08, +4.77,0.42, +5.6,0.74, +6.68,1.18, +2.7,4.97, +0.59,7.36, +0.41,6.96, +0.41,7.18, +1.04,6.91, +1.22,6.89, +1.48,6.82, +2.38,6.55, +3.01,6.29, +3.04,6.39, +2.96,6.62, +2.93,6.73, +4.54,7.17, +4.95,7.41, +3.89,7.08, +3.73,8.53, +2.72,10.7, +2.37,10.64, +1.91,9.71, +1.81,10.27, +1.82,10.34, +2.2,10.48, +2.01,10.14, +2.68,9.51, +2.65,9.51, +2.34,9.52, +2.56,9.54, +2.63,8.85, +2.5,9.36, +2.65,9.42, +4.55,8.74, +-0.66,9.47, +-3.5,9.63, +-3.54,9.73, +-3.15,9.78, +-1.77,10.05, +-1.7,9.63, +-1.67,9.44, +-1.66,9.4, +-1.64,9.28, +-1.67,9.44, +-1.67,9.44, +-1.72,9.76, +-1.63,9.23, +-2.45,7.21, +-3.13,6.23, +-2.84,6.1, +-3.32,6.02, +-2.99,7.06, +-3.1,7.93, +-3.1,7.55, +-1.97,8.07, +-2.39,7.87, +-1.6,7.39, +-1.22,6.89, +-0.17,6.5, +0.52,6.27, +2.16,5.07, +5.22,3.01, +5.22,3.01, +5.48,3.02, +6.11,2.63, +6.58,2.39, +6.58,2.39, +6.58,2.39, +6.73,1.8, +6.96,1.36, +6.84,1.41, +5.82,2.57, +5.69,3.28, +5.34,3.08, +5.22,3.01, +5.22,3.01, +5.22,3.01, +5.22,3.01, +5.43,3.39, +5.25,4.41, +4.86,4.08, +4.62,3.87, +7.67,2.05, +7.85,1.38, +8.27,1.46, +9.05,0.19, +8.94,0, +9.17,-1.62, +9.4,-0.96, +8.07,-1.62, +7.49,-2.73, +7.9,-2.87, +8.22,-2.5, +7.85,-2.17, +7.15,-3.95, +8.64,-3.7, +8.14,-2.96, +7.59,-2.76, +8.19,-2.77, +7.99,-1.41, +7.47,-1.32, +7.61,-1.74, +7.49,-2.73, +7.9,-2.87, +8.21,-3.46, +8.75,-3.46, +9.32,-3.39, +8.92,-3.25, +8.03,-3.87, +7.89,-4.16, +8.16,-4.71, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.57,-5.23, +8.41,-6.56, +9.44,-5.77, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +10.5,-3.8, +12.81,1.26, +13.54,2.39, +13.78,2.43, +13.92,2.45, +14.61,1.28, +12.25,-0.32, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +15.01,-1.49, +20.02,0, +20.99,0, +21.32,0, +20.02,0, +17.16,-2.55, +15.37,-5.59, +13.59,-7.85, +11.4,-6.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.74,1, +-9.99,3.63, +-10.01,4.03, +-7.8,3.27, +-6.46,3.21, +-5.5,3.17, +-4.83,2.94, +-4.5,1.57, +3.21,0.51, +4.53,1.46, +5.05,1.86, +5.7,2.49, +6.77,2.83, +3.14,4.81, +-1.27,7.21, +-1.25,7.76, +-0.69,7.91, +0,7.58, +0.41,7.18, +1.22,6.89, +1.72,7.13, +1.89,7.61, +1.96,7.69, +2.05,7.67, +2.59,8.43, +2.91,8.73, +3.41,8.24, +3.25,8.54, +1.72,11.48, +1.2,11.34, +0.64,11.35, +0,10.76, +0.27,11.06, +0.64,11.46, +0,11.69, +0.27,11.47, +0.43,11.47, +-0.75,12.09, +0,12.05, +0.6,11.42, +0.38,10.14, +0.86,9.84, +1.08,9.82, +1.15,9.81, +-0.1,10.2, +-3.15,10.96, +-4.01,11.02, +-3.98,10.93, +-1.95,11.05, +-1.86,10.53, +-1.85,10.48, +-1.81,10.26, +-1.79,10.15, +-1.77,10.05, +-1.72,9.76, +-1.72,9.76, +-1.53,9.78, +-2.99,7.44, +-3.28,6.16, +-2.27,6.23, +-2.41,6.11, +-2.39,6.58, +-2.65,7.29, +-2.68,7.93, +-1.49,8.45, +-2.22,8.63, +-1.27,8.51, +-0.32,7.09, +0.27,6.97, +0.52,6.81, +1.19,6.75, +4.31,4.11, +4.96,3.38, +5.46,3.15, +5.87,3.39, +6.08,3.16, +6.58,2.39, +6.98,2.54, +6.58,2.39, +7.52,2, +7.32,1.29, +7.42,1.31, +7.44,1.31, +5.96,2.21, +5.59,2.61, +5.22,3.01, +5.22,3.01, +5.03,2.9, +4.92,3.44, +4.8,4.03, +4.62,3.87, +4.62,3.87, +8.19,2.77, +8.55,1.68, +8.81,1.55, +8.73,0.86, +8.92,-0.28, +8.81,-1.55, +8.9,-0.44, +8.23,-1.61, +7.49,-2.73, +7.75,-2.82, +8.27,-2.3, +7.88,-1.79, +7.64,-2.78, +8.81,-3.21, +8.4,-3.06, +8.26,-2.53, +7.85,-1.38, +7.73,-1.36, +6.89,-1.22, +7.51,-2.24, +7.49,-2.73, +7.49,-2.73, +8.29,-3.02, +8.06,-2.93, +8.4,-3.06, +8.4,-3.06, +8.4,-3.06, +8.18,-3.53, +8.1,-4, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.81,-5.64, +9.34,-5.39, +9.6,-5.54, +9.6,-5.54, +9.6,-5.54, +9.71,-5.29, +10.32,-3.98, +10.34,-3.51, +12.8,0.08, +13.78,2.43, +14.02,2.47, +14.74,2.6, +14.77,2.31, +11.16,0.04, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.96,-0.35, +14.12,-2.49, +20.36,-1.74, +20.58,0, +20.99,0, +20.75,0, +18.81,0, +16.32,-2.03, +14.6,-4.36, +12.82,-6.65, +10.61,-6.12, +4.29,-2.48, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.94,2.16, +-10.04,3.74, +-9.78,5.18, +-7.77,4.48, +-6.49,4.23, +-5.45,4.37, +-4.34,4.15, +-2.58,4.28, +1.86,4.06, +3.47,3.56, +4.65,3.75, +5.34,3.97, +6.55,4.49, +1.05,6.67, +-1.98,7.69, +-1.38,7.85, +-1.38,7.85, +-0.92,7.89, +0,7.97, +0.69,7.48, +0.9,7.78, +1.21,8.14, +1.47,8.33, +0.92,8.21, +0.84,8.31, +1.08,9.46, +1.15,10.16, +0.6,11.44, +0,12.66, +0,12.7, +-0.57,12.61, +-1.62,12.47, +-1.92,12.1, +-2.15,12.19, +-1.87,12.31, +-1.7,12.34, +-2.3,13.04, +-2.35,13.3, +-2.05,13.06, +-0.5,11.4, +0,10.3, +0,10.3, +0,9.91, +0,9.91, +-0.81,10.77, +-2.19,12.4, +-2.77,12.26, +-2.18,12.35, +-2.16,12.25, +-2.12,12.01, +-2.07,11.75, +-2.04,11.55, +-2.03,11.51, +-1.97,11.19, +-1.92,10.91, +-1.92,10.91, +-0.45,10.48, +-3.08,7.44, +-2.88,7.42, +-2.54,6.97, +-2.84,6.61, +-2.52,6.92, +-2.25,6.78, +-1.72,7.98, +-1.51,8.57, +-1.55,8.81, +-1.02,8.75, +-0.63,8.35, +0.81,7.09, +1.22,6.89, +1.22,6.89, +1.84,6.71, +4.62,3.87, +4.82,3.59, +5.94,3.43, +6.06,3.5, +6.65,3.38, +7.49,2.73, +7.03,2.56, +7.22,2.63, +7.73,1.83, +7.85,1.38, +7.85,1.38, +5.22,3.01, +5.22,3.01, +5.22,3.01, +5.1,2.94, +4.66,2.69, +5.08,3.2, +4.52,3.8, +4.62,3.87, +4.62,3.87, +7.46,3.26, +8.85,2.46, +9.66,1.7, +8.94,1.38, +8.92,-0.26, +8.91,-1.57, +9.39,-0.41, +9.05,-1.89, +8.06,-2.93, +8.06,-2.93, +8.6,-2.31, +8.57,-1.51, +8.04,-1.59, +8.06,-2.93, +8.55,-2.91, +9.28,-1.64, +8.65,-1.52, +8.09,-1.43, +7.64,-1.68, +6.73,-2.45, +7.15,-2.6, +7.49,-2.73, +7.49,-2.73, +8.06,-2.93, +8.3,-3.02, +8.1,-2.95, +8.4,-3.06, +8.32,-3.23, +8.86,-3.22, +9.11,-3.84, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.75,-5.05, +8.9,-4.29, +9.07,-3.91, +9.19,-3.65, +9.35,-3.63, +9.8,-3.57, +9.68,-3.52, +9.47,-3.45, +11.32,-1.62, +13.98,2.05, +14.74,2.6, +14.74,2.6, +15.06,2.66, +4,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +14.19,-2.5, +20.49,-2.56, +20.83,0, +21.21,0, +20.85,0, +19.05,0, +15.67,-1.67, +13.31,-3.25, +9.32,-4.29, +3.11,-1.8, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.4,1.96, +-10.58,3.9, +-9.72,5.36, +-8.98,5.18, +-6.68,5.61, +-5.52,5.12, +-3.82,5.57, +-1.72,5.64, +0.5,4.99, +1.77,5.5, +3.35,5.61, +4.27,5.35, +5.55,5.83, +5.5,7.06, +-2.73,7.49, +-2.73,7.49, +-2.09,7.99, +-1.62,7.93, +-1.04,7.88, +-0.48,8.04, +-0.39,8.54, +0,8.94, +0,8.94, +0,8.94, +0,8.82, +0,9.37, +0,11.86, +-0.35,13.41, +-0.56,13.84, +-1.76,13.23, +-2.41,13.65, +-3.64,14.02, +-4.22,13.97, +-3.19,13.25, +-2.83,12.12, +-2.9,12.1, +-2.18,12.35, +-2.75,14.1, +-2.32,13.19, +-2.21,12.55, +-1.55,12.78, +-1.58,12.59, +-1.13,12.44, +-0.25,11.81, +-1.31,12.48, +-2.36,13.37, +-2.39,13.54, +-2.36,13.4, +-2.35,13.3, +-2.32,13.19, +-2.36,13.36, +-2.29,12.96, +-2.22,12.59, +-2.19,12.4, +-1.49,12.06, +-0.24,11.54, +-0.91,10.1, +-2.53,7.54, +-2.77,7.62, +-2.73,7.49, +-2.54,6.97, +-2.82,7.75, +-1.35,7.4, +-1.41,7.97, +-1.43,8.12, +-0.69,8.77, +0,9.31, +0.8,9.03, +1.11,8.2, +1.51,6.81, +1.75,6.58, +1.2,6.8, +3.98,4.83, +4.83,4.05, +5.75,3.94, +6.17,3.56, +6.76,3.9, +7.1,3.57, +7.42,2.88, +7.49,2.73, +7.53,2.58, +7.8,1.55, +7.85,1.38, +5.65,2.87, +5.22,3.01, +4.52,2.61, +4.59,2.65, +5.13,2.96, +5.22,3.01, +4.59,3.71, +4.62,3.87, +4.62,3.87, +8.23,2.68, +9.06,3.09, +9.76,1.72, +9.17,1.62, +8.91,0.35, +9.7,-0.57, +9.67,-1.21, +9.71,-1.91, +8.71,-3.17, +8.4,-3.06, +9.44,-2.17, +9.49,-1.67, +8.81,-1.55, +8.67,-2.05, +8.25,-3, +9.18,-2.29, +9.13,-1.61, +8.33,-1.47, +6.75,-2.27, +6.47,-2.36, +6.84,-2.49, +7.83,-2.85, +8.3,-3.02, +8.86,-3.22, +8.66,-3.15, +8.4,-3.06, +8.4,-3.06, +8.53,-3.11, +8.91,-3.24, +9.32,-3.39, +8.69,-4.73, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +9.07,-3.91, +8.95,-4.17, +9.15,-3.74, +9.07,-3.91, +9.21,-3.61, +9.32,-3.39, +9.32,-3.39, +10.55,-2.23, +14.23,1.79, +14.9,2.63, +15.56,2.74, +15.7,2.77, +4.83,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.06,-0.89, +19.72,-3.48, +20.48,-3.15, +20.92,-0.81, +20.99,0, +20.99,0, +19.05,0, +14.55,0, +4.96,-0.64, +0,0, +2.72,-1.57, +2.67,-1.54, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.19,0.8, +-10.25,3.73, +-10.44,3.8, +-9.82,5.24, +-9.65,5.57, +-7.85,5.87, +-5.97,5.86, +-4.31,6.54, +-2.43,6.53, +-0.45,5.55, +0.75,6.39, +2.21,6.59, +3.86,6.69, +4.45,7.1, +4.07,7.93, +-3.38,7.72, +-3.3,8.05, +-3.06,8.4, +-2.72,8.62, +-2.46,8.56, +-1.79,8.74, +-1.55,8.81, +-1.61,9.13, +-1.49,9.54, +-1.48,9.78, +-1.23,9.92, +-1.94,10.98, +-2.29,12.96, +-2.31,14.47, +-2.54,14.38, +-2.54,14.38, +-2.93,14.87, +-5.08,14.63, +-3.24,13.07, +-3.22,12.67, +-4.35,12.77, +-4.83,13.28, +-3.96,14.38, +-3,15.13, +-2.72,15.42, +-2.73,15.49, +-2.75,15.59, +-2.66,15.06, +-2.49,14.1, +-1.98,14.36, +-1.24,13.73, +-2.54,14.42, +-2.54,14.42, +-2.6,14.74, +-2.54,14.42, +-2.54,14.42, +-2.51,14.26, +-2.27,12.87, +-2.11,11.95, +-1.37,12.26, +-0.24,13, +0,12.7, +-0.44,11.26, +-1.99,8.19, +-3.02,8.3, +-3.01,8.27, +-2.85,7.83, +-0.79,7.16, +1.46,7, +-1.55,8.81, +-1.36,8.71, +-0.25,9.61, +1.21,9.08, +1.74,8.87, +1.5,8.53, +1.66,6.77, +2.1,5.76, +2.77,5.36, +5.02,4.44, +4.95,4.15, +5.36,4.5, +5.91,4.44, +6.81,4.11, +6.9,3.98, +7.07,3.63, +7.42,2.87, +7.49,2.73, +7.59,2.34, +7.81,1.53, +6.63,2.65, +5.22,3.01, +5.68,2.72, +6.26,2.41, +5.1,2.94, +5.22,3.01, +5.61,2.84, +6.17,2.74, +5.96,2.47, +6.97,0.35, +7.77,1.13, +9.51,2.65, +9.22,1.63, +8.83,1.29, +9.48,0, +9.64,-1.7, +9.49,-1.67, +9.03,-1.75, +8.69,-1.98, +9.3,-2.19, +9.89,-1.74, +9.49,-1.67, +8.81,-1.55, +8.54,-2.56, +9.43,-3.06, +10.78,-1.9, +8.88,-2.02, +7.19,-2.43, +6.11,-2.73, +6.86,-3.56, +8.4,-3.06, +8.4,-3.06, +9.21,-3.35, +9.06,-3.3, +8.4,-3.06, +8.4,-3.06, +8.63,-3.14, +9.32,-3.39, +9.32,-3.39, +8.83,-4.43, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +9.07,-3.91, +9.32,-3.39, +9.32,-3.39, +9.96,-2.26, +13.98,1.19, +15.7,2.77, +15.7,2.77, +15.7,2.77, +11.3,1.46, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +13.57,-2.39, +19.44,-3.43, +19.96,-3.52, +20.57,-2.39, +20.83,0, +20.02,0, +17.01,0, +13.02,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.65,2.78, +-10.41,3.79, +-10.58,3.9, +-10.26,5.48, +-9.91,5.72, +-8.4,5.93, +-6.01,6.89, +-4.9,6.77, +-2.68,6.92, +-1.31,6.64, +0,7.45, +1.7,7.64, +2.72,7.89, +3.42,8.77, +4.69,9.51, +0,9.5, +-2.47,10.16, +-2.42,10.03, +-3.06,9.67, +-3.39,9.32, +-2.97,9.43, +-2.92,9.71, +-2.24,9.93, +-1.77,10.05, +-1.81,10.27, +-1.97,11.15, +-2.02,11.46, +-2.49,14.1, +-2.81,15.94, +-2.72,15.42, +-2.4,15.19, +-2.54,14.38, +-2.43,13.78, +-3.62,13.8, +-4.26,14.42, +-4.61,14.92, +-3.4,15.75, +-2.85,16.18, +-2.89,16.38, +-2.94,16.68, +-3,17.04, +-2.97,16.85, +-2.86,16.23, +-2.82,16.02, +-2.31,15.74, +-1.19,15.51, +-2.75,15.59, +-2.77,15.7, +-2.65,15.02, +-2.39,13.57, +-2.21,12.51, +-2.08,11.78, +-2.05,11.61, +-2.04,11.55, +-1.92,10.91, +-1.92,10.91, +-1.89,10.72, +-0.85,10.47, +-2.3,9.76, +-3,9.09, +-2.72,8.49, +-3.1,8.5, +4.35,5.47, +2.69,5.98, +-1.09,8.63, +-0.26,9.24, +0,9.7, +1.47,9.35, +2.08,8.83, +2.71,8.39, +1.55,7.8, +1.88,6.51, +2.66,5.39, +4.06,5.41, +4.47,4.47, +5.36,4.5, +5.77,4.85, +6.11,5.12, +6.8,4.13, +6.9,3.98, +6.81,4.25, +7.23,3.1, +7.49,2.73, +7.54,2.53, +6.8,1.81, +6.86,1.99, +6.49,2.49, +6.58,2.39, +6.52,2.52, +5.44,2.91, +6.75,2.6, +6.68,2.43, +7.37,1.3, +7.68,0.52, +7.79,1.02, +8.4,3.06, +8.43,2.54, +8.08,1.27, +8.35,-0.44, +9.28,-1.64, +8.93,-1.57, +8.81,-1.55, +8.81,-1.55, +8.93,-1.57, +9.13,-1.61, +9.17,-1.62, +9.05,-1.59, +9.51,-1.68, +12.44,-2.19, +11.32,-2, +9.39,-3.11, +8.1,-2.95, +7.06,-3.08, +6.9,-3.98, +8.16,-3.59, +8.63,-3.14, +9.32,-3.39, +8.71,-3.17, +8.4,-3.06, +8.4,-3.06, +8.91,-3.24, +9.32,-3.39, +9.32,-3.39, +9.36,-3.61, +8.83,-4.82, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +9.28,-3.19, +9.57,-2.44, +9.76,-1.72, +9.76,-1.72, +12.9,-0.56, +15.83,2.79, +16.34,2.88, +16.42,2.89, +15.6,2.46, +7.15,1, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +14.71,-3.45, +18.87,-3.33, +18.6,-3.28, +19.4,-3.42, +19.49,-1.14, +18.16,0, +5.86,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.3,2.29, +-10.55,3.84, +-10.94,4.22, +-10.02,5.53, +-9.83,5.67, +-9.6,5.54, +-7.38,7.13, +-4.87,7.61, +-3.53,7.35, +-2.18,7.51, +-0.86,7.89, +0.46,8.04, +1.14,8.72, +2.23,9.8, +2.55,10.63, +-3.91,10.75, +-3.52,10.48, +-2.34,10.8, +-2.16,10.85, +-2.4,10.61, +-3.17,10.58, +-2.62,10.72, +-3.79,10.41, +-3.79,10.41, +-3.58,10.47, +-2.99,10.63, +-2.71,11.37, +-2.43,13.78, +-2.79,15.83, +-2.94,14.98, +-3.53,15.05, +-5.4,14.85, +-5.6,15.38, +-5.42,15.9, +-5.44,16.18, +-4.65,16.73, +-3.39,17.34, +-3.13,17.75, +-3.15,17.89, +-3.23,18.34, +-3.28,18.6, +-3.21,18.23, +-3.15,17.87, +-2.48,16.89, +-0.29,16.89, +-1.13,16.84, +-2.86,16.24, +-2.63,14.89, +-2.35,13.34, +-2.22,12.57, +-2.12,12.02, +-2.1,11.89, +-2.09,11.87, +-2.02,11.43, +-1.93,10.94, +-1.92,10.91, +-1.91,10.85, +-0.62,9.96, +0.98,9.71, +-2.43,11.33, +-2.98,10.43, +0.47,8.17, +4.71,5.61, +3.73,4.96, +2.37,5.31, +0.19,9.36, +0,9.91, +1.01,9.02, +1.72,8.76, +2.87,8.45, +3.06,8.4, +2.09,8.1, +2.97,6.18, +3.18,5.5, +3.79,5.83, +4.74,5.11, +5.77,5.07, +6.11,5.12, +6.2,4.98, +5.69,5.86, +6.15,5.63, +6.48,4.17, +7.49,2.73, +7.49,2.73, +6.8,2.1, +7.44,1.31, +7.05,2.28, +6.71,2.44, +6.58,2.39, +6.53,2.5, +7.1,2.58, +7.18,2.62, +7.85,1.38, +7.88,0.99, +8.35,1.8, +8.22,2.99, +7.95,2.89, +7.61,1.74, +7.86,0, +8.17,-1.44, +8.26,-1.46, +8.7,-1.53, +8.65,-1.52, +8.67,-1.53, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +10.55,-1.86, +11.39,-2.01, +12.11,-2.14, +10.68,-3.89, +10.17,-3.7, +8.25,-4.76, +7.74,-4.47, +7.74,-4.47, +8.63,-3.33, +9.32,-3.39, +8.55,-3.11, +8.4,-3.06, +8.4,-3.06, +9.01,-3.28, +9.32,-3.39, +9.32,-3.39, +9.47,-3.45, +8.83,-4.7, +9.31,-5.37, +9.26,-5.35, +8.7,-5.02, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +9.6,-2.18, +9.76,-1.72, +9.76,-1.72, +9.95,-1.76, +10.89,-1.68, +15.83,2.79, +16.65,2.94, +16.8,2.96, +16.85,2.97, +13.27,1.68, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +16.19,-5.89, +17.01,-3, +16.7,-2.95, +18.44,-3.25, +16.95,-0.39, +13.61,0, +8.49,0.35, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.41,2.7, +-10.8,3.93, +-10.88,4.5, +-9.97,5.66, +-10.04,5.8, +-9.63,5.56, +-8.53,6.72, +-5.82,7.7, +-4.42,7.65, +-2.77,7.62, +-1.7,8.65, +-0.35,8.91, +0.25,9.34, +1.6,10.16, +-1.87,10.68, +-3.88,10.67, +-3.94,10.82, +-3.97,10.92, +-3.23,11.42, +-2.04,11.55, +-2.04,11.55, +-2,11.32, +-2.06,11.66, +-2.17,12.29, +-2.23,12.67, +-2.43,12.56, +-2.34,13.25, +-2.49,14.1, +-2.68,15.22, +-2.81,15.94, +-2.94,16.65, +-2.9,16.44, +-2.98,16.91, +-4.09,17.35, +-3.16,17.9, +-3.22,18.28, +-3.28,18.62, +-3.44,19.51, +-3.48,19.72, +-3.48,19.72, +-3.41,19.33, +-1.68,19.51, +0,19.19, +0,18.94, +0,18.44, +-1.99,16.29, +-2.51,14.21, +-2.43,13.78, +-2.33,13.24, +-1.76,12.89, +-1.86,12.86, +-1.91,12.29, +-1.73,12.03, +-1.52,11.94, +-1.97,11.19, +-1.93,10.93, +-1.97,11.15, +0.51,10.65, +3.66,10.07, +3.92,9.59, +-2.17,12.28, +3.96,7.85, +5.18,6.17, +3.95,4.8, +2.63,4.56, +1.15,5.57, +0,10.25, +1.17,8.84, +2.13,8.22, +3.06,8.4, +3.04,8.81, +3.06,8.4, +3.36,6.63, +3.6,5.99, +3.45,6.79, +3.2,6.14, +4.53,6.09, +5.83,5.56, +5.29,6.43, +4.47,7.74, +5.32,7.15, +6.2,4.99, +7.42,2.87, +7.49,2.73, +7.03,2.56, +7.6,1.5, +7.32,1.5, +7.38,2.68, +7.39,2.69, +6.71,2.44, +7.18,2.62, +7.57,2.43, +7.85,1.38, +7.89,0.92, +8.64,1.7, +8.55,3.11, +7.59,2.76, +7.27,2.14, +7.27,0.61, +7.89,-0.92, +7.85,-1.38, +8.11,-1.66, +8.55,-1.51, +8.69,-1.53, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +9.83,-1.73, +11.44,-2.02, +11.05,-1.95, +12.09,-4.4, +10.53,-3.83, +8.81,-4.81, +8.73,-5.04, +8.69,-5.02, +8.59,-4.96, +8.87,-4.01, +8.54,-3.52, +8.4,-3.06, +8.4,-3.06, +9.01,-3.28, +9.32,-3.39, +9.32,-3.39, +9.47,-3.45, +9.04,-4.89, +9.22,-5.32, +9.02,-5.21, +9.43,-5.44, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.57,-4.73, +9.49,-2.54, +9.76,-1.72, +9.76,-1.72, +10.58,-1.87, +11.05,-1.95, +14.91,1.9, +17.8,3.14, +17.8,3.14, +17.8,3.14, +14.63,2.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.87,-0.68, +11.58,-4.21, +7.69,-1.36, +12.91,-2.28, +15.94,-2.26, +11.99,0, +1.63,0, +1.7,0.3, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.3,0.47, +-8.1,2.95, +-10.43,3.8, +-10.49,3.82, +-10.26,4.62, +-9.49,5.46, +-9.57,5.52, +-9.12,5.96, +-7.57,6.73, +-4.54,7.86, +-3.37,8.26, +-2.15,8.64, +-1.04,8.85, +-0.55,9.4, +-3.92,9.85, +-4.08,10.28, +-3.79,10.41, +-3.79,10.41, +-3.84,10.54, +-4.12,11.33, +-3.92,11.38, +-3.19,11.91, +-2.92,12.65, +-2.99,12.63, +-2.32,13.15, +-2.45,13.88, +-2.54,14.42, +-2.62,14.88, +-2.85,16.14, +-2.99,16.94, +-3.08,17.45, +-3.16,17.91, +-3.2,18.16, +-3.21,18.21, +-3.25,18.44, +-3.31,18.76, +-3.31,18.76, +-3.31,18.76, +-3.31,18.76, +-3.4,19.31, +-3.46,19.61, +-2.17,19.83, +0,20.02, +0,19.48, +0,19.05, +-2.23,15.38, +-2.6,14.74, +-2.57,14.6, +-2.44,13.87, +-1.27,13.23, +-0.28,13.01, +-0.26,13.02, +-0.48,12.9, +-0.24,12.15, +-0.39,12.03, +-0.69,11.84, +-1.1,11, +-1.19,11.39, +-0.2,10.96, +3.97,10.91, +4.91,10.42, +4.98,10.31, +6.54,8.19, +5.91,7.05, +4.7,5.6, +3.42,5.06, +2.24,5.11, +1.51,10.19, +1.72,8.43, +2.8,7.69, +3.02,8.3, +3.17,8.71, +3.32,9.11, +2.9,6.9, +2.83,5.66, +2.74,7.13, +2.87,6.49, +3.99,6.9, +4.38,7.28, +4.68,7.42, +4.47,7.74, +5.61,6.95, +5.7,6.22, +7.09,3.86, +7.07,3.78, +7.34,3.04, +7.69,1.96, +7.54,2.11, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.36,2.68, +7.64,2.15, +7.85,1.38, +7.85,1.38, +8.55,1.45, +9.21,3.35, +7.79,2.84, +6.62,2.23, +6.99,1.08, +7.88,-0.99, +7.62,-2.22, +8.42,-2.15, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +9.32,-1.64, +11.84,-2.6, +10.98,-4, +12.37,-4.5, +10.93,-6, +9.6,-5.54, +9.6,-5.54, +9.17,-4.63, +9.11,-3.84, +9.32,-3.39, +9.32,-3.39, +8.92,-3.25, +9.32,-3.39, +9.32,-3.39, +9.86,-3.59, +10.14,-3.69, +10.27,-3.74, +9.6,-5.54, +9.48,-5.48, +9.6,-5.54, +8.59,-4.96, +8.59,-4.96, +8.59,-4.96, +8.71,-4.7, +9.23,-3.56, +9.64,-2.2, +10.48,-1.85, +10.91,-1.92, +10.91,-1.92, +13.64,0.84, +16.78,1.88, +16.62,1.25, +18.02,3.18, +17.04,2.72, +0,0, +0,0, +0,0, +1.97,-0.35, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +14.02,-2.47, +8.43,-0.89, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.21,1.89, +-10.41,3.79, +-10.41,3.79, +-10.37,3.77, +-9.83,4.22, +-8.86,5.11, +-8.98,5.18, +-8.11,6.15, +-7.59,6.37, +-6.29,7.11, +-3.69,8.58, +-2.87,9.31, +-1.8,9.01, +-4.41,8.73, +-4.87,9.54, +-3.79,10.41, +-3.79,10.41, +-3.79,10.41, +-3.79,10.41, +-3.97,10.92, +-4.12,11.33, +-4.23,11.63, +-4.45,12.24, +-4.45,12.24, +-3.93,13.05, +-2.99,13.86, +-2.6,14.74, +-2.69,15.27, +-2.86,16.23, +-3.11,17.64, +-3.14,17.8, +-3.21,18.23, +-3.31,18.76, +-3.27,18.55, +-3.25,18.44, +-3.31,18.76, +-3.31,18.76, +-3.31,18.76, +-3.31,18.76, +-1.47,18.92, +-0.41,19.01, +0,19.19, +0,19.16, +-1.04,17.79, +-2.68,15.21, +-2.6,14.76, +-2.59,14.72, +-2.56,14.49, +-2.45,13.89, +-1.83,13.76, +-0.07,13.16, +0,13.02, +0,12.8, +0,12.56, +0,12.27, +0,11.79, +-0.13,11.08, +-0.06,11.12, +1.15,11.15, +4.28,11.75, +4.89,12, +6.35,11, +6.63,9.58, +6.46,8.2, +5.54,6.6, +4.27,5.81, +3.27,5.72, +2.44,6.89, +2.21,9.57, +2.64,7.26, +2.95,8.1, +3.06,8.4, +3.31,9.09, +3.02,8.37, +2.91,6.57, +2.68,7.38, +3.22,7.11, +4.2,7.28, +4.4,7.62, +4.47,7.74, +4.79,7.64, +5.75,6.85, +6.07,6.36, +6.7,5.62, +6.9,4.55, +7.72,2.81, +7.92,2.37, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.53,2.56, +7.85,1.38, +7.85,1.38, +8.29,1.02, +8.92,3.25, +7.81,3.17, +6.92,2.52, +7.21,0.69, +8.09,-1.11, +8.58,-2.41, +8.69,-1.98, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +8.81,-1.55, +8.85,-1.04, +8.81,-1.55, +9.68,-2.2, +13.67,-4.08, +13.41,-3.84, +13.58,-4.6, +11.19,-5.56, +10.33,-5.97, +10.28,-4.83, +9.68,-3.52, +9.8,-3.57, +9.32,-3.39, +9.32,-3.39, +9.44,-3.44, +9.68,-3.52, +10.26,-3.73, +10.41,-3.79, +10.41,-3.79, +10.41,-3.79, +10.05,-4.57, +9.6,-5.54, +9.47,-5.47, +8.84,-5.1, +8.59,-4.96, +8.59,-4.96, +8.9,-4.29, +9.32,-3.39, +10.15,-1.79, +10.91,-1.92, +10.91,-1.92, +12.78,-4.54, +14.13,-4.25, +14.47,-2.55, +15.54,-1.42, +18.76,3.31, +18.14,3.2, +3.5,0, +0,0, +2.76,-0.49, +11.81,-2.08, +2.3,-0.41, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.28,-0.75, +7.74,-1.36, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.48,1.24, +-10.42,3.77, +-10.39,3.78, +-10.05,3.66, +-9.24,3.73, +-8.59,4.96, +-8.59,4.96, +-7.91,5.88, +-7.59,6.37, +-6.64,7.35, +-6,7.81, +-4.23,8.82, +-4.38,8.34, +-4.77,8.27, +-4.17,8.95, +-3.39,9.32, +-3.54,9.73, +-3.64,10, +-3.79,10.41, +-3.88,10.67, +-4.12,11.33, +-4.23,11.63, +-4.45,12.24, +-4.64,12.75, +-4.79,13.15, +-4.79,13.15, +-3.72,14.44, +-2.73,15.49, +-2.82,16.02, +-3,17.04, +-3.12,17.68, +-3.14,17.8, +-3.14,17.8, +-3.2,18.12, +-3.28,18.6, +-3.29,18.66, +-3.27,18.52, +-2.83,18.41, +-0.92,17.93, +-0.35,18.63, +-0.79,18.09, +-1.38,17.37, +-2.37,16.18, +-2.77,15.7, +-2.67,15.14, +-2.6,14.74, +-2.55,14.47, +-2.44,13.82, +-2.39,13.79, +-1.43,13.87, +-0.45,13.26, +0,13.02, +0,12.87, +0,12.76, +0,12.21, +0,11.88, +0,11.66, +0,11.34, +1.48,11.45, +4.65,12.79, +4.78,13.12, +5.51,12.45, +6.29,11.07, +5.92,9.73, +5.67,8.41, +4.89,7.64, +4,6.93, +3.79,6.74, +2.46,8.5, +2.56,8.39, +2.73,7.49, +3.15,8.66, +3.25,8.92, +3.47,9.07, +3.07,7.45, +2.73,7.49, +3.53,7.35, +4.26,7.38, +4.47,7.74, +5.11,7.3, +6.06,7.62, +5.89,7.02, +6.02,6.57, +6.85,5.75, +7.61,4.04, +8.06,2.93, +8.4,3.06, +7.79,2.84, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.58,2.39, +7.85,1.38, +7.85,1.38, +9.25,2.64, +8.81,3.21, +7.85,4.24, +7.23,3, +7.44,1.99, +7.91,-0.69, +8.25,-2.24, +8.77,-2.14, +8.97,-1.58, +8.81,-1.55, +8.93,-1.57, +9.26,-1.16, +8.93,-1.26, +8.99,-1.32, +11.02,-2.27, +13.36,-2.36, +14.1,-2.49, +14.64,-2.96, +14.36,-4.24, +10.44,-6.03, +10.71,-4.18, +10.55,-3.64, +10.58,-3.17, +10.41,-3.79, +10.26,-3.73, +10.41,-3.79, +10.41,-3.79, +10.41,-3.79, +10.41,-3.79, +10.41,-3.79, +10.41,-3.79, +10.41,-3.79, +10.11,-4.45, +9.6,-5.54, +9.02,-5.21, +8.84,-5.1, +8.79,-4.51, +9.32,-3.39, +9.32,-3.39, +10.9,-6.53, +11.72,-5.31, +11.78,-5.55, +13.75,-5.79, +14.63,-4.67, +14.88,-2.62, +15.6,-2.35, +18.03,2.14, +18.76,3.31, +4.45,0.78, +0,0, +4.91,-0.87, +14.53,-2.56, +13.3,-2.35, +5.93,-1.05, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.33,-0.76, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.47,1.6, +-10.49,3.24, +-9.76,3.55, +-9.42,3.43, +-9.24,3.57, +-8.6,4.94, +-8.46,5.15, +-7.75,5.68, +-7.54,6.37, +-6.61,7.14, +-6.1,7.28, +-4.8,7.72, +-4.21,7.88, +-4.29,7.83, +-3.62,8.38, +-3.28,9.01, +-3.39,9.32, +-3.86,9.1, +-4.43,9.2, +-4.45,10.11, +-5.09,10.45, +-4.44,11.33, +-4.45,12.24, +-4.75,13.05, +-4.79,13.15, +-4.91,13.49, +-5.12,14.07, +-3.55,15.15, +-2.86,16.23, +-2.94,16.65, +-3,17.04, +-3.14,17.8, +-3.16,17.92, +-3.24,18.35, +-3.25,18.44, +-3.09,17.52, +-2.76,15.67, +-2.76,15.63, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-2.74,15.53, +-2.61,14.79, +-2.6,14.72, +-2.52,14.3, +-2.43,13.8, +-2.42,13.7, +-1.18,13.05, +-0.25,13.01, +0,12.92, +0,12.63, +0,12.2, +0,12.05, +0,11.99, +0,11.21, +1.15,11.46, +4.67,13.71, +5.08,13.94, +5.06,13.91, +5.75,12.08, +6.13,10.62, +5.6,9.69, +5.58,8.27, +5.43,7.21, +4.41,7.42, +3.83,7.22, +1.99,9.33, +2.41,8.58, +2.95,8.1, +3.4,9.34, +3.22,8.86, +3.02,8.3, +2.98,8.17, +3.73,7.38, +4.36,7.56, +4.47,7.74, +5.93,7.06, +6.37,7.59, +6.37,7.59, +6.49,6.7, +6.85,5.75, +7.58,4.63, +8.31,3.26, +8,2.91, +8.4,3.06, +7.67,2.46, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.62,2.22, +7.85,1.38, +7.85,1.38, +8.7,2.33, +8.1,2.95, +7.96,4, +7.93,4.07, +7.85,2.44, +7.91,-0.14, +8.07,-1.62, +9.4,-1.66, +9.23,-1.63, +8.65,-1.52, +9.4,-1.66, +9.82,-1.08, +9.59,0, +10.7,-0.44, +12.94,-2.28, +13.3,-2.35, +13.15,-2.32, +13.19,-2.32, +13.65,-2.41, +14.38,-4, +11.78,-2.43, +12.74,-3.13, +11.85,-3.11, +10.87,-3.96, +10.64,-3.87, +10.64,-3.87, +11.19,-4.07, +11.12,-4.05, +11.21,-4.08, +11.33,-4.12, +10.61,-3.86, +10.72,-3.9, +10.41,-3.79, +10.05,-4.57, +9.83,-5.04, +9.26,-5.35, +9.73,-3.54, +9.68,-3.52, +10,-3.64, +10.83,-6.14, +12.12,-7, +12.63,-7.29, +14.17,-5.55, +15.18,-4.71, +15.82,-2.79, +15.78,-1.85, +16.88,0.97, +19.51,3.44, +4.82,0.47, +0,0, +4.21,-0.74, +15.38,-2.71, +14,-2.47, +8.9,-1.57, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.7,1.18, +-11.01,1.94, +-10.44,2.74, +-9.9,3.6, +-9.63,3.5, +-9.08,3.94, +-8.59,4.96, +-8.23,5.49, +-7.54,6.33, +-6.83,6.43, +-5.89,6.87, +-5.3,7.21, +-4.52,7.72, +-3.46,8.39, +-3.29,8.53, +-3.15,8.67, +-3.26,8.31, +-4.31,7.82, +-4.53,7.85, +-4.96,8.59, +-5.48,9.48, +-5.84,10.12, +-6.21,10.75, +-5.83,11.6, +-5.14,12.23, +-4.79,13.15, +-4.79,13.15, +-4.98,13.67, +-5.16,14.17, +-3.72,15.89, +-2.94,16.65, +-2.94,16.65, +-2.94,16.65, +-2.99,16.98, +-3.99,16.15, +-3.84,14.98, +-2.64,14.96, +-3.34,15.1, +-3,15.62, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-2.77,15.72, +-2.78,15.75, +-2.72,15.41, +-2.61,14.83, +-2.6,14.74, +-2.59,14.72, +-2.52,14.27, +-2.43,13.78, +-2.39,13.53, +-1.76,13.45, +0,13.19, +0,13, +0,12.78, +0,12.05, +0,12.03, +0,11.42, +0.37,11.6, +3.1,14.88, +5.14,14.64, +5.17,14.2, +5.15,12.68, +6.16,10.66, +6.06,9.26, +6.62,7.89, +6.16,7.35, +5.65,7.14, +5.29,7.33, +3.9,8.3, +2.02,9.35, +2.85,7.83, +3.27,9.69, +2.93,9.11, +3.25,8.92, +3.2,8.79, +3.22,8.33, +4.2,7.72, +5.01,7.81, +5.96,7.1, +6.37,7.59, +6.37,7.59, +6.37,7.59, +6.96,5.84, +7.42,5.5, +8.06,3.78, +7.75,2.82, +7.9,2.87, +7.72,2.39, +7.64,2.15, +7.49,2.73, +7.49,2.73, +7.54,2.53, +7.85,1.38, +7.85,1.38, +8.73,2.26, +8.2,2.98, +7.89,3.39, +7.6,4.01, +7.49,2.73, +7.9,0.4, +8.17,-1.44, +9.35,-1.65, +9.63,-1.7, +8.91,-1.57, +9.28,-1.64, +10.02,-0.74, +10.37,0, +13.32,0, +13.36,-2.36, +13.66,-2.41, +13.11,-3.34, +12.83,-2.26, +13.1,-2.31, +12.51,-2.21, +12.66,-2.47, +13.31,-3.66, +14.18,-3.11, +11.02,-4.01, +11.33,-4.12, +12.81,-4.66, +13.46,-4.9, +13.61,-4.95, +11.78,-4.29, +11.33,-4.12, +11.33,-4.12, +11.33,-4.12, +11.33,-4.12, +10.8,-3.93, +10.23,-4.18, +10.41,-3.79, +10.41,-3.79, +10.14,-3.69, +10.41,-3.79, +11.24,-6.37, +12.23,-7.06, +12.54,-7.24, +14.07,-5.12, +15.43,-5.62, +15.7,-2.77, +16.06,-2.52, +16.65,0.43, +19.6,3.46, +7.5,0.95, +0,0, +5.89,-1.04, +15.76,-2.08, +13.9,-2.45, +12.11,-2.14, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.7,0.83, +-9.72,1.71, +-10.94,1.93, +-10.76,2.57, +-10.13,3.55, +-9.45,3.44, +-9.17,3.75, +-8.59,4.96, +-7.92,5.43, +-6.96,5.88, +-6.3,6.33, +-5.72,6.87, +-4.96,7.42, +-4.43,7.77, +-3.49,8.22, +-3.06,8.4, +-3.09,8.48, +-3.15,8.65, +-3.39,8.32, +-3.68,8.17, +-4.41,8.56, +-5.67,8.72, +-6.54,9.57, +-6.08,10.53, +-6.45,11.17, +-6.51,11.28, +-5.71,12.01, +-4.79,13.15, +-4.83,13.27, +-5.08,13.96, +-4.86,14.57, +-3.22,16.47, +-3.4,16.02, +-4.31,14.71, +-4.82,14.19, +-5.12,14.07, +-4.87,14.26, +-4.14,14.57, +-3,15.43, +-3.02,15.56, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-2.81,15.91, +-2.85,16.13, +-2.88,16.32, +-2.77,15.72, +-2.71,15.35, +-2.62,14.85, +-2.6,14.74, +-2.59,14.71, +-2.45,13.91, +-2.43,13.78, +-2.31,13.8, +-0.27,13.6, +0,13.02, +0,12.83, +0,12.29, +0,12.22, +0,11.88, +0.89,12.97, +2.99,15.62, +5.13,15.08, +5.31,14.59, +4.89,13.43, +5.67,11.56, +6.04,10.23, +7.07,8.58, +6.96,7.87, +7.18,7.46, +6.82,7.59, +6.02,7.64, +4.05,8.64, +3.07,7.79, +2.68,9.8, +2.28,9.96, +3.49,9.59, +3.4,9.34, +3.25,8.92, +3.84,8.04, +4.65,8.06, +6.17,7.74, +6.37,7.59, +6.37,7.59, +6.9,7.07, +7.35,6.16, +7.38,6.19, +8.02,4.65, +8.26,3.37, +8.1,2.95, +8.33,2.85, +7.69,1.98, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.75,1.77, +7.85,1.38, +8.23,1.53, +8.48,1.84, +8.04,2.24, +7.29,3.15, +7.49,2.73, +7.32,1.25, +8.05,-0.95, +9.28,-1.64, +9.76,-1.72, +8.85,-1.04, +9.24,-0.35, +9.75,-0.25, +13.49,0, +13.76,-0.45, +12.83,-2.26, +12.35,-2.18, +12.8,-3.78, +13.47,-3.61, +13.31,-3.25, +12.59,-2.22, +11.71,-2.06, +12.72,-2.24, +13.83,-3.85, +11.3,-3.77, +11.22,-4.09, +12.24,-4.45, +14.46,-5.26, +13.24,-6.89, +12.71,-6.5, +11.93,-4.34, +11.44,-4.16, +11.33,-4.12, +11.33,-4.12, +11.33,-4.12, +10.8,-3.93, +10.41,-3.79, +10.41,-3.79, +10.41,-3.79, +10.41,-3.79, +11.69,-6.01, +12.21,-7.05, +12.12,-7, +13.61,-4.95, +15.3,-5.57, +16.57,-2.92, +16.68,-2.94, +16.39,-0.37, +18.46,2.32, +15.91,0.85, +5.91,-1.04, +15.22,-2.68, +15.75,-2.15, +14.78,-2.17, +13.1,-2.31, +10.7,-3.01, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.19,1.26, +-10.88,1.92, +-10.91,1.92, +-10.89,1.92, +-9.94,2.83, +-9.34,3.4, +-9.23,3.6, +-8.44,4.87, +-7.14,5.62, +-6.8,5.8, +-5.86,6.75, +-5.54,7.01, +-4.53,7.71, +-4.23,7.87, +-3.1,8.39, +-3.06,8.4, +-3.06,8.4, +-3.11,8.38, +-3.3,8.3, +-3.68,8.13, +-4.01,8.19, +-4.84,8.51, +-5.05,8.9, +-5.48,9.48, +-5.52,9.67, +-6.04,10.5, +-6.04,10.78, +-5.41,12.15, +-5.17,12.28, +-4.7,12.83, +-4.93,13.56, +-4.9,13.46, +-5,13.73, +-5.12,14.07, +-5.12,14.07, +-5.12,14.07, +-4.59,14.22, +-3.77,14.45, +-2.74,14.76, +-3.79,14.71, +-3.11,15.25, +-2.77,15.7, +-2.77,15.7, +-2.84,16.11, +-2.91,16.48, +-2.9,16.44, +-2.77,15.7, +-2.77,15.7, +-2.74,15.55, +-2.6,14.74, +-2.6,14.74, +-2.46,13.97, +-2.43,13.78, +-2.43,13.78, +-0.59,13.85, +0,13.39, +0,13.05, +0,13.02, +0,12.93, +0,12.38, +0.92,13.27, +4.44,15.27, +5.45,14.98, +5.43,14.92, +5.02,13.8, +4.7,12.82, +6.15,10.97, +7.25,9.55, +8.27,8.21, +8.98,7.53, +8.92,7.63, +7.52,7.8, +6.37,7.59, +4.37,8.41, +2.36,10.87, +2.73,10.56, +3.69,10.14, +3.62,9.94, +3.48,9.56, +3.52,8.95, +4.82,8.35, +5.9,7.92, +6.37,7.59, +6.37,7.59, +6.51,7.46, +7.59,6.37, +7.5,6.29, +8.71,5.28, +7.74,4.47, +8.23,3.18, +8.06,2.93, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.53,2.56, +8.01,1.41, +7.9,0.77, +8.46,0, +8.09,0.79, +7.49,2.73, +7.41,2.91, +7.76,1.57, +7.94,-0.31, +8.83,-1.33, +9.76,-1.72, +9.12,-0.38, +9.53,0, +12.19,0.7, +13.6,-1.39, +13.25,-1.13, +12.83,-2.26, +12.28,-2.17, +11.92,-2.43, +12.04,-4.38, +13,-4.73, +13.19,-2.58, +12.61,-2.22, +12.51,-2.21, +12.38,-2.67, +12.63,-4.6, +12.54,-4.57, +12.04,-4.38, +12.87,-6.93, +13.07,-7.54, +12.96,-7.48, +13.43,-6.49, +12.35,-4.5, +11.85,-4.31, +11.63,-4.23, +11.43,-4.16, +11.33,-4.12, +11.19,-4.07, +10.69,-3.89, +12.11,-6.25, +10.98,-4, +12.26,-6.04, +12.96,-7.48, +12.48,-6.51, +14.52,-5.29, +15.02,-4.36, +16.32,-2.88, +17.2,-3.03, +16.7,-0.84, +16.29,0.43, +16.43,0.65, +14.07,-2.48, +15.38,-2.71, +15.7,-2.77, +15.49,-2.73, +14.42,-2.54, +13.3,-4.25, +3.17,-1.16, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.1,0, +-8.93,1.07, +-10.86,1.92, +-10.91,1.92, +-10.47,1.87, +-9.47,3.16, +-9.31,3.41, +-9.05,4.03, +-7.98,4.79, +-6.89,5.69, +-6.64,5.98, +-5.81,6.79, +-5.5,7.04, +-4.52,7.71, +-4.36,7.8, +-4.16,7.91, +-3.2,8.24, +-3.24,8.21, +-3.81,8.07, +-4.19,7.89, +-4.71,7.72, +-5.04,8.47, +-5.14,8.47, +-5.28,8.45, +-5.43,9.04, +-5.87,10, +-6.03,10.44, +-5.37,9.25, +-4.34,7.52, +-4.54,8.12, +-5.2,12.5, +-5.16,13, +-4.88,13.11, +-4.82,13.25, +-5.07,13.94, +-5.1,14.01, +-5.1,14, +-5.08,13.95, +-4.94,14.12, +-4.51,14.25, +-4.81,14.17, +-3.43,14.6, +-3.11,15.22, +-3.12,15.62, +-2.77,15.7, +-2.83,16.06, +-2.91,16.53, +-2.77,15.7, +-2.77,15.7, +-2.68,15.2, +-2.61,14.8, +-2.6,14.74, +-2.47,14.03, +-2.43,13.78, +-2.4,13.79, +-1.37,13.91, +-0.03,13.97, +0,13.3, +0,13.05, +0,13.02, +0,12.96, +1.46,12.43, +5.07,15.09, +5.45,14.98, +5.33,14.65, +5.04,13.86, +4.76,13.08, +5.45,11.95, +6.76,11.12, +7.98,10.03, +8.45,9.9, +8.31,9.28, +7.46,8.88, +6.63,8.22, +4.49,8.56, +3.34,10.37, +4.08,10.55, +3.93,10.8, +3.9,10.72, +3.72,10.23, +3.45,9.48, +4.25,8.44, +5.11,8.28, +6,7.72, +6.37,7.59, +6.58,7.39, +7.52,6.31, +7.59,6.37, +8.25,5.05, +7.74,4.47, +7.61,3.45, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.56,1.64, +7.87,1.19, +8.11,0, +8.11,1.02, +7.64,2.78, +7.49,2.73, +7.62,2.22, +8.21,0.4, +9,-0.52, +9.65,-1.45, +10.11,0, +11.53,0.5, +12.73,1.51, +12.37,-0.57, +12.71,-2.24, +12.83,-2.26, +11.98,-2.11, +11.53,-3.36, +11.72,-4.26, +12.82,-3.43, +13.21,-3.95, +13.37,-2.36, +13.07,-2.3, +12.34,-4.09, +12.46,-3.63, +12.54,-4.26, +12.89,-4.69, +12.98,-6.3, +12.96,-7.48, +13.66,-6.97, +14.27,-5.54, +14.52,-5.29, +13.58,-5.31, +12.52,-5.27, +11.46,-4.17, +11.33,-4.12, +11.33,-4.12, +11.33,-4.12, +12.64,-7.02, +12.96,-7.48, +13.17,-7.61, +12.68,-7.32, +13.49,-5.7, +13.86,-5.05, +15.13,-5.51, +16.55,-2.92, +17.09,-3.01, +16.95,-1.74, +15.33,0, +14.06,-0.61, +14.1,-2.49, +15.38,-2.71, +16.02,-2.82, +16.65,-2.94, +15.7,-2.77, +14.37,-5.23, +5.27,-1.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.34,0.59, +-10.82,1.82, +-10.91,1.92, +-10.39,1.83, +-9.43,3.04, +-9.17,3.36, +-8.63,4.24, +-7.91,4.66, +-6.99,5.57, +-6.84,5.76, +-6.37,6.25, +-5.71,6.88, +-5.17,7.04, +-4.68,7.06, +-4.27,7.44, +-3.97,7.03, +-4.01,6.97, +-4.64,7.49, +-5.02,7.38, +-5.5,7.11, +-6.11,7.55, +-6.33,7.63, +-6.53,7.79, +-6.9,8.45, +-6.7,9.15, +-6.06,10.38, +-4.07,7.05, +0,0, +-4.88,8.46, +-6.5,11.41, +-6.96,12.05, +-5.63,12.79, +-4.79,13.15, +-4.88,13.41, +-4.93,13.56, +-4.88,13.42, +-4.93,13.54, +-5.08,13.96, +-5.12,14.07, +-5.12,14.07, +-5.06,14.08, +-5.17,14.31, +-5.01,15.07, +-3.98,15.4, +-2.86,15.68, +-2.84,16.1, +-2.77,15.72, +-2.77,15.7, +-2.74,15.55, +-2.65,15.05, +-2.6,14.74, +-2.52,14.28, +-2.43,13.78, +-1.92,13.85, +-0.92,13.95, +0,14, +0,13.67, +0,13.07, +0,13.02, +0,13.02, +1.59,13.58, +5.45,14.98, +5.38,14.78, +5.15,14.16, +4.98,13.68, +4.79,13.15, +4.79,13.15, +5.57,12.61, +6.48,12.31, +7.32,11.63, +7.38,10.77, +7.02,10.32, +5.92,9.77, +4.94,8.55, +4.48,7.77, +4.47,7.86, +4.02,10.27, +4.12,11.33, +4.05,11.12, +3.73,10.25, +3.84,9.11, +4.81,8.33, +4.93,8.02, +6.29,7.5, +6.54,7.29, +7.59,6.37, +7.7,6.22, +7.09,4.99, +7.6,4.39, +7.15,3.95, +7.41,2.91, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.67,2.05, +7.85,1.38, +7.91,0.69, +9.7,2.29, +9.17,3.34, +7.64,2.78, +7.67,2.05, +8.15,0.69, +8.94,0, +9.43,0, +9.43,0, +9.75,0.38, +12.08,2.13, +12.03,0.3, +12.63,-0.28, +12.73,-2.24, +12.03,-2.12, +11.33,-4.12, +11.9,-4.33, +12.57,-2.6, +12.83,-3.07, +13.15,-2.65, +12.93,-3.99, +12.85,-4.68, +12.24,-4.45, +12.14,-4.42, +11.98,-4.36, +11.56,-6.67, +12.34,-6.77, +13.45,-6.45, +15.24,-5.55, +14.89,-6.4, +15.02,-6.73, +13.34,-6.95, +14.07,-5.12, +14.03,-5.57, +12.92,-6.57, +11.87,-5.24, +13.44,-7.76, +13.62,-7.86, +13.17,-7.61, +12.96,-7.48, +13.67,-4.98, +13.61,-4.95, +14.78,-5.38, +16.85,-2.97, +17.38,-3.06, +17.83,-1.95, +15.97,0, +14.7,-1.62, +14.6,-2.58, +15.22,-2.68, +16.27,-2.87, +17.31,-3.05, +17.52,-3.09, +15.79,-5.75, +9.99,-3.63, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.25,0.02, +-10.87,0.72, +-10.91,1.92, +-10.53,1.86, +-9.46,2.83, +-8.66,3.15, +-8.1,3.77, +-7.74,4.47, +-6.98,5.62, +-6.9,5.79, +-6.79,5.82, +-5.96,6.65, +-5.69,6.78, +-5.19,6.26, +-4.77,6.34, +-4.76,6.26, +-4.77,6.37, +-5.08,6.45, +-5.74,6.71, +-6.14,7.11, +-6.42,7.29, +-6.5,7.71, +-6.97,8.3, +-7.12,8.49, +-7.18,8.55, +-7.2,9.62, +-3.88,5.79, +0,0, +-0.65,1.13, +-6.57,11.38, +-6.97,12.06, +-6.93,12.16, +-6.31,12.48, +-4.98,13.21, +-4.82,13.24, +-4.8,13.18, +-4.88,13.42, +-5.05,13.86, +-5.01,13.78, +-5.11,14.03, +-5.12,14.07, +-5.14,14.13, +-5.4,14.84, +-5.36,15.01, +-4.45,15.28, +-3.04,15.64, +-2.77,15.7, +-2.77,15.7, +-2.73,15.48, +-2.6,14.74, +-2.6,14.74, +-2.57,14.58, +-2.43,13.8, +-2.3,13.8, +-0.69,13.96, +0,13.95, +0,13.19, +0,13.02, +0,13.02, +0,13.02, +1.63,13.8, +5.25,14.43, +5.13,14.1, +5.09,13.98, +4.83,13.27, +4.79,13.15, +4.79,13.15, +4.79,13.15, +5.04,13.04, +5.16,13, +5.19,12.27, +5.41,11.57, +4.9,10.88, +4.41,9.79, +4.55,8.36, +4.47,7.74, +3.05,7.86, +3.74,10.89, +4.17,11.46, +4,10.98, +3.57,9.8, +4.61,8.57, +4.47,7.74, +5.5,7.46, +6.37,7.59, +7.46,6.51, +7.59,6.37, +6.53,5.48, +7.19,4.7, +7.74,4.47, +7.77,3.84, +7.95,3.26, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.49,2.73, +7.67,2.05, +7.85,1.38, +7.92,0.52, +9.27,1.2, +12.78,3.05, +9.59,2.96, +7.98,2.48, +8.28,1.23, +12.03,0, +10.14,0, +8.94,0, +9.05,0, +11.03,1.49, +11.73,0, +12.05,0, +12.08,-1.22, +12.38,-2.69, +11.17,-4.07, +11.85,-4.31, +12.61,-3.08, +12.67,-2.23, +12.54,-3.74, +12.54,-4.26, +12.89,-4.69, +12.85,-4.68, +12.54,-4.57, +12.81,-4.66, +12.21,-5.67, +11.7,-6.75, +12.46,-6.26, +14.2,-5.17, +14.59,-7.15, +13.75,-7.51, +13.82,-6.41, +14.07,-5.12, +14.07,-5.12, +14.03,-5.57, +13.3,-7.68, +13.38,-7.73, +13.68,-7.9, +13.28,-7.67, +12.96,-7.48, +13.58,-5.29, +13.93,-5.07, +15.37,-5.17, +17.21,-3.03, +18.28,-3.22, +17.89,-1.61, +16.66,0, +15.14,-1.76, +15.22,-2.68, +15.7,-2.77, +17.47,-3.08, +18.52,-3.27, +18.76,-3.31, +17.46,-5.57, +11.94,-4.35, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.38,0, +-10.91,0.64, +-10.53,1.86, +-9.89,1.76, +-9.39,3.08, +-8.71,3.19, +-8.18,3.58, +-7.69,4.62, +-7.26,5.91, +-6.94,5.83, +-6.81,5.79, +-6.26,6.36, +-4.28,4.69, +-1.87,2.03, +-5.31,5.85, +-4.73,5.64, +-5.14,5.98, +-5.91,6.1, +-6.69,5.92, +-7.33,6.35, +-7.56,6.41, +-7.45,6.94, +-7.65,7.99, +-7.68,8.3, +-7.57,9.02, +-8.14,9.7, +-3.62,4.21, +0,0, +0,0, +-6.52,11.3, +-6.78,11.75, +-7,12.12, +-6.97,12.13, +-6.34,12.45, +-4.88,13.11, +-4.79,13.15, +-4.85,13.33, +-5.03,13.83, +-5.05,13.88, +-5.12,14.07, +-5.12,14.07, +-5.12,14.07, +-5.2,14.28, +-5.45,14.98, +-5.45,14.98, +-3.19,15.6, +-2.74,15.55, +-2.72,15.43, +-2.64,14.98, +-2.6,14.74, +-2.6,14.74, +-2.59,14.72, +-2.54,14.42, +-1.83,13.89, +-0.27,13.99, +0,13.83, +0,13.08, +0,13.02, +0,13.02, +0,13.02, +1.05,13.18, +4.41,14.26, +5.09,13.97, +4.84,13.31, +4.79,13.15, +4.79,13.15, +4.79,13.15, +3.64,13.47, +2.88,13.68, +2.6,13.75, +2.93,13.33, +3.74,12.44, +4.19,11.51, +3.96,10.88, +3.92,9.63, +4.46,7.72, +3.1,7.44, +2.58,7.97, +4.33,11.9, +4.32,11.1, +3.9,10.72, +4.48,8.95, +4.54,7.86, +4.47,7.74, +6.09,7.53, +6.98,6.98, +7.59,6.37, +6.41,5.58, +6.74,5.66, +7.74,4.47, +7.74,4.47, +7.71,4.22, +8.33,3.22, +8.4,3.06, +8.17,2.98, +7.72,2.81, +7.61,2.28, +7.85,1.38, +7.87,1.19, +10.19,2.05, +13.61,2.67, +12.83,2.26, +11.44,2.19, +10.34,1.54, +13.02,0, +11.39,0, +8.94,0, +9.91,0, +11.51,0.47, +11.99,0.7, +12.05,0, +11.93,0, +11.74,-1.72, +12.2,-4.22, +12.06,-4.05, +12.35,-2.18, +12.19,-2.15, +12.09,-4.4, +12.54,-4.57, +12.76,-4.64, +12.85,-4.68, +12.54,-4.57, +12.24,-4.83, +10.86,-6.27, +11.28,-6.51, +12.21,-5.82, +13.46,-4.9, +13.45,-7.2, +13.92,-7.07, +13.68,-7.23, +14.07,-5.12, +14.07,-5.12, +14.35,-5.53, +12.96,-7.48, +13.8,-7.97, +13.38,-7.73, +13.56,-7.83, +13.28,-7.67, +13.75,-5.79, +14.54,-5.29, +16.39,-4.92, +16.98,-2.99, +18.4,-3.24, +18.28,-3.22, +17.68,-2.76, +15.82,-2.79, +15.7,-2.77, +16.3,-2.87, +17.74,-3.13, +19.44,-3.43, +19.82,-3.5, +18.94,-4.78, +12.73,-4.63, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.79,0, +-10.68,0.74, +-9.91,1.75, +-9.76,1.72, +-9.43,2.48, +-8.43,3.05, +-8.29,3.33, +-7.74,4.57, +-7.15,5.68, +-6.85,5.75, +-6.68,5.61, +-4.75,3.98, +-0.73,0.75, +0,0, +0,0, +-3.65,3.72, +-5.83,5.42, +-5.57,4.68, +-6.11,5.12, +-7.33,6.15, +-7.61,6.39, +-8.11,6.8, +-8.69,7.29, +-9.04,7.93, +-8.24,8.8, +-7.89,9.36, +-8.04,9.58, +-6.43,7.66, +0,0, +-6.51,11.28, +-6.68,11.57, +-7,12.12, +-7,12.12, +-7,12.12, +-6.15,12.55, +-4.96,13.08, +-4.79,13.15, +-4.79,13.15, +-4.82,13.24, +-5.09,13.97, +-5.12,14.07, +-5.12,14.07, +-5.27,14.49, +-5.44,14.94, +-4.88,15.16, +-3.2,15.61, +-2.69,15.26, +-2.63,14.91, +-2.63,14.89, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-2.52,14.29, +-1.76,13.92, +-0.31,13.82, +0,13.12, +0,13.02, +0,13.02, +0,13.02, +0.66,12.99, +2.58,13.58, +4.4,13.85, +4.82,13.26, +4.79,13.15, +4.79,13.15, +4.79,13.15, +4.7,13.18, +2.64,13.74, +2.43,13.78, +2.43,13.78, +2.41,13.69, +2.26,12.83, +3.18,12.48, +3.97,11.57, +3.95,10.79, +4.79,9.19, +4.44,7.69, +3.04,6.71, +4.69,10.55, +5.48,10.69, +5.17,9.73, +5.25,9.09, +4.9,8.49, +4.85,8.4, +6.1,7.62, +6.49,7.2, +5.48,6.14, +6.29,6.11, +7.18,6.03, +7.3,5.11, +7.74,4.47, +7.74,4.47, +8.16,4.71, +8.69,3.98, +8.21,3.46, +8.27,3.01, +7.59,2.76, +8.01,1.41, +7.85,1.38, +9.88,2.14, +13.15,4.79, +12.95,2.53, +12.83,2.26, +12.83,2.26, +12.95,0.85, +13.02,0, +11.21,0, +10.08,0, +12.51,0, +12.81,0, +12.21,0, +12.05,0, +11.32,-1.46, +10.82,-3.35, +12.64,-4.6, +12.57,-3.2, +11.71,-2.67, +11.63,-4.23, +12.24,-4.45, +12.58,-4.58, +13.15,-4.79, +12.63,-4.6, +11.75,-5.51, +11,-6.35, +11.38,-6.57, +12.22,-5.65, +12.46,-6.26, +12.55,-6.58, +13.32,-6.72, +13.72,-6.18, +14.34,-5.22, +14.22,-5.17, +13.93,-5.41, +13.2,-7.62, +13.24,-7.65, +13.43,-7.75, +13.08,-7.55, +13.38,-7.73, +13.63,-6.34, +14.37,-5.23, +14.76,-5.02, +16.88,-2.98, +18.16,-3.2, +18.4,-3.24, +17.89,-3.15, +16.25,-2.87, +15.7,-2.77, +16.44,-3.3, +18.16,-3.2, +20.2,-3.56, +20.91,-3.69, +20.2,-3.56, +17.44,-6.35, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.51,0, +-10.25,0.3, +-9.79,1.51, +-9.76,1.72, +-9.12,2.37, +-8.43,3.03, +-8.31,3.29, +-7.75,4.46, +-7.6,4.7, +-6.85,5.75, +-6.6,5.54, +-3.49,2.93, +0,0, +0,0, +0,0, +0,0, +-2.62,2.2, +-1.22,1.02, +-3.29,2.76, +-7.61,6.39, +-7.99,6.71, +-8.43,7.08, +-8.84,7.42, +-9.23,7.75, +-9.09,7.9, +-7.83,9.16, +-7.75,9.23, +-6.52,7.77, +-0.69,1.1, +-6.95,11, +-6.54,11.33, +-6.83,11.83, +-7,12.12, +-7,12.12, +-6.97,12.14, +-5.88,12.68, +-5.39,12.9, +-4.98,13.23, +-4.79,13.15, +-4.89,13.44, +-5.11,14.03, +-5.13,14.09, +-5.16,14.18, +-5.4,14.85, +-5.28,15.04, +-4.53,15.23, +-2.64,14.96, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-2.49,14.12, +-1.46,13.89, +0,13.95, +0,13.8, +0,13.06, +0,13.02, +0,13.02, +1.2,12.94, +3.03,12.96, +4.39,13.28, +4.79,13.15, +4.79,13.15, +4.79,13.15, +4.79,13.15, +4.7,13.18, +2.91,13.67, +2.41,13.68, +2.42,13.7, +2.36,13.37, +2.29,12.98, +2.26,12.83, +3.68,12.4, +4.21,11.56, +4.67,10.34, +4.86,8.42, +4,6.93, +5.23,10.76, +6.03,10.44, +5.52,9.57, +5.15,8.92, +5.12,8.87, +5.21,9.02, +5.74,8.04, +5.96,7.1, +5.28,6.29, +5.59,6.66, +7.39,6.58, +7.26,6.09, +7.62,4.65, +7.74,4.47, +8.59,4.96, +8.59,4.96, +9.05,3.7, +8.52,3.1, +8.27,3.01, +8.23,1.45, +7.85,1.38, +10.52,2.91, +13.15,4.79, +12.54,4.57, +12.83,2.26, +12.83,2.26, +12.85,1.94, +13,0.25, +13.02,0, +12.22,0, +13.02,0, +12.75,0, +12.05,0, +11.73,0, +10.67,-1.39, +10.41,-3.79, +10.56,-3.84, +11.78,-4.29, +11.59,-4.22, +11.63,-4.23, +12.33,-4.49, +12.76,-4.64, +12.81,-4.66, +13.02,-4.74, +12.09,-5.14, +11,-6.35, +11.9,-5.94, +11.52,-6.65, +12.02,-6.94, +12.32,-6.25, +13.4,-5.4, +13.61,-4.95, +14.59,-5.31, +14.18,-5.16, +13.7,-5.91, +13.49,-7.79, +13.49,-7.79, +13.32,-7.69, +13.15,-7.59, +13.66,-7.89, +14.27,-5.54, +14.98,-5.45, +15.13,-5.51, +16.76,-3.24, +18.05,-3.18, +18.62,-3.28, +18.16,-3.2, +16.05,-2.83, +15.22,-4.56, +15.9,-5.31, +19.19,-3.38, +20.95,-3.69, +21.63,-3.81, +20.89,-3.68, +18.03,-6.03, +1.89,-0.69, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.72,0, +-10.19,0, +-10.24,0, +-9.82,1.26, +-9.53,1.68, +-8.79,1.9, +-8.4,3.06, +-8.22,3.49, +-7.74,4.47, +-7.31,5.12, +-3.82,3.14, +0,0, +-0.68,0.57, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.88,0.51, +-5.89,3.86, +-8.25,5.73, +-8.76,6.53, +-8.55,7.18, +-9.09,7.4, +-9.23,7.75, +-9.11,7.88, +-8.37,8.91, +-8.03,9.57, +-6.42,7.58, +0,0, +-8.01,10.25, +-5.06,8.77, +-6.55,11.35, +-6.76,11.71, +-6.71,11.62, +-6.75,11.69, +-6.75,11.69, +-6.71,11.62, +-5.26,13.14, +-4.79,13.15, +-4.79,13.15, +-4.93,13.54, +-5.1,14.02, +-5.12,14.07, +-5.24,14.41, +-5.43,14.91, +-3.94,15.22, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-2.59,14.68, +-2.47,14, +-1.46,13.9, +-0.03,14, +0,14, +0,13.37, +0,13.02, +0,13, +0.77,12.7, +2.62,12.87, +4.79,13.15, +4.79,13.15, +4.79,13.15, +4.79,13.15, +4.79,13.15, +4.68,12.86, +3.82,12.57, +2.26,12.83, +2.27,12.85, +2.29,13, +2.43,13.78, +2.41,13.69, +3.41,13.27, +4.45,12.24, +4.34,11.54, +4.71,9.53, +4.56,9.44, +6,11.52, +6.1,10.56, +5.7,9.88, +5.54,9.6, +5.54,9.6, +5.54,9.6, +5.29,8.59, +6.37,7.59, +6.22,7.41, +6.02,7.17, +6.37,7.59, +7.59,6.37, +7.52,5.16, +8.38,4.84, +8.59,4.96, +8.59,4.96, +8.95,4.17, +8.75,3.18, +8.4,3.06, +8.6,2.31, +8.81,1.55, +10.05,2.48, +13.15,4.79, +12.95,4.71, +12.34,4.09, +12.83,2.26, +12.83,2.26, +12.88,1.62, +13.02,0, +13.02,0, +12.97,0.57, +12.88,0.28, +12.05,0, +11.3,0, +10.94,-1.65, +10.1,-3.68, +10.92,-3.97, +11.19,-4.07, +11.53,-4.2, +12.24,-4.45, +12.63,-4.6, +12.95,-4.71, +13.15,-4.79, +13.04,-4.75, +12.47,-5.1, +11.94,-4.72, +11.56,-6.13, +11.59,-6.69, +11.38,-6.57, +12.87,-5.4, +13.3,-4.84, +13.35,-4.86, +14.22,-5.17, +14.07,-5.12, +13.58,-6.17, +13.2,-7.62, +13.24,-7.65, +13.24,-7.65, +13.44,-7.76, +13.49,-7.79, +15.31,-5.57, +16.06,-5.84, +15.66,-5.7, +16.55,-2.92, +17.92,-3.16, +17.39,-3.07, +16.32,-2.88, +15.78,-3.49, +14.98,-5.45, +16.37,-5.12, +19.72,-3.48, +21.87,-3.86, +22.59,-3.98, +21.51,-3.79, +19.02,-4.83, +2.83,-1.03, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.23,0, +-9.6,0, +-10.32,0, +-9.91,0, +-9.83,1.16, +-9.43,1.64, +-8.77,1.73, +-8.44,2.94, +-8.32,3.27, +-5.55,3.1, +-4.18,2.67, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.66,0.96, +-5.83,3.37, +-8.5,4.91, +-8.6,4.97, +-8.66,5.65, +0,0, +-2.29,1.89, +-9.21,7.73, +-9.23,7.75, +-8.86,8.18, +-7.35,8.61, +-4.76,4.99, +0,0, +0,0, +0,0, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.47,12.01, +-5.46,12.87, +-4.84,13.13, +-4.79,13.15, +-4.92,13.52, +-5.12,14.07, +-5.12,14.07, +-5.26,14.46, +-4.59,14.88, +-2.65,14.76, +-2.6,14.74, +-2.57,14.57, +-2.57,14.58, +-2.59,14.68, +-2.55,14.48, +-2.44,13.81, +-2.31,13.8, +-0.65,13.97, +0,13.89, +0,13.17, +0,13.02, +0,13.02, +0.82,12.87, +3.1,12.96, +4.79,13.15, +4.84,13.31, +4.93,13.56, +4.79,13.15, +4.79,13.15, +4.55,12.5, +4.42,12.25, +2.53,12.76, +2.26,12.83, +2.27,12.85, +2.41,13.66, +2.43,13.78, +3.73,13.45, +4.77,13.11, +4.5,12.36, +4.22,10.99, +4.87,10.32, +6.22,11.42, +6.3,10.9, +5.95,10.3, +5.78,10.02, +5.84,10.12, +5.54,9.6, +5.25,9.09, +6.17,7.74, +5.84,6.96, +6.09,7.26, +6.37,7.59, +7.05,6.92, +7.59,6.37, +8.02,5.77, +8.59,4.96, +8.59,4.96, +8.59,4.96, +8.63,4.29, +8.53,3.11, +8.4,3.06, +8.81,1.55, +11.22,3.35, +13.15,4.79, +13.02,4.74, +12.24,4.45, +12.73,2.63, +12.83,2.26, +12.83,2.26, +12.96,0.75, +13.02,0, +12.91,1.29, +12.91,1.29, +12.59,0, +11.24,0, +10.35,-2.04, +10.61,-3.86, +11.33,-4.12, +11.33,-4.12, +11.33,-4.12, +12.24,-4.45, +12.76,-4.64, +13,-4.73, +13.15,-4.79, +13.15,-4.79, +12.14,-4.67, +12.01,-4.37, +11.6,-5.83, +11.37,-6.57, +11.84,-6.84, +13.38,-4.87, +13.15,-4.79, +13.3,-4.84, +13.31,-5.08, +13.53,-5.29, +13.21,-7.23, +13.08,-7.55, +12.96,-7.48, +13.15,-7.59, +13.1,-7.56, +13.59,-7.85, +15.56,-6.65, +15.89,-5.78, +16.38,-5.96, +16.46,-4.39, +19.03,-3.36, +17.71,-3.12, +15.58,-2.75, +14.63,-4.69, +14.98,-5.45, +16.71,-4.65, +19.6,-3.46, +22.32,-3.93, +22.59,-3.98, +21.95,-3.87, +19.46,-3.91, +7.66,-2.29, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.13,-0.2, +-8.18,-0.81, +-10.89,0, +-9.98,0, +-9.91,0, +-9.91,0.08, +-9.69,1.49, +-8.97,1.6, +-8.65,2.2, +-8.4,3.06, +-2.79,1.05, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.57,0.91, +0,0, +-4.3,2.48, +-6.78,3.91, +-8.02,4.63, +-8.51,4.91, +-2.15,1.24, +-2.14,1.47, +-1.83,1.24, +-7.33,6.08, +-9.1,7.64, +-9.23,7.75, +-9.2,7.79, +-5.79,6.12, +0,0, +-5.04,5.9, +-3.14,3.74, +-1.87,2.67, +-6.64,11.2, +-6.54,11.26, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.73,11.66, +-6.78,12.24, +-5.08,13.03, +-4.79,13.15, +-4.89,13.43, +-5.09,13.97, +-5.12,14.07, +-5.12,14.07, +-4.44,14.27, +-2.6,14.74, +-2.6,14.74, +-2.47,14.03, +-2.46,13.97, +-2.5,14.19, +-2.46,13.93, +-2.43,13.78, +-2.43,13.78, +-1.78,13.87, +0,13.66, +0,13.02, +0,13.41, +0,13.27, +0.09,13.02, +2.57,13.32, +4.74,13.02, +4.79,13.17, +5.1,13.68, +4.79,13.15, +4.79,13.15, +4.51,12.39, +4.45,12.24, +4.16,12.33, +2.89,12.68, +2.41,12.8, +2.53,13.22, +3.38,13.55, +4.93,13.64, +5.05,13.88, +4.79,13.15, +4.39,12.06, +4.93,10.05, +6.02,11.64, +6.22,11.42, +6.12,10.61, +6.16,10.68, +6.03,10.44, +5.81,10.06, +5.54,9.6, +5.74,8.04, +6.02,7.17, +6.19,7.38, +6.37,7.59, +6.37,7.59, +7.35,6.45, +7.82,6.56, +8.36,6.64, +8.59,4.96, +8.59,4.96, +8.59,4.96, +8.45,3.46, +8.4,3.06, +8.73,1.85, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13,4.73, +12.54,3.76, +12.83,2.26, +12.83,2.26, +12.83,2.26, +12.99,0.38, +12.89,1.51, +12.9,1.41, +12.03,0.27, +11.15,-0.43, +10.37,-2.45, +10.64,-3.87, +11.33,-4.12, +11.06,-4.03, +11.13,-4.55, +12.24,-4.45, +12.75,-4.64, +13.15,-4.79, +13.54,-4.93, +13.7,-4.99, +12.85,-4.68, +12.24,-4.45, +12.37,-4.5, +12.98,-5.53, +12.55,-6.6, +13.15,-4.79, +13.61,-4.95, +13.84,-5.04, +13.26,-5.69, +12.12,-7, +12.26,-7.08, +13.17,-7.61, +13.07,-7.54, +12.6,-7.28, +12.96,-7.48, +13.52,-7.81, +14.88,-7.72, +15.68,-5.71, +14.67,-5.34, +14.51,-5.53, +18.43,-4.07, +16.68,-2.94, +16.14,-2.85, +14.22,-5.17, +14.78,-5.38, +16.71,-4.97, +20.99,-3.7, +22.73,-4.01, +23.14,-4.08, +22.35,-3.94, +19.72,-3.48, +9.09,-1.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.15,-0.2, +-8.32,-1.47, +-10.48,-0.79, +-10.54,-0.02, +-9.91,0, +-9.91,0, +-9.9,0.25, +-9.77,1.7, +-9.1,1.61, +-8.89,1.76, +-8.43,2.97, +0,0, +0,0, +0,0, +-0.87,0.48, +-2.93,1.63, +-3.55,1.77, +-6.65,3.63, +-7.75,4.33, +-6.01,3.3, +-4.55,2.62, +-8.45,4.88, +-8.56,4.94, +-4.32,2.31, +-5.3,3.04, +-8.05,5.03, +-8.5,7.14, +-9.14,7.67, +-8.08,6.78, +-5.11,4.32, +-6.84,7.61, +-8.08,9.63, +-6.56,7.4, +-2.39,2.85, +-1.84,2.23, +-8.15,10.15, +-7.08,10.91, +-6.53,11.27, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.54,11.32, +-6.74,11.72, +-6.29,12.42, +-4.86,13.12, +-4.79,13.15, +-4.92,13.52, +-5.08,13.94, +-5.11,14.04, +-4.93,14.13, +-4.51,14.17, +-2.74,14.68, +-2.47,14, +-2.44,13.86, +-2.45,13.9, +-2.44,13.83, +-2.43,13.78, +-2.43,13.78, +-1.25,13.85, +0,13.14, +0,13.02, +0,13.69, +0,13.8, +0,13.43, +2.87,13.22, +4.92,12.77, +5.79,12.74, +6.81,13.05, +5.92,12.63, +4.72,12.88, +4.45,12.24, +4.45,12.24, +4.45,12.24, +4.45,12.24, +4.45,12.24, +4.37,12.68, +4.79,13.15, +5.05,13.88, +5.1,14, +4.85,13.33, +4.56,12.52, +4.51,11.47, +6.03,10.5, +6.26,10.85, +6.59,11.42, +6.69,11.59, +6.45,11.17, +6.19,10.72, +5.69,9.86, +5.28,8.26, +5.83,6.94, +6.23,7.43, +6.16,7.35, +6.37,7.59, +7.25,6.72, +7.71,6.47, +8.26,6.93, +8.54,5.88, +8.59,4.96, +8.59,4.96, +8.16,3.91, +8.33,3.22, +8.31,2.78, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.15,4.79, +12.9,2.8, +12.83,2.26, +12.83,2.26, +13,0.23, +13.02,0, +12.95,0.85, +11.95,1.2, +11.06,-0.27, +9.79,-2.11, +10.56,-3.84, +11.1,-4.04, +10.53,-3.83, +10.43,-5.39, +11.74,-4.77, +12.63,-4.6, +13.15,-4.79, +14.07,-5.12, +14.22,-5.17, +13.46,-4.9, +12.24,-4.45, +12.35,-4.5, +12.95,-4.71, +13.41,-4.88, +13.56,-4.93, +13.15,-4.79, +13.49,-4.91, +12.95,-6.19, +12.68,-7.32, +12.21,-7.05, +12.36,-7.14, +12.68,-7.32, +12.24,-7.07, +12.6,-7.28, +13.28,-7.67, +14.67,-8.47, +12.24,-4.45, +11.33,-4.76, +11.11,-5.16, +11.93,-4.6, +16.85,-3.82, +16.63,-3.31, +15.11,-5.5, +15.21,-5.53, +17.72,-4.67, +21.63,-3.81, +23.33,-4.11, +23.23,-4.1, +21.99,-3.88, +19.29,-3.4, +8.36,-1.47, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.74,-1.89, +-9.9,-1.69, +-9.91,-0.81, +-9.91,0, +-9.91,0, +-9.72,0.09, +-9.56,1.28, +-9.44,1.67, +-9.47,1.69, +-8.94,2.53, +-5.25,1.91, +0,0, +-0.93,0.34, +-6,2.18, +-8.4,3.79, +-8.44,3.09, +-8.25,3.42, +-8.23,3.47, +-3.5,1.54, +0,0, +-5.41,3.12, +-8.51,4.92, +-8.9,5.14, +-9.53,5.5, +-9.44,5.79, +-8.66,7.18, +-9.17,7.69, +-7.18,5.99, +-2.31,1.9, +-6.18,6.23, +-7.76,9.22, +-4.82,4.91, +0,0, +-4.19,4.99, +-8.37,9.98, +-8.29,10.04, +-6.84,11.07, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.78,11.8, +-6.27,12.49, +-4.85,13.13, +-4.79,13.15, +-4.8,13.17, +-5.05,13.88, +-5.08,13.96, +-4.96,13.64, +-3.67,13.84, +-2.67,13.74, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.08,13.8, +-0.34,13.67, +0,13.35, +0,13.89, +0,14, +0,14, +2.1,13.23, +6.98,12.09, +7.21,12.49, +7.36,12.75, +6.84,12.2, +4.67,12.38, +4.45,12.24, +4.45,12.24, +4.45,12.24, +4.45,12.24, +4.45,12.24, +4.45,12.24, +4.77,13.11, +4.88,13.42, +5.12,14.07, +5.1,14, +4.83,13.27, +4.44,12.21, +5.74,11.18, +6.07,10.52, +6.11,10.59, +6.8,10.91, +6.72,11.64, +6.66,11.53, +5.65,9.79, +5.25,7.72, +5.83,6.94, +6.06,7.22, +6.25,7.45, +6.16,7.35, +6.92,7.05, +7.59,6.37, +7.85,6.59, +8.1,6.55, +8.57,5.32, +8.27,4.77, +7.95,4.59, +8.07,3.76, +11.71,6.56, +12.56,6.05, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.57,3.22, +12.95,2.28, +12.88,1.7, +13.02,0, +13.02,0, +13.02,0, +12.78,0, +11.44,0, +9.64,-2.2, +10.08,-3.67, +11.19,-4.07, +10.44,-4.31, +10.44,-6.03, +11.59,-4.54, +12.64,-4.6, +13.3,-4.84, +13.72,-4.99, +14.11,-4.47, +13.67,-4.98, +13.05,-4.75, +13.41,-4.88, +13.15,-4.79, +13.54,-4.93, +13.54,-4.93, +12.85,-4.68, +13.76,-5.01, +13.45,-6.7, +12.48,-7.21, +12.33,-7.12, +12.33,-7.12, +12.4,-7.16, +12.02,-6.94, +12.36,-7.14, +13.59,-7.85, +14.22,-8.21, +11.93,-4.79, +11.43,-4.66, +11.63,-4.23, +11.72,-4.26, +14.93,-4.99, +16.07,-5.85, +15.38,-5.6, +15.28,-5.56, +18.81,-4.53, +21.49,-3.79, +23.55,-4.15, +23.16,-4.08, +21.63,-3.81, +18.64,-3.29, +2.78,-0.49, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.06,-1.07, +-10.91,-1.92, +-10.31,-1.82, +-9.8,-1.73, +-9.79,-1.55, +-9.89,-0.09, +-9.42,0, +-9.04,0, +-8.91,0.57, +-8.94,1.58, +-9.72,1.71, +-9.73,1.75, +-9.41,2.18, +-8.67,2.87, +-8.51,3.05, +-8.52,3.1, +-8.83,3.21, +-9.13,3.32, +-9.03,3.29, +-8.79,3.2, +-2.1,0.7, +0,0, +-6.96,4.02, +-8.59,4.96, +-8.78,5.07, +-9.55,5.51, +-9.43,5.8, +-7.36,6.09, +-0.93,0.67, +-2.3,1.87, +-9.23,7.75, +-7.99,6.98, +-5.61,6.34, +0,0, +0,0, +-7.33,8.73, +-8.33,9.93, +-8.26,9.94, +-7.19,10.77, +-6.71,11.16, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.54,11.32, +-6.67,11.55, +-5.65,12.79, +-4.86,13.13, +-4.79,13.15, +-4.81,13.22, +-4.82,13.25, +-4.89,13.44, +-4.82,13.23, +-4.76,13.16, +-2.7,13.72, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.76, +-2.43,13.78, +-0.89,13.95, +0,14, +0,14, +0,14, +0,14, +1.92,13.32, +7,12.12, +7.33,12.69, +7.23,12.53, +6.93,12.01, +5.69,11.67, +4.87,12.06, +4.89,12.04, +4.7,12.14, +4.96,12.01, +4.81,12.08, +4.53,12.21, +4.5,12.36, +4.79,13.15, +5.03,13.83, +5.12,14.07, +4.94,13.59, +4.72,12.9, +6.35,11.51, +6.52,11.3, +6.89,11.03, +7.94,10.3, +7.26,11.05, +6.67,11.56, +6.08,10.53, +5.29,9.16, +5.69,7.78, +6.22,7.41, +6.37,7.59, +6.23,7.43, +6.37,7.59, +7.42,6.55, +7.79,6.54, +7.59,6.37, +7.89,5.95, +7.95,5.58, +8.27,5.02, +10.66,6.16, +12.12,7, +12.24,6.75, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.5,3.48, +13.65,2.41, +12.85,1.94, +13.02,0, +13.02,0, +13.02,0, +13.02,0, +11.82,-0.21, +9.86,-1.99, +9.91,-2.66, +10.97,-3.77, +11.33,-4.12, +10.88,-5.07, +11.89,-4.91, +12.7,-4.62, +13.15,-4.79, +13.38,-4.47, +13.54,-3.76, +12.76,-4.64, +12.63,-4.6, +13.05,-4.75, +13.15,-4.79, +13.49,-4.91, +12.24,-4.45, +12.5,-4.55, +13.06,-4.75, +12.62,-6.32, +12.31,-7.11, +12.96,-7.48, +12.48,-7.21, +11.91,-6.88, +11.52,-6.65, +11.56,-6.67, +13.2,-7.62, +14.25,-8.22, +11.98,-4.36, +11.48,-4.18, +11.33,-4.12, +11.33,-4.12, +12.64,-4.6, +15.13,-5.51, +15.27,-5.56, +15.46,-5.63, +18.02,-5.33, +21.84,-3.85, +23.43,-4.13, +23,-4.06, +21.22,-3.74, +18.09,-3.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.43,-1.1, +-10.91,-1.92, +-10.86,-1.92, +-10.21,-1.8, +-10,-1.76, +-9.78,-1.64, +-9.89,-0.54, +-9.75,0, +-9.24,0, +-8.94,0.4, +-9.63,1.63, +-9.76,1.72, +-9.76,1.72, +-9.76,1.74, +-9.75,1.8, +-9.41,2.76, +-9.37,3.08, +-9.32,3.39, +-9.32,3.39, +-9.32,3.39, +-9.26,3.37, +-7.23,2.63, +-6.92,2.52, +-9.17,4.51, +-9,5.19, +-8.99,5.19, +-9.54,5.51, +-9.6,5.54, +-6.23,4.26, +0,0, +0,0, +-3.08,2.55, +-2.32,1.72, +0,0, +-3.44,4.1, +-6.71,7.78, +-8.15,9.71, +-8,9.53, +-7.81,9.31, +-7.88,9.46, +-7.97,10.2, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.63,11.48, +-6.67,11.56, +-6.45,12.02, +-4.89,13.09, +-4.79,13.15, +-4.79,13.15, +-4.79,13.15, +-4.79,13.15, +-4.79,13.15, +-4.74,13.02, +-2.88,13.3, +-2.37,13.43, +-2.43,13.76, +-2.43,13.76, +-2.37,13.46, +-2.33,13.21, +-2.43,13.78, +-2.24,13.81, +-0.85,13.95, +0,14.22, +0,14.17, +0,14, +0.87,14.08, +7,12.12, +7.39,12.79, +7.27,12.59, +6.87,11.9, +6.51,11.28, +6.51,11.28, +6.51,11.28, +6.51,11.28, +6.46,11.19, +6.09,10.55, +5.89,11.21, +4.45,12.24, +4.75,13.06, +4.92,13.52, +5.12,14.07, +5.06,13.91, +5.9,12.74, +7,12.12, +7,12.12, +7.6,11.49, +8.92,10.63, +8.25,10.91, +6.78,11.75, +6.43,11.14, +5.7,9.87, +5.28,8.71, +6.17,7.74, +6.37,7.59, +6.37,7.59, +6.3,7.51, +6.68,7.29, +7.72,6.48, +7.86,6.6, +7.59,6.37, +9.84,8.26, +11.31,7.39, +12.12,7, +12.12,7, +12.12,7, +12.92,5.28, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.36,4, +13.78,2.43, +13.4,2.03, +13.02,0, +13.02,0, +13.02,0, +13.02,0, +12.38,0, +10.05,-1.77, +10.34,-1.82, +10.55,-2.51, +10.61,-3.86, +11.1,-4.04, +12.35,-4.5, +13.04,-4.75, +13.93,-5.07, +14.19,-4.24, +14.36,-3.38, +13.35,-4.51, +12.24,-4.45, +12.85,-4.68, +13.15,-4.79, +13.02,-4.74, +12.24,-4.45, +12.39,-4.51, +12.75,-4.99, +12.12,-7, +12.54,-7.24, +13.15,-7.59, +11.88,-6.86, +11.38,-6.57, +11.49,-6.63, +11.56,-6.67, +12.19,-7.64, +13.55,-8.33, +11.55,-4.21, +11.67,-4.25, +11.98,-4.36, +11.2,-3.35, +11.75,-3.16, +13.58,-3.7, +15.13,-5.51, +14.98,-5.45, +17.62,-4.81, +21.63,-3.81, +23.43,-4.13, +22.91,-4.04, +20.56,-3.62, +13.57,-2.39, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.49,-0.54, +-8.17,-2.72, +-10.81,-2.38, +-10.91,-1.92, +-10.91,-1.92, +-10.5,-1.85, +-9.76,-1.72, +-9.86,-0.82, +-9.91,0, +-9.89,0, +-9.72,0, +-9.8,0.51, +-9.89,0.5, +-9.79,1.53, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.73,1.87, +-9.55,2.58, +-9.34,3.32, +-9.32,3.39, +-9.32,3.39, +-9.58,3.49, +-10.3,3.75, +-10.3,4.07, +-9.76,5.22, +-9.6,5.54, +-9.6,5.54, +-9.6,5.54, +-9.26,6.28, +-9.12,7.58, +-8.19,6.73, +-2.86,2.4, +0,0, +0,0, +0,0, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.8,9.63, +-6.68,11.12, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.73,11.65, +-6.69,11.65, +-5.77,12.63, +-4.81,13.14, +-4.79,13.15, +-4.78,13.13, +-4.65,12.76, +-4.78,13.13, +-4.63,12.79, +-2.89,12.68, +-2.27,12.85, +-2.4,13.59, +-2.42,13.7, +-2.31,13.09, +-2.32,13.18, +-2.43,13.78, +-2.43,13.78, +-1.89,13.88, +-0.17,14.79, +0,14.69, +0,14.36, +1.75,14.26, +7,12.12, +7.03,12.18, +7.01,12.15, +6.62,11.47, +6.51,11.28, +6.51,11.28, +6.51,11.28, +6.51,11.28, +6.27,10.87, +6.03,10.44, +6.08,10.53, +5.78,11.47, +4.63,12.73, +4.79,13.17, +5,13.75, +5.5,13.9, +7.32,13.02, +7.45,12.91, +7.35,12.74, +8.01,12.44, +9.39,11.19, +9.02,10.75, +7.72,11.28, +6.78,11.75, +6.09,10.54, +5.47,9.47, +5.72,8.37, +6.37,7.59, +6.37,7.59, +6.37,7.59, +6.22,7.41, +7.61,6.73, +7.59,6.37, +7.94,6.66, +10.27,8.62, +11.42,8, +12.23,7.06, +12.96,7.48, +12.68,7.32, +12.51,6.82, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.47,3.61, +13.78,2.43, +13.78,2.43, +13.3,0, +13.02,0, +13.02,0, +13.02,0, +12.59,0, +10.72,-1.56, +10.62,-1.87, +10.91,-1.92, +10.54,-3.84, +11.33,-4.12, +12.39,-4.51, +13.15,-4.79, +13.93,-5.07, +14.22,-5.17, +14.95,-2.64, +13.95,-3.69, +12.51,-4.55, +13.02,-4.74, +13.28,-4.83, +12.58,-4.58, +12.54,-4.57, +12.13,-4.68, +11.86,-6.41, +12.36,-7.14, +12.12,-7, +12.96,-7.48, +11.75,-6.78, +11.28,-6.51, +10.44,-7.29, +10.67,-7.62, +10.85,-9.1, +12.59,-9.33, +11.46,-4.17, +12.04,-4.38, +11.78,-4.29, +11.06,-4.03, +11.01,-3.71, +12.99,-3.8, +14.57,-5.3, +14.72,-5.36, +17.57,-3.82, +21.31,-3.76, +22.86,-4.03, +22.59,-3.98, +20.27,-2.71, +8.82,-0.78, +4.67,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.7,-2.08, +-10.41,-3.79, +-10.72,-2.7, +-10.91,-1.92, +-10.91,-1.92, +-10.34,-1.82, +-9.76,-1.72, +-9.87,-0.72, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0.06, +-9.81,1.4, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.76,1.74, +-9.64,2.36, +-9.62,3.29, +-9.68,3.52, +-9.92,3.61, +-10.41,3.79, +-10.38,3.87, +-9.84,5.07, +-3.2,1.85, +-6.85,3.96, +-9.6,5.54, +-9.61,5.75, +-9.37,7.45, +-9.14,7.67, +-8.66,7.27, +-6.11,5.12, +-5.78,4.75, +-6.62,6.1, +-8.16,8.85, +-7.75,9.23, +-7.75,9.23, +-7.76,9.25, +-7.79,9.28, +-8.21,9.86, +-7.37,10.72, +-6.53,11.27, +-6.51,11.28, +-6.51,11.28, +-6.52,11.3, +-6.53,11.3, +-6.63,11.86, +-5.08,13.04, +-4.79,13.15, +-4.78,13.13, +-4.51,12.4, +-4.68,12.87, +-4.53,12.45, +-3.68,12.47, +-2.26,12.83, +-2.28,12.91, +-2.41,13.66, +-2.43,13.76, +-2.42,13.7, +-2.43,13.78, +-2.43,13.78, +-1.81,13.86, +-0.13,14.84, +0,14.95, +0,14.92, +3,13.75, +7,12.12, +7,12.12, +6.93,12.01, +6.52,11.3, +6.51,11.28, +6.51,11.28, +6.51,11.28, +6.51,11.28, +6.29,10.9, +6.03,10.44, +6.03,10.44, +6.17,10.69, +6.29,11.46, +5.8,12.55, +6.04,12.97, +6.99,13.21, +7.5,12.99, +7.65,13.25, +7.66,13.28, +8.68,12.75, +9.8,11.68, +9.34,11.13, +8.42,11.21, +6.76,11.7, +6.74,10.67, +5.47,9.47, +5.66,9.31, +6.78,8.07, +6.37,7.59, +6.37,7.59, +6.56,7.82, +6.72,7.25, +7.59,6.37, +7.74,6.5, +11.1,9.69, +11.15,9.35, +12.57,7.63, +12.96,7.48, +12.96,7.48, +13.09,7.22, +13.3,4.84, +13.15,4.79, +13.15,4.79, +13.51,4.06, +13.78,2.43, +13.78,2.43, +13.79,0.91, +13.15,0, +13.02,0, +13.02,0, +12.28,-0.55, +10.53,-1.86, +10.72,-1.89, +11.6,-2.04, +11.73,-2.6, +11.33,-4.12, +12.24,-4.45, +13.15,-4.79, +13.66,-4.97, +14.46,-3.19, +15.29,-2.7, +14.28,-3.91, +13.3,-4.84, +13.25,-4.82, +13.41,-4.88, +13.02,-4.74, +12.92,-4.7, +12.56,-6.05, +11.95,-6.9, +12.12,-7, +12.12,-7, +12.12,-7, +11.76,-6.79, +11.17,-6.45, +9.36,-7.85, +10.04,-8.11, +10.93,-9.17, +11.38,-8.47, +11.07,-4.67, +11.63,-4.23, +11.53,-4.2, +11.48,-4.18, +10.87,-3.96, +11.19,-4.07, +11.72,-4.26, +13.76,-4.4, +16.33,-3.79, +20.25,-3.57, +22.47,-3.96, +21.51,-3.79, +19.97,-0.61, +14.55,0, +10.74,0, +3.44,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.06,-0.38, +-6.12,-2.23, +-10.1,-3.56, +-10.73,-2.7, +-10.88,-2.07, +-10.91,-1.92, +-10.91,-1.92, +-10.27,-1.81, +-9.76,-1.72, +-9.88,-0.63, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0.04, +-9.8,1.44, +-9.76,1.72, +-9.76,1.72, +-9.84,1.73, +-10.04,1.77, +-6.98,1.21, +-1.44,0.28, +-8.39,1.74, +-10.57,3.29, +-10.39,3.78, +-10.41,3.79, +-10.41,3.79, +-10.41,3.79, +0,0, +0,0, +-9.6,5.54, +-10.08,5.82, +-10.3,6.24, +-9.59,7.27, +-9.22,7.73, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.19,7.79, +-8.19,8.82, +-7.77,9.22, +-7.79,9.29, +-8.2,9.78, +-8.31,9.9, +-8.31,10.02, +-6.87,11.05, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.58,11.41, +-5.8,12.6, +-4.79,13.15, +-4.79,13.15, +-4.51,12.4, +-4.61,12.66, +-4.54,12.47, +-4.14,12.34, +-2.26,12.83, +-2.26,12.83, +-2.37,13.46, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-0.6,14.33, +0,14.41, +0,14.91, +1.4,14.4, +7,12.12, +6.99,12.1, +6.78,11.74, +6.51,11.28, +6.51,11.28, +6.51,11.28, +6.51,11.28, +6.51,11.28, +6.8,10.05, +6.91,9.83, +6.06,10.42, +6.07,10.52, +6.46,11.18, +6.84,11.85, +7.02,12.16, +7.48,12.96, +7.66,13.26, +7.97,13.8, +8.11,14.05, +8.23,14.19, +9.99,12.43, +9.72,11.58, +9.52,11.34, +8.36,10.77, +7.78,10.9, +6.47,9.96, +6.88,8.5, +6.96,8.29, +6.6,7.86, +6.48,7.72, +6.69,7.98, +6.54,7.79, +7.25,6.72, +8.49,8.48, +9.93,11.16, +11.26,9.83, +11.68,9.32, +12.96,7.48, +12.96,7.48, +12.96,7.48, +13.6,5.87, +13.28,4.83, +13.15,4.79, +13.49,4.91, +13.69,2.77, +13.78,2.43, +13.87,1.39, +13.39,0, +13.02,0, +13.02,0, +12.25,-0.21, +10.91,-1.92, +11.02,-1.94, +11.87,-2.09, +11.87,-2.09, +11.44,-4.16, +12.5,-4.55, +12.88,-4.69, +13.61,-4.16, +14.74,-2.6, +15.01,-2.65, +14.33,-3.58, +13.38,-4.87, +14.07,-5.12, +13.56,-4.93, +13.15,-4.79, +13.61,-4.95, +12.57,-6.88, +12.4,-7.16, +12.21,-7.05, +12.26,-7.08, +11.91,-6.88, +11.76,-6.79, +10.83,-7.35, +9.7,-8.14, +11.03,-4.76, +11.32,-5.66, +10.72,-6.19, +11.41,-5.66, +11.19,-4.74, +11.1,-4.6, +11.21,-4.66, +11.48,-4.18, +11.22,-4.09, +11.48,-4.18, +13.53,-4.32, +15.94,-3.57, +19.72,-3.48, +21.39,-3.77, +21.31,-1.91, +19.27,0, +16.11,0, +13.27,0, +4.72,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.06,-0.38, +-10,-3.64, +-10.25,-2.99, +-10.96,-2.06, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.44,-1.84, +-9.76,-1.72, +-9.85,-0.86, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.9,0.32, +-9.77,1.7, +-9.76,1.72, +-10.26,1.81, +-10.74,1.89, +-4.56,0.68, +-4.72,0.7, +-9.7,1.71, +-10.66,2.93, +-10.5,3.52, +-10.41,3.79, +-10.45,3.8, +-10.57,3.85, +-1.3,0.47, +0,0, +-9.79,5.34, +-10.01,5.78, +-10.44,6.03, +-10.26,6.32, +-9.63,7.23, +-9.29,7.68, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.17,7.82, +-8.23,8.78, +-7.85,9.35, +-8.29,9.88, +-8.37,9.98, +-8.37,9.98, +-8.15,10.15, +-6.98,10.98, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.52,11.3, +-6.66,12.08, +-4.95,13.09, +-4.79,13.15, +-4.64,12.74, +-4.75,13.06, +-4.52,12.42, +-3.77,12.45, +-2.33,12.81, +-2.26,12.83, +-2.32,13.17, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.4,13.79, +-0.49,14.02, +0,14.21, +0,14.69, +2.33,14.02, +7,12.12, +6.96,12.05, +6.51,11.28, +6.62,11.21, +6.51,11.28, +7.1,10.88, +7.21,10.81, +7.31,10.73, +7.93,9.49, +7.75,9.23, +7.43,9.47, +6.4,10.18, +6.29,10.9, +6.55,11.34, +7,12.12, +7.42,12.85, +7.66,13.27, +7.98,13.82, +8.44,14.62, +8.46,14.65, +9.56,13.68, +10.16,12.11, +9.78,11.66, +9.44,11.25, +8.93,10.64, +7.93,9.45, +7.66,9.13, +7.81,9.31, +7.33,8.74, +7.18,8.55, +6.98,8.32, +7.19,8.57, +7.07,8.09, +8.04,6.75, +9.61,9.19, +9.88,11.2, +11.47,9.62, +12.03,8.82, +12.96,7.48, +12.96,7.48, +13.28,6.81, +13.46,4.9, +13.15,4.79, +13.15,4.79, +13.67,3.34, +13.78,2.43, +13.81,2.16, +14,0, +13.15,0, +13.02,0, +12.21,0, +11.04,-0.43, +11.19,-1.97, +11.87,-2.09, +11.87,-2.09, +12.45,-2.75, +12.84,-4.07, +12.24,-4.45, +13.26,-3.12, +13.94,-2.46, +14.74,-2.6, +14.82,-3.37, +14.18,-5.16, +14.17,-5.16, +14.07,-5.12, +13.56,-4.93, +13.46,-4.9, +13.35,-6.93, +12.24,-7.07, +12.54,-7.24, +12.4,-7.16, +12.23,-7.06, +11.37,-6.57, +11.13,-7.29, +9.23,-7.75, +11.44,-5.83, +11.02,-5.93, +10.9,-6.3, +11.29,-6.19, +10.44,-6.03, +10.58,-6.11, +10.43,-5.75, +10.32,-5.3, +11.63,-4.23, +11.96,-4.35, +13.28,-4.83, +15.8,-2.79, +18.44,-3.25, +20.4,-3.6, +20.58,0, +18.39,0, +15.33,0, +12.02,0.32, +9.61,1.48, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.56,-1.3, +-9.97,-3.63, +-10.62,-3.04, +-11.15,-1.97, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.34,-1.82, +-9.76,-1.72, +-9.87,-0.64, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0.27, +-10.22,1.53, +-10.42,1.84, +-8.23,1.45, +-1.46,0.26, +-1.19,0.19, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.81,2.35, +-10.45,3.69, +-10.92,3.97, +-5.48,1.99, +-3.53,1.29, +-10.59,3.86, +-10.69,5.19, +-10.46,5.98, +-10.44,6.03, +-10.44,6.03, +-10.38,6.12, +-9.76,7.03, +-9.27,7.71, +-9.23,7.75, +-9.46,7.94, +-9.23,7.75, +-8.84,8.35, +-8.29,9.83, +-8.37,9.98, +-8.34,9.94, +-8.37,9.98, +-8.37,9.98, +-6.98,10.98, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.52,11.3, +-6.93,12.01, +-5.41,12.89, +-4.79,13.15, +-4.78,13.13, +-4.79,13.15, +-4.65,12.88, +-3.37,12.65, +-2.33,12.81, +-2.3,13.02, +-2.36,13.4, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-0.38,14.23, +0,14.97, +0,14.97, +2.33,14.02, +7,12.12, +6.99,12.1, +6.85,11.21, +7.86,10.37, +8.27,10.06, +8.37,9.98, +8.37,9.98, +8.37,9.98, +8.07,9.62, +7.75,9.23, +7.75,9.23, +7.67,9.29, +6.62,10.09, +6.55,11.34, +7,12.12, +7.42,12.85, +7.8,13.5, +8.16,14.14, +8.46,14.65, +8.63,14.94, +9.78,14.01, +10.5,12.51, +10.14,12.09, +9.62,11.47, +9.53,11.36, +8.45,10.07, +8.22,9.79, +8.16,9.73, +8.23,9.81, +7.57,9.52, +7.59,9.05, +7.28,8.67, +7.12,8.49, +7.71,7.53, +9.48,7.96, +9.38,11.63, +10.77,10.31, +11.47,9.62, +12.8,7.72, +12.96,7.48, +12.9,7.11, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.51,3.44, +13.78,2.43, +13.94,2.46, +14.06,0.54, +13.55,-0.32, +12.5,-1.01, +12.05,0, +11.08,0, +11.24,-1, +11.87,-2.09, +11.87,-2.09, +12.01,-2.12, +12.66,-2.89, +12.85,-4.03, +13.37,-2.74, +14,-2.47, +14.86,-2.62, +14.4,-4.3, +14.22,-5.17, +14.33,-5.21, +14.07,-5.12, +14.07,-5.12, +13.76,-5.01, +12.92,-6.05, +12.29,-7.1, +12.48,-7.21, +12.24,-7.07, +12.23,-7.06, +11.28,-6.51, +10.76,-7.62, +9.36,-7.85, +11.09,-6.4, +10.65,-6.15, +10.68,-6.16, +10.8,-6.23, +10.72,-6.19, +10.9,-6.3, +10.58,-6.11, +10.77,-6.22, +12.09,-4.77, +12.24,-4.45, +13.15,-4.79, +14.79,-3.05, +16.85,-2.97, +18.76,-3.31, +19.15,0, +16.98,0, +13.97,0.28, +11.32,1.69, +8.94,1.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.38,-3.21, +-9.55,-2.86, +-10.51,-1.95, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.85,-1.91, +-10.26,-1.81, +-9.76,-1.72, +-9.86,-0.84, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-10.48,0.96, +-10.91,1.92, +0,0, +-3.12,0.55, +-7.27,1.28, +-10.91,1.92, +-10.93,1.93, +-8.55,1.51, +-6.82,1.2, +-10.84,2.37, +-10.94,3.59, +-6.31,2.25, +-8.45,3.07, +-11.17,4.06, +-11.31,4.17, +-10.81,5.27, +-10.66,5.79, +-10.52,5.86, +-10.44,6.03, +-10.41,6.07, +-9.73,7.07, +-9.26,7.71, +-9.34,7.84, +-9.46,7.94, +-9.62,8.15, +-8.69,9.21, +-8.13,9.65, +-8.1,9.66, +-8.37,9.98, +-8.37,9.98, +-7.13,10.88, +-6.53,11.27, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.68,11.58, +-6.01,12.61, +-4.82,13.14, +-4.61,12.66, +-4.72,12.97, +-4.75,13.05, +-4.53,12.62, +-3.21,12.72, +-2.45,13.58, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-0.88,14.08, +0,14.94, +0,14.97, +0,14.97, +7,12.12, +7,12.12, +7.73,11.48, +8.37,9.98, +8.37,9.98, +8.37,9.98, +8.37,9.98, +8.37,9.98, +8.15,9.71, +7.75,9.23, +7.75,9.23, +7.75,9.23, +7.58,9.77, +7.25,10.86, +7,12.12, +7.34,12.72, +7.73,13.38, +8.14,14.1, +8.46,14.65, +8.93,15.46, +9.29,15.4, +11,13.1, +10.86,12.94, +10.34,12.32, +9.78,11.65, +9.54,11.37, +9.09,10.83, +8.54,11.04, +7.57,11.72, +6.99,11.54, +7.44,9.74, +7.2,8.58, +7.12,8.49, +7.51,8.1, +9.48,7.96, +8.97,11.45, +10.31,10.77, +11.47,9.62, +12.32,8.4, +12.96,7.48, +12.4,7.16, +12.46,6.26, +13.15,4.79, +13.15,4.79, +13.15,4.79, +13.69,2.77, +13.78,2.43, +13.76,1.07, +12.94,-1.01, +11.89,0, +11.57,0, +11.54,-0.24, +10.91,-1.92, +11.02,-1.94, +11.39,-2.01, +11.81,-2.32, +12.44,-3.18, +13.13,-3.48, +13.78,-2.43, +14.33,-2.53, +14.74,-2.6, +14.68,-3.47, +14.47,-5.27, +14.2,-5.17, +14.33,-5.21, +14.37,-5.23, +13.63,-4.59, +13.06,-5.64, +12.82,-7.4, +13.15,-7.59, +12.54,-7.24, +12,-6.93, +11.04,-6.37, +10.39,-8.26, +9.7,-7.59, +10.72,-6.19, +10.62,-6.13, +10.72,-6.19, +10.65,-6.15, +10.44,-6.03, +11.04,-6.37, +11.28,-6.51, +11.42,-6.59, +12.2,-5.82, +12.24,-4.45, +12.31,-4.18, +12.75,-2.54, +15.38,-2.71, +17.17,-3.03, +17.14,0, +15.33,0, +12.7,1.83, +10.04,1.77, +8.4,1.48, +0.98,0.17, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-6.5,-1.2, +-9.76,-1.74, +-10.43,-1.84, +-10.91,-1.92, +-10.86,-1.91, +-10.66,-1.88, +-10.77,-1.9, +-9.92,-1.75, +-9.76,-1.72, +-9.86,-0.69, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-8.79,-0.02, +-10.48,1.13, +-10.91,1.92, +-10.88,1.92, +-9.82,1.73, +-10.91,1.92, +-10.91,1.92, +-7.32,1.29, +0,0, +-1.32,0.23, +-11.27,1.99, +-5.19,1.03, +-7.67,2.37, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.32,4.19, +-11.95,4.38, +-10.88,5.39, +-10.44,6.03, +-10.44,6.03, +-10.38,6.13, +-9.44,7.48, +-9.49,7.97, +-9.71,8.15, +-9.68,8.12, +-9.37,7.95, +-8.63,9.03, +-8.28,9.87, +-8.2,9.78, +-8.09,9.64, +-8.02,10.12, +-6.61,11.22, +-6.51,11.28, +-6.51,11.28, +-6.51,11.28, +-6.61,11.45, +-6.62,12.32, +-4.86,13.1, +-4.52,12.42, +-4.49,12.34, +-4.58,12.58, +-4.77,13.11, +-4.67,13.15, +-3.04,13.65, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-1.66,13.88, +0,14.75, +0,14.97, +2.62,13.9, +7,12.12, +7,12.12, +8.03,11.42, +8.78,10.47, +8.37,9.98, +8.37,9.98, +8.37,9.98, +8.37,9.98, +8.23,9.8, +7.75,9.23, +8.25,8.76, +8.44,8.56, +8.04,9.59, +8.23,10.12, +8.07,11.39, +7.27,12.6, +7.58,13.13, +7.97,13.8, +8.44,14.62, +8.9,15.41, +9.04,15.66, +11.2,14.08, +11.16,13.3, +10.73,12.31, +10.34,12.32, +10.17,12.12, +9.3,11.95, +7.62,13.2, +7.54,13.06, +7.97,11.44, +7.9,9.42, +7.39,8.81, +7.12,8.49, +7.19,8.57, +9.27,8.62, +8.84,11.13, +9.01,11.89, +10.77,10.31, +11.47,9.62, +12.78,7.75, +12.4,7.16, +11.4,6.58, +12.01,5.71, +12.7,4.62, +12.81,4.66, +13.2,4.07, +13.1,2.31, +13.06,0.77, +12.34,-0.38, +11.06,0, +10.75,0, +11.01,-0.82, +11.6,-3.5, +11.02,-1.94, +10.45,-2.66, +10.61,-2.16, +11.11,-2.69, +12.25,-2.54, +13.54,-2.39, +13.92,-2.45, +14.74,-2.6, +14.13,-3.63, +14.33,-5.21, +14.88,-5.41, +14.33,-5.21, +14.07,-5.12, +13.41,-4.88, +13.12,-5.51, +12.44,-7.18, +13.24,-7.65, +13.08,-7.24, +12.26,-6.7, +10.69,-5.93, +9.96,-6.99, +10.81,-6.93, +10.44,-6.03, +10.72,-6.19, +11.09,-6.4, +10.92,-6.3, +10.44,-6.03, +11,-6.35, +11.49,-6.63, +11.4,-6.58, +11.87,-6.39, +12.95,-4.71, +12.5,-4.55, +12.8,-2.83, +14.42,-2.54, +15.46,-2.73, +15.37,-0.92, +13.44,0.83, +11.76,2.07, +9.35,1.65, +8.09,1.43, +4.59,0.81, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.32,-1.29, +-9.76,-1.72, +-9.76,-1.72, +-10.12,-1.79, +-10.82,-1.91, +-10.64,-1.88, +-9.95,-1.76, +-10.5,-1.85, +-9.76,-1.72, +-9.76,-1.72, +-9.88,-0.58, +-9.91,0, +-9.91,0, +-9.91,0, +-7.05,-0.05, +-4.41,0, +0,0, +-3.68,0.28, +-10.95,1.64, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.98,1.94, +-8.28,1.46, +-3.96,0.7, +-6.59,1.16, +-11.87,2.09, +-3.96,0.7, +-10.15,1.91, +-11.48,3.61, +-11.36,4.11, +-11.37,4.14, +-11.49,4.18, +-12.13,4.42, +-11.72,4.91, +-10.61,5.87, +-10.44,6.03, +-10.47,6.07, +-10.05,7.18, +-9.61,7.97, +-9.96,8.35, +-9.6,8.05, +-9.47,7.95, +-8.54,8.47, +-7.88,9.32, +-7.78,9.27, +-7.75,9.23, +-7.84,9.44, +-7.18,10.79, +-6.56,11.25, +-6.51,11.28, +-6.51,11.28, +-6.65,11.52, +-6.93,12.11, +-5.9,12.66, +-4.51,12.4, +-4.5,12.35, +-4.6,12.63, +-4.76,13.09, +-4.79,13.15, +-3.93,13.41, +-2.53,13.76, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-1.97,13.84, +0,14.53, +0,14.97, +1.57,14.35, +7.3,12.65, +7.15,12.38, +7.45,11.99, +9,10.72, +8.89,10.59, +8.37,9.98, +8.37,9.98, +8.37,9.98, +8.37,9.98, +8.41,9.52, +9.63,8.08, +9.49,7.96, +9.5,8.86, +8.63,9.89, +8.98,10.7, +8.38,11.86, +7.61,13.17, +8.03,13.9, +8.43,14.61, +9.03,15.63, +9.33,16.16, +12.16,14.66, +11.73,13.98, +12.11,11.91, +11.61,12.14, +10.79,12.86, +10.78,12.85, +9.56,12.82, +8.05,13.94, +7.72,12.8, +7.89,10.48, +7.82,9.33, +7.59,9.05, +7.75,9.23, +7.96,9.48, +8.88,10.66, +8.67,12.13, +10.41,10.68, +11.47,9.62, +12.21,8.98, +12.6,7.28, +11.65,6.73, +11.28,6.51, +11.39,6.28, +12.1,4.75, +12.24,4.45, +12.34,3.59, +12.25,1.45, +11.43,0.41, +9.78,0, +10.35,-0.47, +11.26,-2.94, +11.6,-3.11, +11.29,-1.99, +10.06,-2.55, +10.62,-1.87, +10.91,-1.92, +11.02,-1.94, +11.39,-2.01, +13.68,-2.41, +13.89,-2.98, +12.58,-4.58, +14.29,-5.2, +14.52,-5.29, +14.07,-5.12, +13.91,-5.06, +14.07,-5.12, +13.67,-4.98, +12.78,-7.03, +13.24,-7.65, +13.39,-5.27, +11.73,-6.12, +10.72,-6.19, +10.9,-6.3, +11.7,-6.75, +10.81,-6.24, +10.56,-6.1, +10.86,-6.27, +10.32,-5.96, +10.58,-6.11, +10.54,-6.09, +11.56,-6.67, +11.37,-6.57, +12.54,-5.81, +13.04,-4.75, +12.5,-4.55, +12.89,-3.63, +13.9,-2.45, +14.74,-2.6, +14.12,-0.76, +12.64,0.7, +11.15,1.97, +9.44,1.67, +7.85,1.38, +5.91,1.04, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.3,-1.46, +-9.76,-1.72, +-9.76,-1.72, +-9.81,-1.73, +-10.46,-1.84, +-10.21,-1.8, +-9.79,-1.73, +-10.27,-1.81, +-9.83,-1.73, +-9.76,-1.72, +-9.86,-0.82, +-9.91,0, +-9.91,0, +-9.91,0, +-0.99,-0.1, +-3.24,-0.09, +0,0, +-3.65,0.28, +-11.05,0.63, +-10.98,1.35, +-10.93,1.83, +-11.15,1.97, +-11.78,2.08, +-6.32,0.9, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.85,2.21, +-11.87,3.76, +-12.16,4.42, +-12.21,4.44, +-11.83,4.31, +-12.24,4.45, +-11.52,5.2, +-10.53,6.08, +-10.85,6.26, +-10.88,6.34, +-9.89,7.14, +-9.48,7.96, +-9.55,8.01, +-9.25,7.76, +-9.2,7.78, +-8.27,8.74, +-7.75,9.23, +-7.76,9.25, +-7.93,9.46, +-8.14,9.78, +-7.17,10.86, +-6.51,11.28, +-6.51,11.28, +-6.56,11.35, +-6.86,11.89, +-6.29,12.29, +-4.83,12.43, +-4.67,12.84, +-4.67,12.83, +-4.73,13, +-4.79,13.15, +-4.79,13.15, +-3.41,13.55, +-2.46,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.13,13.83, +-0.18,14.43, +0,14.97, +1.25,14.63, +7.48,12.96, +7.48,12.96, +7.37,12.55, +8.74,10.9, +8.78,10.46, +8.37,9.98, +8.11,10.06, +7.71,9.91, +8.46,9.9, +9.05,9.33, +9.57,8.79, +9.98,8.37, +10.03,8.41, +10.11,9.43, +9.18,10.56, +9.22,11.11, +8,12.88, +7.98,13.83, +8.46,14.65, +8.95,15.51, +9.41,16.19, +11.98,14.96, +12.39,14.76, +12.88,12.58, +13.27,11.14, +12.08,12.44, +11.2,13.35, +11.19,13.34, +8.55,14.19, +8.11,14.04, +7.7,12.29, +8.29,9.88, +7.9,9.42, +8.14,9.7, +9,10.72, +8.55,10.64, +7.65,13.24, +9.99,12.39, +10.59,11.47, +11.82,10.44, +11.64,8.95, +11.84,6.84, +11,6.35, +11,6.35, +11.49,6.05, +12.1,4.75, +11.85,4.03, +11.89,1.83, +10.59,1.16, +8.92,1.2, +10.19,0.23, +11.48,-1.4, +11.87,-2.09, +11.2,-1.97, +10.58,-1.86, +10.91,-1.92, +10.91,-1.92, +11.05,-1.95, +11.53,-2.25, +11.13,-3.62, +11.33,-4.12, +11.85,-4.31, +12.63,-4.6, +13.38,-4.87, +14.07,-5.12, +13.43,-4.89, +13.8,-5.02, +13.25,-4.82, +12.43,-6.71, +12.89,-7.07, +12.84,-5.78, +11.4,-6.58, +11.17,-6.45, +11.28,-6.51, +11.11,-6.41, +10.58,-6.11, +10.53,-6.08, +10.86,-6.27, +10.33,-5.97, +10.44,-6.03, +10.75,-6.21, +11.47,-6.62, +11.7,-6.75, +12.9,-5.32, +13,-4.73, +12.7,-4.62, +13.29,-3.73, +13.9,-2.45, +13.78,-2.43, +13.74,-1.39, +13.16,0, +11.99,0.67, +9.7,1.71, +7.99,1.41, +7.13,1.26, +1.15,0.2, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-4.94,-0.87, +-9.82,-1.73, +-9.76,-1.72, +-9.76,-1.72, +-10.17,-1.79, +-10.06,-1.77, +-4.71,-0.98, +0,0, +-8.56,-1.42, +-9.87,-0.77, +-9.9,-0.28, +-9.91,0, +-9.91,0, +-9.91,0, +-5.51,0, +-1.24,0, +-5.49,0.35, +-10.41,1.45, +-10.99,1.24, +-10.97,1.41, +-10.92,1.88, +-11.66,2.06, +-10.39,1.83, +-2.6,0.44, +-8.88,1.54, +-11.87,2.09, +-11.89,2.1, +-11.87,2.09, +-11.87,2.09, +-11.89,2.1, +-12.43,2.47, +-12.33,4.19, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.12,4.68, +-10.79,5.88, +-10.61,6.12, +-11.05,6.38, +-10.07,6.79, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.22,7.77, +-8.02,8.99, +-7.75,9.23, +-7.98,9.51, +-8.35,9.95, +-8.35,9.95, +-7.69,10.47, +-6.51,11.28, +-6.49,11.24, +-6.62,11.47, +-6.75,11.69, +-6.65,11.67, +-4.97,12.59, +-4.52,12.21, +-4.48,12.32, +-4.69,12.89, +-4.79,13.15, +-4.79,13.15, +-4.61,13.21, +-2.97,13.66, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-2.43,13.81, +-0.95,14.09, +0,14.97, +2.49,14.3, +7.48,12.96, +7.48,12.96, +7.29,12.63, +7.18,11.94, +6.85,11.14, +6.61,10.8, +5.55,10.84, +5.12,11.17, +6.53,11, +6.98,10.96, +8.33,9.98, +9.52,8.84, +10.4,8.73, +10.72,9, +10.61,9.13, +9.78,11.16, +9.55,11.58, +8.3,13.59, +8.42,14.59, +8.91,15.44, +10.46,15.47, +12.25,14.59, +12.25,14.59, +13.68,12.6, +13.61,11.42, +12.82,11.22, +11.62,13.85, +11.69,13.93, +10.39,13.96, +8.68,14.38, +9.03,13.23, +8.96,11.19, +7.97,9.97, +7.93,9.45, +8.86,10.56, +7.17,12.7, +6.8,14.23, +7.61,13.97, +8.98,13.1, +9.96,12.41, +9.99,10.93, +9.93,9.1, +9.23,7.75, +10.02,7.29, +10.63,7.44, +11.49,6.05, +12.1,4.75, +11.68,2.35, +10.32,1.55, +10.54,0.19, +10.59,0.01, +11.74,-1.74, +12.16,-2.14, +12.69,-2.24, +10.23,-1.8, +10.77,-1.62, +11.7,-0.87, +11.58,-1.24, +11.55,-2.04, +10.51,-3.83, +11.17,-4.07, +12.04,-4.38, +12.37,-4.5, +12.7,-4.62, +12.47,-4.54, +12.76,-4.64, +13.04,-4.75, +13.15,-4.79, +13.75,-5.29, +13.42,-5.75, +12.71,-5.74, +11.76,-6.79, +11.28,-6.51, +11.52,-6.65, +10.8,-6.23, +10.96,-6.33, +10.44,-6.03, +10.25,-5.92, +10.08,-5.82, +10.61,-6.12, +11.14,-6.43, +11.61,-6.38, +11.81,-5.7, +13.28,-4.83, +13.61,-4.95, +12.92,-4.7, +12.88,-3.31, +12.99,-2.29, +13.78,-2.43, +13.78,-2.43, +13.23,-1.31, +12.42,0, +9.69,0.39, +8.14,1.21, +7.73,1.16, +6.16,0.73, +1.1,0.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.66,-1, +-10.27,-1.81, +-9.83,-1.73, +-9.76,-1.72, +-9.83,-1.73, +-9.81,-1.73, +-7.59,-1.34, +-7.26,-1.34, +-9.77,-1.68, +-9.9,-0.25, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +0,0, +-1.24,0, +-8.78,0.69, +-10.06,1.34, +-10.81,1.91, +-10.91,1.92, +-10.91,1.92, +-11.63,2.05, +-8.9,1.57, +-1.32,0.23, +-5.14,0.91, +-12.12,2.14, +-12.06,2.13, +-12.57,2.22, +-12.18,2.15, +-11.87,2.09, +-12.23,2.16, +-12.38,4.02, +-12.33,4.17, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-11.3,5.78, +-10.46,6.04, +-10.87,6.28, +-10.36,6.31, +-9.29,7.68, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.01,7.99, +-7.88,9.11, +-7.76,9.25, +-7.98,9.51, +-7.83,9.33, +-7.78,9.88, +-6.6,11.11, +-6.31,10.92, +-6.48,11.23, +-6.76,11.71, +-6.58,11.39, +-6.55,11.87, +-5.4,11.82, +-4.72,12.13, +-4.63,12.72, +-4.79,13.15, +-4.79,13.15, +-4.79,13.15, +-4.5,13.25, +-2.74,13.72, +-2.43,13.78, +-2.43,13.78, +-2.47,14, +-2.51,14.21, +-2.5,14.4, +-1.65,14.66, +0.85,14.69, +7.48,12.96, +7.44,12.88, +7.07,12.24, +6.55,11.74, +5.73,11.66, +4.37,12.15, +3.71,12.29, +3.03,13.16, +4,13.06, +5.23,12.84, +6.85,11.57, +8.62,10.4, +9.85,9.72, +10.7,9.02, +11.26,9.45, +10.81,10.3, +9.93,11.84, +9.81,12.86, +8.58,14.86, +9.45,15.39, +11.54,14.32, +12.2,14.54, +12.39,14.47, +14.51,12.34, +14.07,11.81, +13.21,11.08, +12.63,12.99, +12.25,14.59, +11.65,13.88, +11.41,13.59, +10.24,13.28, +9.93,13.11, +8.17,12.93, +8.42,11.8, +9,10.72, +7.82,11.15, +5.78,14.16, +6.29,14.59, +7.97,13.8, +7.67,13.28, +8.4,12.32, +7.96,11.11, +7.83,10.21, +8.37,9.98, +8.91,9.44, +10.19,8.06, +11.19,6.46, +11.58,4.56, +10.29,1.84, +11.44,0, +11.92,-1.49, +12.4,-2.19, +12.58,-2.54, +11.28,-2.81, +9.95,-1.76, +11.22,-1.22, +11.91,0, +12.03,-0.3, +11.15,-1.97, +10.22,-3.69, +9.87,-5.16, +13.02,-4.74, +13.15,-4.79, +12.24,-4.45, +11.67,-4.25, +10.93,-3.98, +12.24,-4.45, +13.15,-4.79, +13.8,-5.02, +14.07,-5.12, +12.98,-5.16, +12.37,-6.47, +11.7,-6.75, +11.28,-6.51, +10.8,-6.23, +10.23,-5.9, +10.56,-6.1, +10.75,-6.21, +10.8,-6.23, +11.28,-6.51, +11.28,-6.51, +11.72,-6.33, +12.45,-4.86, +13.27,-4.83, +13.61,-4.95, +13.61,-3.69, +12.83,-2.26, +13.1,-2.31, +13.65,-2.41, +13.89,-2.45, +12.99,-2.29, +12.15,-1.61, +10.46,0, +8.82,0, +7.95,0.2, +9.02,1.59, +7.82,1.38, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.88,-1.74, +-10.57,-1.86, +-10.29,-1.82, +-9.79,-1.73, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.82,-1.24, +-9.91,-0.2, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0.09, +-2.69,-0, +-3.92,0.69, +-9.89,1.12, +-9.82,1.71, +-10.7,1.89, +-10.91,1.92, +-7.64,1.35, +-8.18,1.44, +-2.38,0.36, +-1.72,0.12, +-9.5,1.63, +-11.99,2.11, +-12.56,2.22, +-12.32,2.17, +-12.69,2.24, +-12.1,2.13, +-12.09,2.13, +-12.71,2.79, +-12.61,3.18, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.15,4.52, +-10.98,5.54, +-10.64,6.14, +-10.73,6.23, +-9.72,7.09, +-9.25,7.73, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-8.56,8.45, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.44,9.71, +-6.31,10.64, +-6.35,10.99, +-6.59,11.42, +-6.83,11.83, +-6.83,11.83, +-6.03,11.55, +-6.14,11.48, +-4.82,12.17, +-4.59,12.62, +-4.66,12.81, +-4.79,13.15, +-4.79,13.15, +-3.79,13.44, +-2.75,13.72, +-2.44,13.84, +-2.58,14.64, +-2.6,14.74, +-2.6,14.72, +-2.47,14.59, +1.92,14.56, +7.44,12.89, +6.89,12.15, +5.88,12.05, +4.52,12.21, +3.67,12.58, +2.41,13.46, +1.52,14.3, +1.51,15.05, +2.63,15.13, +3.53,14.94, +5.32,13.84, +6.57,12.97, +7.84,11.63, +9.08,10.78, +10.82,9.97, +11.46,9.7, +10.91,11.5, +10.62,12.73, +9.85,14.24, +11.2,14.17, +11.62,13.85, +11.66,13.89, +13.19,13.19, +14.59,12.25, +14.27,11.98, +13.81,11.59, +13.11,12.91, +12.31,14.68, +12.14,14.47, +11.29,13.45, +11.51,13.72, +10.73,13.84, +8.39,14.53, +7.79,13.49, +7.54,13.07, +7.55,11.73, +6.34,14.4, +5.62,15.43, +5.61,13.95, +5.58,12.63, +6.22,10.77, +6.33,10.96, +6.65,11.52, +6.62,11.47, +6.51,11.28, +8,10.24, +9.75,8.6, +11.35,6, +12.06,3.25, +12.34,0.83, +12.61,0, +13.54,-2.06, +12.34,-2.66, +10.23,-3.72, +10.5,-2.46, +11.32,-2, +12.66,-0.48, +12.39,-0.92, +11.97,-2.64, +10.67,-4.15, +9.9,-4.68, +14.06,-2.79, +13.61,-4.95, +12.64,-4.6, +11.17,-4.07, +10.41,-3.79, +11.72,-4.26, +13.15,-4.79, +13.67,-4.98, +13.67,-4.98, +13.27,-5.2, +12.12,-7, +12.02,-6.94, +11.88,-6.86, +11.37,-6.57, +11.42,-6.59, +11.09,-6.4, +11.42,-6.59, +11.07,-6.39, +11.28,-6.51, +11.62,-6.06, +11.8,-5.65, +13.02,-4.74, +13.61,-4.95, +13.76,-5.01, +13.75,-2.97, +13.24,-2.33, +12.93,-2.28, +13.51,-2.38, +13.34,-3.61, +13.37,-3.44, +12.14,-2.14, +10.56,-0.96, +9.43,0, +9,0.89, +8.81,1.55, +9.64,1.7, +6.07,1.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.49,-1.5, +-9.98,-1.76, +-10.1,-1.78, +-10.06,-1.77, +-10.02,-1.77, +-9.95,-1.76, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.86,-0.75, +-9.91,0, +-9.91,0, +-9.91,0, +-9.91,0, +-9.87,0.65, +-9.77,1.68, +-9.83,1.73, +-10.36,1.83, +-10.06,1.77, +-10.39,1.83, +-8.49,1.5, +0,0, +0,0, +-9,0.63, +-11.94,1.6, +-11.99,1.17, +-11.96,2.08, +-12.68,2.24, +-12.8,2.26, +-12.83,2.26, +-12.68,2.24, +-12.4,2.19, +-12.83,2.26, +-12.72,2.73, +-12.38,4, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-11.13,5.52, +-10.48,6.05, +-10.86,6.27, +-10.34,6.19, +-9.45,7.46, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.06,7.94, +-7.94,9.06, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.7,9.41, +-6.91,10.26, +-6.15,10.65, +-6.52,11.3, +-6.71,11.62, +-6.61,11.45, +-6.54,11.34, +-6.48,11.3, +-5.95,11.57, +-4.75,12.11, +-4.63,12.73, +-4.79,13.15, +-4.79,13.15, +-4.79,13.15, +-3.04,13.65, +-2.43,13.8, +-2.52,14.29, +-2.6,14.74, +-2.6,14.74, +-2.51,14.43, +0.54,14.56, +5.14,13.19, +4.46,12.85, +3.92,12.39, +2.86,12.74, +2.38,13.48, +1.03,14.22, +0,15.04, +0,15.94, +2.06,16.1, +2.89,16.41, +4.11,15.71, +5.42,14.89, +6.9,13.8, +8.18,13.04, +10.13,11.47, +10.88,11.09, +12.07,10.44, +10.79,12.8, +11.35,13.53, +11.62,13.85, +11.62,13.85, +11.65,13.83, +13.35,12.28, +14.57,12.23, +14.67,12.06, +15.02,10.36, +13.91,12.22, +12.78,15.23, +12.18,14.51, +11.93,14.22, +11.29,13.45, +11.48,13.68, +9.56,14.94, +8.89,15.4, +8.47,14.67, +8.02,13.9, +6.71,13.72, +6.5,11.75, +6.65,11.52, +6.67,11.56, +6.64,11.5, +6.64,11.5, +6.43,11.08, +7.18,10.99, +6.63,11.49, +6.51,11.28, +8.34,9.94, +9.93,8.33, +11.6,5.83, +12.66,2.89, +13,0.28, +13.48,-0.3, +12.35,-2.33, +9.56,-3.48, +10.61,-2.4, +11.99,-2.11, +12.99,-0.38, +13.17,-0.75, +13.38,-1.54, +11.78,-4.29, +10.67,-3.88, +13.19,-3.23, +14.2,-4.15, +12.76,-4.64, +11.53,-4.2, +10.41,-3.79, +11.12,-4.05, +12.7,-4.62, +12.92,-4.7, +13.15,-4.79, +12.62,-5.42, +11.76,-6.79, +11.84,-6.84, +11.76,-6.79, +11.52,-6.65, +11.38,-6.57, +11.42,-6.59, +11.65,-6.73, +11.52,-6.65, +12.24,-6.51, +13.15,-4.79, +13.15,-4.79, +13.67,-4.98, +14.59,-5.31, +14.38,-4.89, +14.88,-2.62, +13.54,-2.39, +12.99,-2.29, +13.07,-3.21, +13,-4.73, +12.24,-4.45, +12.15,-2.43, +11.01,-1.75, +10.5,0, +8.81,1.55, +9.08,1.6, +9.51,2.42, +10.83,2.15, +1.7,0.3, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.91,-1.92, +-10.38,-1.83, +-10.31,-1.82, +-10.85,-1.91, +-10.91,-1.92, +-10.48,-1.85, +-9.78,-1.73, +-9.76,-1.72, +-9.78,-1.63, +-9.89,-0.47, +-9.91,0, +-9.96,0, +-9.91,0, +-9.88,0.58, +-9.86,0.94, +-4.15,0.44, +0,0, +-5.89,1.04, +-10.84,1.91, +-10.51,1.85, +-8.49,1.5, +0,0, +-5.34,0.37, +-12.02,0.6, +-12.05,0.02, +-12.03,0.5, +-11.97,1.14, +-12.26,1.71, +-12.8,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.74,2.65, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12,4.95, +-10.76,6.07, +-10.68,6.17, +-10.65,6.15, +-10.29,6.26, +-9.64,7.2, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-8.85,8.16, +-7.79,9.19, +-7.75,9.23, +-7.75,9.23, +-7.75,9.23, +-7.45,9.45, +-6.28,10.51, +-6.51,11.28, +-6.53,11.3, +-6.59,11.41, +-6.89,11.93, +-6.53,11.32, +-6.51,11.28, +-5.86,11.63, +-4.99,12.94, +-4.79,13.15, +-4.79,13.15, +-4.79,13.15, +-4.61,13.21, +-3.21,13.59, +-2.48,14.08, +-2.6,14.74, +-2.6,14.74, +-2.6,14.72, +-1.54,14.51, +4.3,13.09, +2.61,12.81, +1.93,13.03, +1.24,13.88, +0.27,14.45, +0,14.85, +0.24,15.46, +1.32,15.84, +2.92,16.56, +3.01,17.06, +4.04,16.71, +5.92,16.26, +6.06,16.05, +8.1,14.58, +8.78,13.63, +10.39,12.11, +12.24,10.82, +11.36,12.49, +11.47,13.67, +11.62,13.85, +11.62,13.85, +11.62,13.85, +13.85,11.62, +14.3,12, +15.61,10.83, +16.5,9.52, +15.66,9.87, +13.86,13.21, +12.77,15.21, +12.25,14.59, +12.16,14.49, +12.18,14.51, +12.01,14.31, +10.81,14.71, +8.8,15.25, +7.87,13.64, +7.18,12.44, +7.13,12.35, +7.04,12.2, +7.14,12.37, +7.17,12.42, +7.15,12.39, +6.94,12.02, +7.55,10.86, +8.16,10.23, +9.04,9.93, +7.13,10.85, +8.73,9.62, +10.72,7.31, +12.16,4.42, +12.91,1.29, +13.63,0, +12.44,-1.37, +10.72,-3.11, +11.69,-2.3, +12.86,-1.91, +14.6,0, +14.81,0, +13.18,-0.74, +11.78,-2.41, +11.79,-2.79, +13.37,-2.36, +14.12,-4.45, +13.67,-4.98, +12.11,-4.41, +10.75,-3.91, +10.72,-3.9, +11.78,-4.29, +12.24,-4.45, +12.33,-4.49, +11.33,-5.27, +10.72,-6.19, +11.28,-6.51, +11.49,-6.63, +11.64,-6.72, +11.28,-6.51, +11.38,-6.57, +11.38,-6.57, +11.88,-6.86, +13.22,-6.17, +14.07,-5.12, +13.76,-5.01, +14.64,-5.33, +14.86,-5.41, +14.98,-5.45, +15.7,-2.77, +13.9,-2.45, +13.1,-2.31, +12.47,-2.2, +12.65,-4.31, +12.89,-4.69, +12.79,-2.94, +12.35,-2.18, +10.84,-0.62, +9.64,1.7, +9.37,3.18, +9.32,3.39, +10.95,2.64, +9.89,1.74, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.88,-1.92, +-10.74,-1.89, +-10.77,-1.9, +-10.91,-1.92, +-10.91,-1.92, +-10.84,-1.91, +-10.05,-1.77, +-9.77,-1.7, +-9.89,-0.52, +-9.91,-0.02, +-10.13,0, +-10.23,0, +-9.88,0.52, +-4.21,0.57, +-3.85,0.54, +-3.66,0.65, +-3.25,0.57, +-6.53,1.15, +-10.5,1.85, +-10.89,1.92, +-10.91,1.92, +-11.34,1.81, +-11.95,1.5, +-12.04,0.42, +-12.05,0.03, +-12.2,0, +-12.83,0, +-12.79,0.78, +-12.83,2.08, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.9,2.27, +-12.8,2.4, +-12.38,4.02, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-11.55,5.35, +-10.46,6.04, +-10.76,6.21, +-10.48,6.05, +-10.18,6.42, +-9.36,7.58, +-9.23,7.75, +-9.23,7.75, +-9.19,7.79, +-8.18,8.84, +-7.76,9.25, +-7.75,9.23, +-7.75,9.23, +-7.78,9.27, +-7.5,10.28, +-6.51,11.28, +-6.51,11.28, +-6.62,11.47, +-6.99,12.1, +-6.77,11.72, +-6.6,11.42, +-6.57,11.49, +-6.57,12.35, +-5.42,12.89, +-4.83,13.14, +-4.79,13.15, +-4.79,13.15, +-4.15,13.37, +-2.84,14.23, +-2.6,14.74, +-2.6,14.74, +-2.54,14.41, +-0.92,14.42, +2.42,13.7, +2.42,13.72, +2.29,13.8, +0.33,14.3, +0.34,14.96, +1.24,14.88, +2.41,15.39, +2.84,16.08, +2.94,16.68, +3.12,17.71, +3.14,17.8, +5.27,17.24, +6.18,16.99, +7.15,16.43, +8.48,14.69, +9.95,13.63, +12.22,11.62, +12.69,11.33, +11.91,13.54, +11.62,13.85, +11.62,13.85, +11.62,13.85, +13.62,11.96, +14.53,12.19, +15.75,10.63, +16.83,9.72, +16.61,9.59, +16.01,9.78, +14.22,11.68, +13.75,13.05, +12.93,13.92, +12.25,14.59, +12.25,14.59, +11.05,14.57, +8.79,14.98, +8.27,14.33, +7.84,13.59, +7.65,13.24, +7.48,12.96, +7.52,13.02, +7.58,13.13, +7.8,13.5, +7.51,13.01, +8.59,11.2, +9,10.72, +10.47,9.27, +8.96,10.11, +8.6,9.75, +9.94,8.34, +11.44,6.17, +12.5,3.48, +13.41,1.13, +12.16,-1.53, +12.01,-2.12, +13.07,-2.3, +14.4,-0.89, +15.82,0, +14.21,0, +11.86,-0.6, +11.07,-1.46, +11.87,-2.09, +12.83,-2.26, +13.25,-3.16, +13.38,-4.87, +12.89,-4.69, +10.82,-3.94, +10.41,-3.79, +10.98,-4, +11.19,-4.07, +11.33,-4.44, +10.02,-5.48, +9.6,-5.54, +11.39,-5.77, +12.24,-7.07, +11.95,-6.9, +11.28,-6.51, +11.91,-6.88, +12.4,-7.16, +12.96,-7.48, +13.31,-6.4, +14.07,-5.12, +14.17,-5.16, +14.98,-5.45, +15.32,-5.58, +16.26,-5.92, +16.96,-5.44, +15.34,-3.16, +13.57,-2.74, +12.83,-2.26, +13.14,-4.02, +13.15,-4.79, +13.51,-3, +13.3,-2.35, +10.98,1.43, +9.39,3.11, +9.32,3.39, +9.63,3.5, +10.65,3.24, +11.87,2.09, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.15,-1.79, +-9.76,-1.72, +-9.9,-0.41, +-9.91,0, +-9.91,0, +-9.94,0.04, +-7.67,0.67, +0,0, +-6.49,1.11, +-9.76,1.72, +-9.76,1.72, +-9.74,1.67, +-10.17,1.79, +-10.84,1.91, +-10.91,1.92, +-11.35,1.54, +-11.99,1.15, +-12,0.98, +-12.05,0.13, +-12.68,0, +-13.02,0, +-13.02,0.18, +-12.9,1.67, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-13.32,2.35, +-13.21,2.33, +-12.49,3.58, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.09,4.8, +-10.67,5.94, +-10.44,6.03, +-10.51,6.07, +-10.55,6.09, +-10.02,6.65, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.11,7.88, +-8.3,9.35, +-7.82,9.32, +-7.75,9.23, +-7.75,9.23, +-8.02,9.97, +-7.31,10.75, +-7.17,10.84, +-6.51,11.28, +-6.78,11.74, +-6.97,12.08, +-6.99,12.1, +-6.98,12.1, +-7,12.12, +-7,12.12, +-6.15,12.55, +-4.92,13.1, +-4.79,13.15, +-4.8,13.2, +-4.25,14.25, +-2.85,14.69, +-2.6,14.74, +-2.6,14.74, +-0.88,14.64, +2.48,14.04, +2.47,14, +2.54,14.41, +2.59,14.72, +2.6,14.74, +2.6,14.74, +2.71,15.34, +2.83,16.06, +2.96,16.78, +3.12,17.72, +3.14,17.83, +4.33,18.15, +6.51,17.87, +6.34,17.41, +7.6,16.07, +9.43,13.99, +11.53,12.79, +13.76,11.55, +13.8,11.67, +11.72,13.76, +11.62,13.85, +12.45,13.26, +14.48,12.15, +14.59,12.25, +16.26,10.06, +17.34,10.01, +17.27,9.97, +17.19,9.92, +16.47,10.34, +15.08,12.1, +14.64,12.29, +13.19,13.66, +12.58,14.26, +11.7,13.94, +11.37,14.04, +9.8,14.48, +8.72,14.38, +8.15,14.12, +7.93,13.73, +7.97,13.8, +8.06,13.96, +8.28,14.34, +8.27,13.89, +9.61,11.92, +9.54,11.37, +10.79,10.16, +11.47,9.62, +10.31,9.75, +9.17,9.17, +10.41,7.75, +11.83,5.34, +12.8,2.77, +12.94,-0.97, +13.25,-2.34, +14.05,-1.22, +15.21,0, +15.38,0, +11.22,-0.67, +10.5,0, +11.06,-0.27, +11.55,-1.53, +11.87,-2.09, +12.4,-2.19, +12.24,-4.45, +11.84,-4.56, +10.02,-5.38, +9.52,-4.66, +10.28,-4.08, +10.44,-4.31, +9.96,-5.41, +8.84,-5.6, +10.01,-5.78, +11.52,-6.65, +11.91,-6.88, +11.56,-6.67, +11.91,-6.88, +12.26,-7.08, +13.06,-7.54, +13.49,-6.64, +14.47,-5.27, +14.83,-5.4, +14.86,-5.41, +14.98,-5.45, +15.79,-5.75, +17.17,-6.25, +16.52,-4.1, +15.85,-3.22, +14.89,-4.18, +14.46,-5.26, +14.83,-5.4, +14.47,-5.27, +14.55,-3.32, +13.9,-1.97, +10.8,2.23, +9.62,3.27, +9.32,3.39, +9.72,3.74, +10.39,3.78, +12.16,2.41, +6.09,1.07, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.82,-1.91, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.84,-1.91, +-10.1,-1.78, +-9.85,-1.15, +-9.98,-0.09, +-9.91,0, +-9.91,0, +-2.97,0, +0,0, +-1.95,0.34, +-7.32,1.29, +-9.76,1.72, +-9.76,1.72, +0,0, +-2.18,0.38, +-10.91,1.92, +-10.97,1.93, +-11.63,1.99, +-11.96,1.47, +-11.99,1.17, +-12.04,0.35, +-12.27,0, +-12.95,0, +-13.02,0.18, +-12.85,2.1, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.95,2.28, +-13.47,2.38, +-12.7,2.82, +-12.3,4.26, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-11.15,5.41, +-10.44,6.03, +-10.47,6.05, +-10.52,6.07, +-10.08,6.58, +-9.29,7.67, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-8.75,8.37, +-7.77,9.26, +-7.75,9.23, +-7.75,9.23, +-8.25,9.83, +-8.37,9.98, +-8.11,10.17, +-7.19,10.94, +-7,11.99, +-7,12.12, +-7,12.12, +-7,12.12, +-7,12.12, +-7,12.12, +-7,12.12, +-5.01,13.06, +-4.79,13.15, +-4.94,13.59, +-5.11,14.04, +-3.33,14.57, +-2.6,14.74, +-2.6,14.74, +-1.56,14.74, +2.6,14.74, +2.6,14.74, +2.6,14.74, +2.64,14.96, +2.81,14.97, +3.77,14.63, +4.24,15.13, +4.49,15.49, +3.65,16.6, +3.14,17.8, +3.19,18.1, +3.77,18.66, +6.52,17.9, +6.41,17.6, +7.12,16.57, +9.33,15.31, +11.62,13.85, +13.54,11.95, +13.92,11.68, +13.86,12, +13.11,12.64, +14.11,12.52, +14.59,12.25, +14.59,12.25, +15.19,11.49, +17.34,10.01, +17.34,10.01, +17.91,9.99, +17.66,10.19, +17.27,10.44, +15.61,12.94, +15.21,12.76, +14.17,12.7, +12.27,13.92, +11.62,13.85, +11.62,13.85, +10.85,13.85, +9.81,13.98, +8.78,14.11, +8.54,14.39, +8.46,14.65, +8.65,14.98, +9.41,14.24, +10.57,12.6, +10.26,12.23, +11.76,10.07, +11.56,9.7, +11.83,9.12, +10.89,9.51, +10.72,8.39, +11.4,6.25, +12.81,3.92, +13.78,0, +14.48,0, +14.54,0, +14.97,0, +14.97,0, +12.07,-0.69, +10.64,0, +11.08,0, +11.86,-0.78, +11.9,-1.79, +11.6,-2.04, +11.48,-3.09, +10.88,-5.07, +8.86,-6.59, +7.76,-6.14, +8.92,-5.15, +9.6,-5.54, +9.37,-5.41, +7.06,-6.22, +8.17,-5.39, +10.59,-5.23, +11.76,-6.79, +12.21,-5.82, +12.74,-5.95, +12.8,-7.06, +13.73,-7.47, +15.25,-6.36, +15.13,-5.51, +15.34,-5.58, +15.62,-5.68, +15.91,-5.79, +17.02,-5.21, +18.38,-5.15, +17.91,-4.36, +16.44,-2.9, +15.8,-4.25, +16.05,-5.84, +16.05,-5.84, +14.86,-5.41, +15.45,-3.69, +15.01,-2.65, +10.01,3.35, +9.04,4.67, +8.77,4.57, +8.85,4.79, +10.26,3.73, +11.2,3.33, +11.08,1.95, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.89,-1.74, +-10.17,-1.79, +-10.84,-1.91, +-10.91,-1.92, +-10.91,-1.92, +-10.91,-1.92, +-10.48,-1.85, +-9.8,-1.63, +-9.9,-0.64, +-10.56,0, +-9.94,0, +-9.91,0, +-8.67,0.39, +-7.33,1.23, +-7.59,1.34, +-9.76,1.72, +-10.1,1.78, +-10.14,1.67, +-7.21,1.27, +-8.18,1.44, +-10.91,1.92, +-11.2,1.97, +-11.95,1.47, +-11.89,1.93, +-11.88,2.04, +-12.07,1.27, +-12.83,0, +-13.02,0, +-13.02,0.09, +-12.92,1.46, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.87,2.27, +-13.3,2.35, +-12.76,2.56, +-12.07,4.36, +-11.87,4.32, +-12.03,4.38, +-12.24,4.45, +-10.84,5.66, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.41,6.07, +-9.4,7.53, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.17,7.82, +-8.14,8.87, +-7.76,9.25, +-8.15,9.72, +-8.37,9.98, +-8.37,9.98, +-8.37,9.98, +-8.48,10.32, +-7.57,11.75, +-7,12.12, +-7,12.12, +-7,12.12, +-7,12.12, +-7,12.12, +-6.73,12.27, +-4.87,13.12, +-4.79,13.15, +-5.02,13.78, +-5.08,14.08, +-3.53,14.52, +-2.6,14.74, +-2.6,14.74, +-1.13,14.86, +2.55,14.82, +2.66,15.08, +2.73,15.46, +3.28,15.57, +5.41,14.99, +5.44,14.96, +5.45,14.98, +5.45,14.98, +5.73,15.73, +5.39,17.16, +4.81,17.45, +4.97,18.25, +5.96,18.08, +6.48,17.8, +6.18,16.99, +8.64,15.86, +11.17,14.18, +12.99,12.96, +14.53,12.2, +14.59,12.25, +14.59,12.25, +14.59,12.25, +14.59,12.25, +14.82,12.43, +15.41,12.33, +17.14,10.3, +17.73,9.89, +19.51,7.71, +18.37,10.25, +18.41,10.63, +17.73,11.36, +15.85,12.98, +15.08,12.66, +14.49,12.54, +13.25,13.22, +12.15,13.96, +11.84,14.11, +11.55,13.76, +11.15,13.29, +11,13.1, +10.86,13.72, +11.35,14.02, +11.3,13.96, +11.16,13.3, +11.6,11.92, +12.31,10.33, +12.2,10.15, +13.34,7.98, +12.63,8.53, +11.83,9.36, +11.14,8.93, +11.3,7.14, +12.77,3.14, +13.07,1.29, +14.97,0, +14.97,0, +14.69,0, +12.27,0, +11.89,0, +11.81,0, +11.64,0, +11.69,0, +11.24,-1.4, +11.43,-2.81, +10.78,-4.71, +8.8,-6.67, +7.95,-6.11, +8.87,-5.12, +9.6,-5.54, +9.6,-5.54, +7.73,-6.21, +7.99,-5.24, +10.14,-3.69, +11.43,-4.76, +13.15,-4.79, +13.76,-5.01, +14.18,-5.16, +15.47,-5.63, +17.18,-7.94, +16.91,-6.15, +16.28,-5.92, +16.05,-5.84, +17.86,-4.97, +18.11,-4.13, +18.47,-3.79, +18.16,-3.2, +17.31,-3.05, +17.09,-5.57, +18.97,-6.9, +16.92,-6.16, +16.23,-5.91, +15.28,-5.56, +15.39,-3.92, +9.74,3.46, +8.16,4.71, +8.27,5.24, +8.31,4.8, +9.56,4.09, +10.78,2.88, +9.38,1.65, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.76,-1.72, +-9.76,-1.72, +-10.59,-1.87, +-10.5,-1.85, +-10.23,-1.8, +-10.53,-1.86, +-10.7,-1.89, +-10.24,-0.98, +-10.35,-0.02, +-10.69,0, +-10.47,0, +-9.96,0, +-9.91,0.17, +-9.78,1.59, +-9.76,1.72, +-9.81,1.73, +-10.59,1.87, +-10.91,1.92, +-10.91,1.92, +-10.8,1.74, +0,0, +-2.97,0.52, +-10.55,1.86, +-11.87,2.09, +-12.23,1.51, +-12.76,0.52, +-13.02,0, +-13.02,0, +-13.02,0, +-12.95,1.06, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-13.04,2.3, +-12.8,2.4, +-12.14,4.2, +-11.46,4.17, +-11.53,4.2, +-11.69,4.25, +-11.3,4.82, +-10.47,5.96, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.02,6.67, +-9.35,7.6, +-9.23,7.75, +-9.23,7.75, +-9.23,7.75, +-9.21,7.91, +-8.98,8.63, +-8.43,9.89, +-8.37,9.98, +-8.37,9.98, +-8.37,9.98, +-8.69,10.36, +-8.76,10.91, +-7.86,11.55, +-7,12.12, +-7,12.12, +-7,12.12, +-7,12.12, +-6.93,12.16, +-5.01,13.06, +-4.79,13.15, +-4.94,13.57, +-5.12,14.07, +-4.8,14.17, +-2.66,14.73, +-2.6,14.76, +-1.95,15.04, +0.73,15.83, +2.77,15.7, +2.77,15.7, +2.77,15.7, +3.5,15.52, +5.45,14.98, +5.45,14.98, +5.46,15, +5.75,15.8, +5.97,16.39, +6.18,16.99, +6.25,17.17, +6.52,17.9, +6.52,17.9, +6.35,17.31, +8.81,15.77, +11.05,14.47, +13.16,13.57, +14.59,12.25, +14.59,12.25, +14.59,12.25, +14.59,12.25, +14.59,12.25, +15.02,13.11, +15.34,12.87, +15.83,12.18, +17.26,10.27, +19.5,7.59, +19.94,7.96, +19.42,10.18, +19.15,11.06, +18.79,10.92, +16.94,12.27, +15.7,12.76, +14.96,12.55, +14.58,12.34, +13.8,12.91, +12.06,13.58, +11.62,13.85, +11.62,13.85, +11.64,13.87, +11.92,14.2, +12,14.3, +11.75,14, +12.93,12.06, +12.95,10.87, +12.66,10.02, +13.8,7.97, +13.8,7.97, +13.14,8.58, +11.62,9.75, +11.08,9.25, +10.68,7.41, +10.42,3.59, +13,0.71, +14.97,0, +14.91,-0.65, +14.41,-0.84, +12.92,0, +12.21,0, +12.42,0, +12.05,0, +11.92,-0.23, +11.79,-2.38, +11.21,-4.36, +10.08,-5.48, +9.09,-5.25, +9.6,-5.54, +9.95,-4.79, +9.8,-5.1, +9.2,-5.59, +9.75,-4.52, +12.01,-2.12, +11.9,-4.33, +12.7,-4.62, +13.61,-4.95, +14.22,-5.17, +16.57,-7.33, +20.3,-10.71, +16.7,-6.08, +16.44,-5.98, +18.7,-6.81, +18.97,-6.9, +19.11,-5.31, +19.44,-3.43, +19.08,-3.36, +17.96,-3.17, +17.38,-6.33, +18.42,-6.71, +17.38,-6.33, +17.28,-6.29, +16.03,-5.83, +15.47,-5.21, +9.91,3.18, +7.83,5.19, +6.96,5.84, +7.93,4.58, +8.29,4.45, +10.25,2.71, +11.07,1.95, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.76,-1.72, +-9.76,-1.72, +-9.88,-1.74, +-9.82,-1.73, +-9.76,-1.72, +-10.53,-1.86, +-10.94,-1.69, +-10.52,-0.26, +-10.82,0, +-11.02,0, +-2.7,0, +-7.8,0, +-9.95,0.4, +-9.98,1.6, +-9.85,1.74, +-10.43,1.84, +-10.88,1.92, +-10.91,1.92, +-10.91,1.92, +-8.49,1.5, +0,0, +0,0, +-9.34,1.65, +-12.28,1.8, +-12.84,1.5, +-13.01,0.26, +-13.02,0, +-13.02,0, +-13.02,0, +-12.94,1.16, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.53,3.38, +-11.48,4.18, +-11.33,4.12, +-11.33,4.12, +-11.25,4.41, +-10.48,5.95, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-9.64,7.2, +-9.23,7.75, +-9.38,7.87, +-9.46,7.93, +-9.83,8.25, +-9.84,8.44, +-8.6,9.78, +-8.37,9.98, +-8.37,9.98, +-8.39,9.99, +-8.85,10.55, +-9,10.72, +-8.56,11.06, +-7.05,12.09, +-7,12.12, +-7,12.12, +-7,12.12, +-7,12.12, +-5.85,12.68, +-4.79,13.15, +-4.84,13.31, +-5.1,14.02, +-5.09,14.08, +-3.84,14.44, +-2.66,15.1, +-2.03,15.07, +0,15.94, +1.33,15.84, +2.73,15.7, +2.77,15.7, +2.77,15.7, +3.94,15.4, +5.15,15.07, +5.45,14.98, +5.75,15.79, +5.87,16.12, +6.15,16.9, +6.18,16.99, +6.37,17.49, +6.52,17.9, +6.62,17.86, +9.1,16.32, +10.21,15.71, +12.25,14.59, +14.92,12.66, +15,12.58, +14.72,12.35, +14.59,12.25, +14.61,12.26, +14.33,13.84, +14.21,14.14, +15.69,13.17, +16.6,11.3, +18.46,9.55, +20.12,7.94, +20.65,7.98, +19.84,11.39, +19.63,11.33, +19.05,11, +17.62,11.9, +16.11,12.76, +15.28,12.82, +15.06,12.64, +14.54,12.2, +13.76,12.42, +12.98,13.12, +12.46,13.95, +12.25,14.59, +12.25,14.59, +12.56,14.29, +13.96,12.03, +13.59,11.41, +13.73,9.69, +13.88,8.01, +13.97,8.07, +13.81,8.03, +12.93,9.34, +11.79,9.89, +11,9.23, +10.58,6.71, +10.39,2.76, +13.19,0.68, +15.21,0, +15.45,0, +14.66,-0.37, +13.24,0, +12.89,0, +12.42,0, +12.05,0, +11.95,-1.16, +11.75,-3.16, +11.11,-4.58, +10.06,-4.54, +11.05,-4.71, +11.45,-4.51, +10.66,-4.39, +9.87,-4.96, +10.98,-5.43, +11.63,-3.3, +12.11,-4.41, +12.81,-4.66, +13.76,-5.01, +16.18,-6.77, +20.68,-11.94, +20.84,-11.03, +20.64,-7.51, +18.36,-6.68, +19.47,-7.09, +19.83,-7.22, +20.27,-5.16, +19.96,-3.52, +19.24,-3.39, +18.86,-4.19, +19.42,-7.07, +18.71,-6.81, +19.1,-6.95, +20.28,-7.38, +16.8,-6.12, +17.26,-6.28, +11.45,0.92, +7.68,5.5, +7.2,5.72, +7.5,4.76, +8.23,3.82, +9.45,2.5, +9.19,1.62, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-10.53,-1.86, +-10.99,-1.26, +-6.9,0, +0,0, +-10.86,0, +-4.92,0, +-1.24,0, +0,0, +-6.68,1.18, +-10.44,1.84, +-10.86,1.92, +-10.91,1.92, +-10.91,1.92, +-10.98,1.94, +-11.51,2.03, +-11.79,2.08, +-11.87,2.09, +-12.28,2.16, +-12.83,2.26, +-12.91,1.58, +-13.02,0.13, +-13.02,0, +-13.02,0, +-13.02,0, +-12.99,0.62, +-12.95,1.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.85,2.27, +-12.83,2.26, +-12.62,3.11, +-11.51,4.19, +-11.37,4.14, +-11.33,4.12, +-11.24,4.34, +-10.45,6, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.15,6.48, +-9.39,7.54, +-9.5,7.97, +-9.93,8.34, +-9.98,8.37, +-9.98,8.37, +-9.6,8.78, +-9.23,9.15, +-8.37,9.98, +-8.37,9.98, +-8.7,10.37, +-8.75,10.42, +-8.95,10.72, +-7.46,11.78, +-7,12.12, +-7,12.12, +-7,12.12, +-6.95,12.15, +-5.49,12.85, +-4.79,13.15, +-4.8,13.17, +-5.01,13.77, +-5.12,14.07, +-4.94,14.12, +-3.56,14.51, +-1.88,15.22, +-0.52,15.91, +0,15.94, +0.09,15.85, +0.99,15.87, +2.58,15.73, +2.77,15.7, +3.68,15.46, +4.75,15.18, +5.61,15.41, +5.78,15.89, +6.01,16.52, +6.18,16.99, +6.21,17.07, +6.32,17.35, +7.92,17.23, +9.53,16.5, +9.57,16.47, +12.33,14.69, +14.22,13.93, +15.34,12.87, +15.3,12.83, +14.59,12.25, +14.76,12.39, +13.81,14.84, +13.56,15.97, +14.7,15.11, +16.23,13.38, +17.89,10.93, +19.11,9.81, +21.04,7.72, +20.89,9.35, +19.86,11.47, +19.81,11.44, +19.53,11.27, +18.84,11.25, +17.66,12.07, +15.96,13.33, +15.59,13.08, +15.23,12.78, +14.97,12.56, +14.66,12.35, +13.77,13.23, +14.13,12.72, +14.46,12.47, +14.49,12.16, +14.09,11.82, +14.93,8.92, +14.65,8.46, +14.65,8.46, +14.65,8.46, +14.59,8.42, +13.89,8.82, +12.39,9.76, +12.07,7.3, +11.42,5.13, +13.01,2.08, +14.96,1.06, +16,0.71, +16.08,0, +14.97,0, +13.51,0, +12.33,0, +12.29,0, +12.06,-1.52, +11.98,-2.11, +11.33,-4.12, +11.12,-4.05, +12.39,-4.51, +13.86,-5.31, +11.58,-5.88, +10.52,-4.82, +11.14,-6.43, +12.58,-5.56, +13.02,-4.74, +13.31,-4.2, +15.77,-6.2, +16.69,-6.08, +18.94,-9.53, +18.43,-7.42, +19.75,-7.19, +22.33,-8.13, +20.78,-7.56, +20.67,-7.52, +21.51,-5.43, +20.4,-3.6, +19,-3.35, +18.54,-4.59, +19.73,-7.18, +20.12,-7.32, +19.34,-7.04, +20.06,-7.3, +17.04,-4.93, +17.48,-5.63, +11.33,1.49, +8.45,5.21, +8.47,4.17, +7.92,3.59, +8.58,3.12, +8.83,2.01, +7.5,0.89, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-10.5,-1.45, +-11.07,-0.3, +-9.81,0, +-7.02,0, +-10.98,0, +-11.08,0, +-1.39,0, +-2.13,0.13, +-7.77,1.34, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.94,1.93, +-11.51,2.03, +-11.87,2.09, +-11.95,2.11, +-12.06,2.13, +-12.35,2.18, +-12.83,2.26, +-12.97,0.91, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0.08, +-12.9,1.71, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-13.29,2.34, +-12.83,2.26, +-12.66,2.98, +-12.15,4.33, +-11.69,4.25, +-11.33,4.12, +-11.3,4.18, +-10.91,5.07, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.05,6.62, +-9.23,7.75, +-9.6,8.06, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.91,8.44, +-9.26,9.12, +-8.54,9.83, +-8.38,9.99, +-8.39,10, +-8.56,10.2, +-8.34,10.54, +-7.13,11.92, +-7,12.12, +-7,12.12, +-7,12.12, +-6.17,12.54, +-4.87,13.12, +-4.81,13.22, +-5.06,13.91, +-5.12,14.07, +-5.12,14.07, +-4.41,14.29, +-2.56,14.88, +-2.73,15.7, +-0.78,15.89, +0,15.94, +0,15.94, +0.16,15.93, +2.18,15.76, +2.77,15.7, +3.39,15.61, +5.51,15.62, +5.78,15.89, +5.91,16.24, +6.18,16.99, +6.18,16.99, +6.39,16.9, +8,16.31, +9.28,16.08, +9.51,16.48, +11.7,15.23, +13.04,15.18, +14.75,13.48, +15.34,12.87, +14.87,12.48, +14.87,12.48, +14.93,13.59, +13.55,16.15, +14.1,16.81, +15.96,14.53, +16.38,13.42, +18.25,11.03, +19.99,9.17, +21.5,7.9, +21.58,8.58, +20.81,11.13, +20.18,11.65, +19.86,11.47, +19.56,11.3, +18.66,11.59, +16.92,13.15, +16.08,13.49, +15.73,13.2, +15.53,13.03, +15.13,12.7, +15.11,12.68, +15.21,12.76, +14.7,12.34, +14.66,11.99, +15.81,9.25, +15.18,8.76, +15.04,8.68, +15.08,8.71, +15.06,8.69, +14.72,8.5, +14.14,8.45, +13.24,7.65, +12.31,6.43, +13.16,4.06, +14.34,2.5, +16.11,1.59, +16.55,0, +16.22,0, +14.97,0, +13.27,0, +12.66,0, +12.5,-1.24, +12.14,-2.14, +11.85,-2.62, +12.24,-4.45, +15.94,-2.73, +15.89,-5.78, +13.22,-6.08, +11.88,-6.86, +12.4,-7.16, +13.29,-7.17, +14.41,-5.24, +14.52,-5.29, +12.55,-6.58, +14.51,-5.28, +14.22,-5.93, +17.53,-10.12, +18.05,-8.95, +19.78,-8.37, +18.81,-7.72, +19.58,-7.12, +20.09,-5.83, +20.08,-3.54, +17.66,-3.11, +17.8,-3.14, +18.02,-6.56, +18.21,-6.63, +20.58,-7.49, +20.9,-7.61, +14.61,-1.4, +12.26,1.37, +12.41,-1.27, +9.49,3.89, +8.23,4.28, +8.66,2.9, +8.95,1.91, +8.61,1.49, +8.06,-0.22, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.67,-1.86, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-9.76,-1.72, +-10.63,-0.66, +-11.08,0, +-11.08,0, +-5.42,0, +-1.17,0, +-2.75,-0.02, +-1.23,0, +-5.42,0.45, +-10.86,1.83, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.15,1.97, +-11.8,2.08, +-11.87,2.09, +-12.19,2.15, +-12.78,2.25, +-12.83,2.26, +-12.85,2.06, +-13,0.45, +-13.44,0, +-13.15,0, +-13.02,0, +-13.02,0, +-13.02,0.09, +-12.86,2.04, +-13.18,2.32, +-13.07,2.3, +-12.83,2.26, +-12.85,2.27, +-13.06,2.3, +-12.83,2.26, +-12.82,2.29, +-12.33,4.18, +-11.63,4.23, +-11.33,4.12, +-11.33,4.12, +-10.93,5, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.22,6.36, +-9.35,7.6, +-9.46,7.88, +-9.96,8.35, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.93,8.42, +-9.6,8.78, +-8.56,9.8, +-8.37,9.98, +-8.37,9.98, +-8.37,9.98, +-7.58,11.22, +-7,12.12, +-7,12.12, +-7,12.12, +-6.97,12.14, +-5.08,13.03, +-4.92,13.52, +-5.12,14.07, +-5.12,14.07, +-5.12,14.07, +-5.09,14.08, +-3.03,15.06, +-2.8,15.89, +-2.73,15.7, +-1.2,15.85, +-0.08,15.94, +0,15.94, +0.17,15.93, +2.48,15.85, +2.89,16.42, +3.87,16.41, +5.69,15.92, +5.84,16.05, +6.13,16.85, +6.18,16.99, +6.18,16.99, +6.18,16.99, +7.81,16.25, +9.24,16.09, +9.83,16.29, +11.98,15.62, +13.19,15.04, +15.41,13.58, +15.33,12.71, +15.36,12.63, +15.65,13.25, +14.57,15.38, +14.19,16.91, +14.36,16.59, +15.88,15.11, +17.9,12.58, +19.11,11.03, +21.06,8.84, +22.47,8.18, +22.45,8.22, +21.71,9.89, +20.86,11.62, +20.48,11.82, +20.14,11.63, +19.79,11.43, +18.65,12.03, +17.34,13.11, +16.34,13.71, +16.14,13.54, +15.69,13.17, +15.34,12.87, +15.3,12.84, +15.89,11.14, +16.14,9.32, +15.66,9.04, +16.07,8.19, +16.19,7.9, +15.66,9.04, +15.57,8.99, +15.01,8.67, +14.27,8.24, +13.44,7.56, +14.13,5.34, +14.28,4.72, +15.26,5.13, +16.52,1.53, +16.48,0, +16.1,0, +15.7,0, +15.13,0, +14.85,0, +14.05,-0.97, +13.42,-2.37, +14.52,-2.81, +16.48,-3.68, +15.98,-5.08, +13.49,-6.64, +12.36,-7.14, +12.84,-7.41, +13.32,-7.69, +14.91,-6.16, +15.9,-6.16, +13.37,-7.26, +12.61,-6.23, +12.48,-7.21, +13.83,-7.98, +15.71,-9.07, +15.68,-9.05, +15.9,-9.18, +19.12,-6.96, +18.36,-4.17, +18.83,-3.32, +16.88,-2.98, +17.71,-3.59, +18.22,-6.18, +18,-6.55, +20.28,-7.38, +21.35,-7.4, +14.81,-1.79, +13.51,0, +12.81,-2.37, +10.94,1.61, +8.84,2.67, +8.78,1.51, +9.8,0.23, +10.21,-1.04, +7.12,-0.84, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.72,-2.19, +-9.93,-1.75, +-9.76,-1.72, +-9.76,-1.72, +-10.47,-1.85, +-10.9,-0.95, +-11.08,0, +-11.08,0, +-8.48,-0.12, +-2.77,0, +0,0, +0,0, +-7.83,1.1, +-10.91,1.92, +-6.24,1.1, +-9.82,1.73, +-10.99,1.94, +-11.7,2.06, +-11.87,2.09, +-11.87,2.09, +-12.01,2.12, +-12.83,2.26, +-12.83,2.26, +-12.84,2.18, +-12.95,1.11, +-13.87,0, +-13.59,0, +-13.02,0, +-13.02,0, +-13.02,0, +-12.99,0.61, +-13.29,2.06, +-13.7,2.42, +-13.13,2.31, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.45,3.75, +-11.84,4.28, +-11.33,4.12, +-11.33,4.12, +-11.22,4.39, +-10.74,5.44, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.29,6.26, +-10.27,6.98, +-9.98,8.3, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-8.83,9.55, +-8.37,9.98, +-8.37,9.98, +-8.39,9.99, +-8.3,10.42, +-7.14,11.93, +-7,12.12, +-7,12.12, +-7,12.12, +-5.68,12.77, +-5.13,13.19, +-5.1,14.02, +-5.12,14.07, +-5.12,14.07, +-5.12,14.07, +-4.29,14.8, +-2.93,16.63, +-2.8,15.89, +-2.77,15.72, +-1.28,15.85, +0,15.94, +0,15.97, +0.23,16.7, +2.68,16.69, +2.94,16.65, +4,16.39, +5.82,16.11, +6.16,16.94, +6.18,16.99, +6.12,16.8, +6.03,16.57, +6.27,16.96, +7.89,16.34, +9.49,16.43, +10.6,16.21, +12.71,15.53, +14.68,14.46, +15.39,12.98, +15.56,13.06, +15.97,13.4, +15.79,14.3, +14.26,16.7, +14.12,16.83, +14.7,16.26, +16.93,13.99, +18.14,12.28, +20.04,10.57, +22.33,8.13, +22.47,8.18, +22.47,8.18, +22.39,8.37, +20.97,11.43, +20.71,11.95, +20.5,11.83, +20.06,11.58, +19.75,11.45, +19.04,11.69, +17.85,12.71, +16.82,13.18, +15.73,13.2, +15.69,12.77, +17.2,10.19, +16.74,9.66, +16.11,9.3, +17.07,6.72, +17.24,6.51, +16.74,8.28, +15.87,9.16, +15.66,9.04, +14.79,8.54, +14.1,8.07, +14.93,5.43, +14.98,5.45, +15.74,5.73, +15.83,5.76, +15.47,4.15, +16.31,2.78, +16.18,2.85, +16.1,0.63, +15.94,0, +15.94,0, +15.94,0, +16.68,-0.9, +16.5,-3.94, +15.89,-5.78, +14.35,-7.19, +13.06,-7.54, +13.44,-7.76, +13.52,-7.81, +14.42,-5.67, +13.89,-7.22, +12.26,-7.08, +11.59,-6.69, +11.88,-6.86, +11.7,-6.75, +12.6,-7.28, +13.54,-7.82, +14.39,-8.31, +15.29,-5.93, +15.92,-4.06, +15.56,-2.74, +15.25,-2.36, +16.35,-3.79, +16.05,-5.84, +17.01,-6.19, +19.1,-6.95, +18.05,-5.73, +16.72,-3.45, +14.57,-2.66, +16.7,-5.44, +13.52,-0.69, +13.93,-1.74, +9.36,-1.03, +12.35,-3.41, +9.97,-3.19, +6.81,-2.48, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.75,-1.78, +-9.17,-1.62, +-10.43,-1.84, +-9.89,-1.74, +-10.15,-1.79, +-10.88,-1.66, +-11.07,-0.26, +-11.08,0, +-11.08,0, +-11.08,0, +-9.69,0, +-7.39,0, +-11.06,0.44, +-10.92,1.9, +-10.91,1.92, +0,0, +-8.35,1.47, +-11.72,2.07, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-12.3,2.17, +-12.83,2.26, +-12.83,2.26, +-12.91,1.56, +-13.29,0.68, +-13.95,0, +-13.94,0, +-13.26,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13,1.78, +-13.47,2.38, +-12.95,2.28, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.71,2.75, +-11.56,4.21, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.2,4.43, +-10.55,5.82, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.5,6.06, +-11.05,6.48, +-10.54,7.62, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.83,8.54, +-8.61,9.86, +-8.54,10.18, +-8.68,10.34, +-8.91,10.71, +-7.53,11.78, +-7,12.12, +-7,12.12, +-7,12.12, +-7,12.12, +-6.39,13.19, +-5.15,14.05, +-5.12,14.07, +-5.12,14.07, +-5.12,14.07, +-4.65,14.7, +-2.94,16.65, +-2.93,16.61, +-2.84,16.13, +-2.71,15.76, +-0.52,16.1, +0,16.79, +0,16.91, +0.85,16.85, +2.92,16.8, +3.12,17.68, +4.4,17.48, +6.18,16.99, +6.18,16.99, +5.89,16.2, +5.78,15.89, +6.02,16.54, +6.18,16.99, +7.62,16.74, +9.37,16.52, +11.03,16.41, +13.2,15.94, +15.23,14.27, +16.08,13.49, +16.08,13.49, +16.1,13.51, +14.87,16.11, +14.12,16.83, +14.12,16.83, +14.94,16.02, +16.34,14.63, +19.38,11.78, +21.5,9.11, +22.47,8.18, +22.47,8.18, +22.47,8.18, +22.38,9.28, +21.37,12.23, +20.85,12.04, +20.71,11.95, +20.71,11.95, +20.37,11.76, +20.18,11.65, +19.34,11.44, +17.81,11.73, +17.92,10.87, +18.06,10.43, +17.08,9.86, +16.79,8.93, +17.9,6.52, +17.9,6.52, +17.6,7.2, +16.8,8.66, +15.8,9.12, +15.51,8.96, +14.67,8.47, +15.79,5.98, +15.97,5.81, +16.74,6.09, +16.62,6.05, +15.05,5.48, +14.2,4.27, +15.02,2.18, +15.53,1.21, +16.06,0, +16.43,0, +16.27,-0.35, +16.91,0, +16.85,-0.65, +16.25,-4.55, +15.05,-4.8, +12.99,-7.93, +13.62,-8.24, +13.52,-7.81, +14.07,-5.12, +14.55,-6.9, +13.43,-7.75, +12.24,-7.07, +12.5,-7.21, +12.26,-7.08, +12.21,-7.05, +12.54,-7.24, +13.06,-7.54, +14.45,-6.21, +15.12,-3.27, +14.59,-1.14, +13.85,-0.3, +13.62,-2.4, +14.53,-3.87, +16.9,-6.15, +17.26,-6.28, +16.65,-5.39, +16.46,-4.05, +15.6,-3.52, +17.49,-5.62, +12.35,-2.18, +11.4,-2.92, +10.3,-3.23, +9.84,-3.63, +10.46,-4.83, +9.52,-5.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.36,-0.24, +-9.7,-1.71, +-10.62,-1.87, +-10.38,-1.83, +-10.95,-1.65, +-11.08,-0.07, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0, +-11,1.2, +-10.91,1.92, +-10.91,1.92, +-3.64,0.64, +-10.19,1.8, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-12.1,2.13, +-12.83,2.26, +-12.83,2.26, +-12.88,1.84, +-13.46,1.34, +-13.97,0.55, +-14,0, +-13.93,0, +-13.7,0, +-13.8,0, +-13.68,0, +-13.29,1.38, +-13.26,2.34, +-13.51,2.38, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.73,2.68, +-11.81,4.17, +-11.35,4.13, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-10.93,5.01, +-10.45,6.01, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.49,6.06, +-10.99,6.7, +-10.45,7.75, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.17,9.24, +-8.62,10.17, +-8.94,10.66, +-9,10.72, +-8.32,11.22, +-7.12,12.05, +-7,12.12, +-7,12.12, +-7.05,12.2, +-7.27,12.91, +-5.47,13.82, +-5.44,13.9, +-5.12,14.07, +-5.12,14.07, +-5.19,14.81, +-3.72,16.46, +-2.94,16.65, +-2.94,16.65, +-2.89,16.41, +-2.52,16.54, +-0.04,16.91, +0,16.91, +0,16.91, +1.19,17.29, +3.05,17.82, +3.14,17.8, +3.84,17.63, +5.75,17.12, +6.15,16.88, +5.81,15.95, +5.86,16.1, +6.18,16.99, +6.17,16.96, +7.91,16.44, +9.41,16.37, +11.47,16.14, +13.68,15.81, +16.15,13.79, +16.19,13.59, +16.61,13.94, +16.68,14.28, +14.98,16, +14.12,16.83, +14.12,16.83, +15.99,15.31, +18.37,13.55, +19.85,11.58, +22.04,8.96, +22.47,8.18, +22.47,8.18, +22.49,8.18, +22.71,9.19, +21.55,12.05, +21.18,12.23, +21.06,12.16, +20.9,12.07, +20.95,12.09, +20.23,11.68, +19.27,11.12, +18.42,10.64, +17.96,10.37, +17.34,10.01, +17.77,7.83, +17.93,6.52, +17.9,6.52, +17.9,6.52, +17.87,6.59, +17.22,7.65, +15.91,8.54, +15.5,8.69, +16.53,6.26, +16.74,6.09, +17.31,6.3, +17.09,6.22, +15.44,6.29, +14.52,5.33, +13.74,3.74, +14,1.92, +14.91,0.03, +15.15,-1.94, +15.94,-1.23, +16.67,0, +16.73,-1.65, +15.09,-5.04, +13.8,-6.18, +15.14,-4.66, +13.56,-7.83, +13.07,-7.54, +12.76,-4.64, +13.37,-6.04, +13.8,-7.97, +13.32,-7.69, +13.59,-7.84, +13.07,-7.54, +13.38,-7.73, +13.17,-7.61, +13.72,-6.72, +14.98,-5.45, +15.83,-2.79, +14.24,-0.4, +12.75,0, +12.15,-0.26, +12.01,-2.12, +15.47,-5.1, +15.68,-5.71, +17.04,-6.2, +14.85,-2.95, +12.3,-2.81, +10.85,-3.95, +14.26,-4.57, +10.43,-4.09, +10.73,-5.39, +9.51,-5.81, +10.1,-6.71, +6.7,-5.28, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.06,-1.95, +-10.88,-1.92, +-10.84,-1.91, +-10.91,-1.92, +-11.05,-0.58, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0.08, +-10.95,1.62, +-10.91,1.92, +-10.91,1.92, +-10.98,1.94, +-11.68,2.06, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-12.21,2.15, +-12.83,2.26, +-12.83,2.26, +-13.07,2.28, +-13.73,2.34, +-13.89,1.49, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-13.9,1.43, +-13.73,2.42, +-13.65,2.41, +-12.86,2.27, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.82,2.29, +-12.5,3.4, +-11.37,4.14, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.22,4.37, +-10.52,5.86, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.53,6.08, +-10.94,6.43, +-10.29,7.97, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.94,8.41, +-8.79,9.63, +-8.7,10.37, +-9,10.72, +-9,10.72, +-7.9,11.52, +-7,12.12, +-7,12.12, +-7,12.12, +-7.18,12.43, +-6.94,12.3, +-6.94,12.84, +-5.44,13.93, +-5.12,14.07, +-5.19,14.27, +-4.61,16.22, +-2.94,16.65, +-2.94,16.65, +-2.94,16.65, +-2.94,16.65, +-0.91,16.84, +0,16.91, +0,16.91, +0,17.88, +2.4,17.89, +3.14,17.8, +3.14,17.8, +3.49,17.72, +6,17, +5.93,16.28, +5.85,16.07, +6.15,16.88, +5.89,16.19, +5.98,16.44, +7.88,16.48, +9.65,16.71, +12.67,16.33, +14.78,16.2, +16.8,14.16, +16.83,14.12, +16.83,14.12, +16.8,14.16, +15,15.45, +14.68,16.31, +16.59,15.69, +17.59,14.76, +18.61,13.31, +20.65,10.59, +22.47,8.18, +22.47,8.18, +22.47,8.18, +22.49,8.19, +22.84,9.24, +21.56,12.45, +21.72,12.54, +21.72,12.54, +21.72,12.54, +21.02,12.14, +20,11.55, +19.06,11, +18.01,10.4, +17.53,10.12, +18.07,8.49, +18.57,6.76, +17.95,6.53, +18.29,6.66, +17.93,6.52, +17.86,6.5, +17.17,6.25, +16.62,7.02, +16.99,6.18, +16.99,6.18, +17.87,6.51, +17.18,7.74, +15.65,8.99, +14.95,6.66, +13.8,5.02, +13.51,3.49, +14.15,1.05, +14.87,-1.51, +15.51,-1.15, +15.94,0, +15.63,-0.35, +13.73,-4.27, +13.34,-6.02, +15.75,-3.76, +14.1,-6.47, +12.64,-5.59, +12.09,-4.4, +13.27,-5.22, +15.95,-6.78, +15.39,-6.34, +15.41,-6.87, +15.58,-9, +14.69,-8.48, +15.44,-8.07, +15.56,-6.18, +16.43,-5.56, +17.31,-3.05, +15.08,0, +14.41,0, +12.48,0, +11.93,-1.4, +13.19,-3.34, +13.4,-4.48, +15.14,-5.51, +12.95,-3.92, +9.74,-4.3, +10.89,-5.35, +10.33,-5.39, +10.3,-5.94, +9.31,-7.32, +8.91,-7.16, +8.95,-7.51, +9.23,-7.75, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.3,-1.29, +-10.91,-1.92, +-10.91,-1.92, +-10.94,-1.68, +-11.07,-0.24, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0.02, +-10.96,1.58, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.23,1.98, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-12.11,2.14, +-12.83,2.26, +-12.9,1.64, +-13.13,0.84, +-13.97,0.55, +-13.96,0.68, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-13.96,0.7, +-13.91,1.31, +-13.59,2.4, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.63,2.85, +-11.83,4.17, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.3,4.19, +-11.09,4.68, +-10.55,5.82, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.47,6.05, +-10.5,6.35, +-10.08,8.14, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.82,8.54, +-8.6,9.94, +-8.79,10.48, +-8.98,10.7, +-8.88,10.82, +-7.51,11.79, +-7.02,12.11, +-7,12.12, +-7,12.12, +-7,12.12, +-7.03,12.18, +-6.44,12.83, +-5.26,13.87, +-5.18,14.22, +-4.88,15.44, +-2.89,16.38, +-2.93,16.61, +-2.94,16.65, +-2.94,16.65, +-1.97,16.75, +0,16.91, +0,16.91, +0,17.58, +0.27,18.07, +3.07,17.81, +3.14,17.8, +3.14,17.8, +4.01,17.58, +6.13,16.85, +6.1,16.76, +5.87,16.12, +5.92,16.26, +6.15,16.9, +6.22,16.97, +8.5,16.94, +10.46,17.31, +13.64,16.68, +15.9,15.59, +17.5,14.68, +17.46,14.65, +16.94,14.21, +16.83,14.12, +17.35,14.56, +17.93,15.05, +18.23,15.3, +18.34,14.54, +20.06,11.12, +20.97,9.98, +22.42,8.16, +22.47,8.18, +22.47,8.18, +22.47,8.18, +21.92,9.38, +21.5,11.82, +21.72,12.54, +22.17,11.62, +21.66,12.51, +20.89,12.06, +19.86,11.47, +18.56,10.71, +18.16,10.36, +18.7,7.3, +18.81,6.85, +18.65,6.79, +18.81,6.85, +18.08,6.58, +17.9,6.52, +17.84,6.49, +17.11,6.23, +16.99,6.18, +17.25,6.28, +17.9,6.52, +17.9,6.52, +17.06,6.88, +15.77,6.47, +14.13,6.66, +13.73,5, +13.57,3.36, +14.11,0.69, +14.85,-0.6, +15.78,0, +15.94,0, +13.73,-4.23, +13.86,-5.05, +15.79,-3.66, +15.71,-4.57, +13.15,-4.79, +11.03,-4.51, +14,-5.1, +16.93,-6.16, +16.33,-5.95, +16.67,-6.07, +16.23,-6.43, +16.89,-7.37, +17.11,-6.23, +17.7,-6.44, +17.87,-5.02, +18.39,-2.84, +16.66,0, +15.96,0, +14.32,0, +14.4,-0.95, +13.81,-2.86, +12.73,-3.11, +10.85,-3.95, +9.59,-3.49, +9.21,-4.99, +9.45,-6.33, +9.38,-8.99, +11.2,-12.45, +9.61,-13.34, +8.92,-9.34, +9.55,-8.01, +9.52,-8.83, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.69,-1.71, +-10.91,-1.92, +-10.91,-1.92, +-10.98,-1.37, +-11.08,-0.02, +-11.08,0, +-11.08,0, +-6.13,0, +-4.75,0, +-9.94,0.54, +-10.95,1.64, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.2,1.97, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-12.16,2.14, +-12.88,1.81, +-12.92,1.55, +-13.35,0.13, +-13.97,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-13.93,1.03, +-13.72,2.42, +-12.95,2.28, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.81,2.35, +-12.34,4.15, +-11.61,4.23, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-10.95,4.99, +-10.69,5.54, +-10.5,5.91, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.65,6.15, +-10.75,6.7, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.16,9.23, +-8.41,10.02, +-8.72,10.39, +-9,10.72, +-8.89,10.81, +-7.44,11.84, +-7,12.12, +-7,12.12, +-7,12.12, +-7,12.12, +-6.97,12.13, +-6.37,12.5, +-4.93,13.54, +-5.14,15.08, +-2.77,15.7, +-2.78,15.79, +-2.82,15.97, +-2.86,16.24, +-2.4,16.63, +0,16.91, +0,16.91, +0,17.11, +0,18.05, +1.45,17.97, +3.14,17.8, +3.14,17.8, +3.14,17.8, +5.67,17.15, +6.18,16.99, +6.11,16.78, +6.18,16.99, +6.23,17.12, +6.38,17.54, +8.06,17.57, +10.21,17.68, +12.06,17.59, +15.16,17.28, +16.78,16.42, +17.91,15.02, +17.44,14.63, +16.87,14.15, +17.78,14.92, +18.4,15.44, +18.82,15.79, +18.49,15.51, +18.49,13.95, +19.8,11.56, +21.01,9.45, +22.21,8.08, +22.43,8.16, +22.47,8.18, +22.47,8.18, +21.92,9.37, +21.92,11.03, +23.56,8.58, +23.12,9.6, +21.84,11.67, +20.41,11.78, +19.24,11.11, +18.83,10.24, +19.44,7.08, +18.81,6.85, +18.81,6.85, +18.81,6.85, +18.63,6.78, +17.94,6.53, +17.9,6.52, +17.84,6.49, +17.47,6.36, +17.58,6.4, +17.93,6.52, +18.11,6.59, +17.9,6.52, +16.89,6.64, +14.59,8.42, +14.46,6.33, +13.75,5.01, +13.85,2.26, +14.65,0.67, +14.77,2.31, +15.52,0, +15.2,-2.31, +14.07,-5.12, +15.59,-4.11, +17.42,-3.07, +13.35,-4.86, +10.2,-5.89, +13.38,-6.14, +16.83,-6.13, +17.08,-6.22, +18.68,-6.8, +18.81,-6.85, +20.98,-7.64, +19.96,-7.26, +20.3,-7.39, +21.64,-4.97, +19.99,-3.52, +19.28,-0.52, +17.99,0, +17.11,0, +14.54,-1.86, +12.66,-3.17, +12.12,-4.41, +11.46,-4.17, +10.54,-4.1, +10.23,-5.61, +11.02,-6.67, +10.54,-7.57, +10.27,-10.69, +9.24,-9.25, +8.63,-10.92, +9.52,-9.52, +8.49,-9.68, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-2.97,-0.52, +0,0, +-1.41,-0.25, +0,0, +-5.63,-0.97, +-11.05,-1.36, +-11.08,0, +-11.08,0, +-11.08,0, +-3.69,0, +0,0, +-8.49,1.48, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.29,1.99, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-12.51,2.16, +-12.99,0.79, +-12.98,0.96, +-13.82,0.12, +-14,0, +-14,0, +-14,0, +-14.27,0, +-14.45,0, +-14,0, +-14,0, +-14,0, +-13.97,0.51, +-13.83,2.07, +-13.21,2.33, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.82,2.32, +-12.47,3.67, +-11.94,4.2, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.27,4.27, +-10.48,5.96, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.64,6.14, +-10.76,6.42, +-10.6,7.21, +-10.43,7.77, +-9.99,8.35, +-9.98,8.37, +-9.98,8.37, +-9.57,8.82, +-8.5,9.99, +-9,10.72, +-9,10.72, +-9,10.72, +-8.67,10.97, +-7.89,11.54, +-7.02,12.11, +-7,12.12, +-7,12.12, +-7,12.12, +-6.47,12.39, +-5.2,13.58, +-3.78,15.43, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-1.78,15.82, +0,16.57, +0,16.86, +0,16.91, +0,17.36, +2.12,17.88, +3.14,17.8, +3.14,17.8, +3.14,17.8, +4.89,17.35, +6.25,17.17, +6.33,17.4, +6.44,17.7, +6.57,18.04, +6.71,18.43, +8.26,18.12, +10.1,17.49, +10.92,18.25, +13.78,17.62, +15.37,17.95, +18.03,15.88, +18.12,15.21, +17.52,14.7, +18.33,15.38, +19.15,16.07, +19.25,16.15, +19.01,15.95, +18.15,15.23, +18.37,13.58, +19.56,10.55, +20.77,9.18, +22.21,8.08, +22.44,8.17, +22.47,8.18, +21.72,9.84, +21.68,10.16, +23.36,8.5, +23.56,8.58, +23.54,8.63, +22.43,9.96, +20.7,11.22, +20.25,8.46, +19.73,7.18, +18.92,6.89, +18.81,6.85, +18.81,6.85, +18.81,6.85, +18.24,6.64, +17.9,6.52, +17.9,6.52, +17.9,6.52, +17.9,6.52, +17.9,6.52, +17.68,7.05, +17.9,6.52, +17.4,6.33, +16.22,6.74, +15.31,6.42, +14.37,6.01, +14.09,4.41, +14.43,2.26, +14.64,2.96, +14.81,1.86, +15.12,-1, +14.15,-4.8, +15.68,-3.76, +17.66,-3.11, +12.26,-4.58, +10.08,-5.82, +9.56,-6.4, +16.18,-6.21, +17.06,-6.21, +16.83,-6.13, +17.42,-6.34, +19.08,-6.94, +19.37,-6.17, +21.43,-7.8, +23.26,-4.1, +21.98,-3.88, +21.53,-1.27, +20.97,-0.62, +19.82,-0.85, +17.65,-2.4, +16.02,-3.88, +14.78,-3.81, +12.64,-4.6, +13.11,-3.88, +13.62,-5.56, +14.97,-6.77, +12.8,-7.64, +11.58,-8.62, +10.31,-9.71, +9.46,-10.26, +9.24,-9.89, +8.81,-9.93, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.87,-2.09, +-11.87,-2.09, +-8.35,-1.56, +-7.12,-1.26, +-3.39,-0.6, +-2.35,0, +-11.33,-0.2, +-11.08,0, +-11.08,0, +-11.08,0, +-4.13,-0.02, +0,0, +-8.58,0.62, +-10.92,1.9, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.15,1.97, +-11.77,2.08, +-11.87,2.09, +-11.89,2.1, +-12.08,2.13, +-12.79,1.82, +-13.02,0.08, +-13.29,0, +-13.97,0, +-14,0, +-14.02,0, +-14.32,0, +-14.85,0, +-14.9,0, +-14.11,0, +-14,0, +-14,0, +-14,0, +-13.9,1.46, +-13.51,2, +-12.85,2.27, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.81,2.34, +-12.71,2.67, +-11.89,4.19, +-11.42,4.16, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.3,4.2, +-10.72,5.49, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.49,6.05, +-10.7,6.18, +-10.99,6.35, +-11.2,6.64, +-10.33,7.91, +-9.98,8.37, +-9.98,8.37, +-9.94,8.41, +-9.3,9.12, +-8.98,10.62, +-9,10.72, +-9,10.72, +-9,10.72, +-8.93,10.77, +-8.07,11.4, +-7.05,12.09, +-7,12.12, +-7,12.12, +-7,12.12, +-6.38,12.74, +-4.3,14.7, +-2.76,15.63, +-2.77,15.7, +-2.77,15.7, +-2.77,15.7, +-1.11,15.86, +0,15.94, +0,16.33, +0,16.74, +0,16.91, +2.32,17.26, +3.14,17.8, +3.14,17.8, +3.16,17.9, +4.49,17.9, +6.51,17.88, +6.52,17.9, +6.64,18.24, +6.84,18.79, +7.05,18.84, +9.59,18.01, +10.42,18.05, +10.64,18.43, +13.19,18.19, +15.12,18.02, +17.98,16.98, +18.65,15.65, +18.3,15.35, +19.04,15.98, +19.69,16.52, +19.77,16.59, +19.29,16.19, +19.12,16.04, +18.19,15.26, +18.13,13.03, +19.35,10.84, +20.94,9.04, +21.71,7.9, +22.06,8.03, +21.22,10.65, +20.87,11.64, +22.71,8.65, +23.56,8.58, +23.56,8.58, +23.54,8.57, +22.57,8.32, +21.19,7.71, +19.91,7.25, +19.55,7.11, +18.97,6.91, +18.81,6.85, +18.81,6.85, +18.81,6.85, +18.21,6.63, +17.92,6.52, +17.9,6.52, +17.9,6.52, +17.86,6.63, +17.62,7.21, +17.9,6.52, +17.87,6.51, +17.22,6.27, +16.25,6.83, +14.44,8.18, +15,5.46, +14.6,4.1, +14.27,4.78, +14.74,2.6, +14.82,-0.87, +14.66,-2.91, +14.93,-4.39, +17.8,-3.14, +15.2,-3.65, +10.54,-5.53, +11.01,-7.53, +12.34,-5.09, +14.16,-4.76, +16.18,-3.97, +17.26,-3.54, +16.51,-4.32, +16.02,-2.82, +16.93,-3.4, +16.85,-2.97, +20.44,-3.6, +22.96,-3.52, +22.06,-2.02, +21.88,-3.24, +19.66,-4.08, +18.42,-4.65, +15.02,-4.54, +17.96,-3.95, +19.01,-3.78, +18.41,-5.04, +17.31,-5.74, +14.96,-6.82, +11.47,-8.65, +9.64,-10.07, +9.41,-10.54, +10.55,-9.68, +7.83,-8.29, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.87,-2.09, +-11.87,-2.09, +-11.87,-2.09, +-11.87,-2.09, +-8.9,-1.57, +-11.99,-0.96, +-11.86,-0.02, +-11.53,0, +-11.14,0, +-11.08,0, +-6,-0.12, +-3.69,0, +-9.69,0.07, +-10.95,1.67, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.7,2.06, +-12.08,2.13, +-12.11,2.13, +-12.83,2.26, +-13.1,1.31, +-13.15,0, +-13.53,0, +-14,0, +-14.29,0, +-14.51,0, +-14.53,0, +-14.95,0, +-14.97,0, +-14.38,0, +-14,0, +-13.99,0.19, +-13.96,0.7, +-14,0.05, +-13.87,1.39, +-13.17,2.32, +-12.83,2.26, +-12.83,2.26, +-13.1,2.31, +-12.83,2.26, +-12.83,2.26, +-12.81,2.35, +-12.4,3.76, +-11.37,4.14, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.19,4.44, +-10.52,5.86, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.61,6.12, +-11.18,6.46, +-11.28,6.51, +-11.26,6.54, +-10.22,8.05, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.82,8.58, +-8.94,10.29, +-9,10.72, +-9,10.72, +-9,10.72, +-9,10.72, +-8.91,10.79, +-7.73,11.64, +-7,12.12, +-7,12.12, +-7,12.12, +-6.76,12.36, +-3.58,14.48, +-2.61,14.78, +-2.64,14.96, +-2.68,15.19, +-2.24,15.37, +-0.31,15.8, +0,15.94, +0,15.94, +0,16.26, +1.5,16.68, +2.97,16.87, +3.1,17.59, +3.14,17.8, +3.82,18.1, +6.48,17.92, +6.52,17.9, +6.6,18.13, +6.85,18.81, +6.92,19.02, +7.39,19.51, +10.5,18.18, +10.5,18.18, +10.73,18.58, +11.32,19.01, +15,17.97, +16.19,18.78, +18.8,16.42, +19,15.95, +19.55,16.4, +19.95,16.74, +19.95,16.74, +19.94,16.73, +20.18,16.93, +19.41,16.29, +17.86,14.92, +18.48,12.38, +19.26,11, +20.88,9.18, +21.54,7.89, +20.15,11.17, +20.64,11.92, +21.13,11.17, +23.31,8.49, +23.56,8.58, +23.56,8.58, +23.46,8.54, +21.99,8, +20.82,7.58, +20.02,7.29, +19.73,7.18, +19.1,6.95, +18.81,6.85, +18.81,6.85, +18.79,6.84, +18.11,6.59, +17.9,6.52, +17.9,6.52, +17.9,6.52, +17.9,6.52, +17.9,6.52, +17.9,6.52, +17.87,6.51, +17.14,6.27, +16.07,7.26, +15.61,5.68, +15.02,5.42, +14.62,5.29, +14.4,3.86, +14.82,1.73, +14.58,-2.57, +15.35,-2.98, +17.8,-3.14, +15.27,-3.26, +11.28,-6.51, +12.65,-7.3, +12.72,-4.26, +13.39,-2.96, +14.47,-2.55, +14.35,-4.51, +14.94,-4.41, +15.41,-3.38, +16.16,-2.85, +15.18,-2.68, +18.98,-3.35, +22.21,-3.92, +22.64,-3.99, +20.79,-3.67, +17.64,-3.26, +18.15,-4.2, +18.94,-2.81, +16.87,-4.88, +17.71,-4.59, +17.78,-5.66, +16.51,-5.53, +15.05,-6.89, +13.82,-8.38, +16.21,-7.84, +9.32,-10.24, +10.16,-8.98, +9.95,-9.09, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.96,-2.11, +-12.38,-2.18, +-11.87,-2.09, +-11.88,-2.05, +-12,-0.86, +-12.01,-0.82, +-12.05,-0.16, +-12.05,0, +-11.64,0, +-11.1,0, +-11.08,0, +-11.08,0, +-11.08,0.06, +-10.97,1.49, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.1,1.96, +-11.92,2.1, +-12.36,2.18, +-12.47,2.2, +-12.83,2.26, +-13.24,1.7, +-13.91,0, +-13.9,0, +-14.1,0, +-14.89,0, +-14.38,0, +-14.68,0, +-14.95,0, +-14.97,0, +-14.67,0, +-14.02,0, +-14,0.03, +-13.96,0.85, +-13.85,1.95, +-13.81,2.25, +-13.57,2.39, +-12.83,2.26, +-12.83,2.26, +-13.04,2.3, +-13.1,2.31, +-12.83,2.26, +-12.83,2.26, +-12.54,3.42, +-11.47,4.17, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-10.84,5.23, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.71,6.18, +-11.19,6.46, +-11.28,6.51, +-10.8,7.25, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.96,8.39, +-9.2,9.42, +-8.92,10.63, +-9,10.72, +-9,10.72, +-9,10.72, +-9,10.72, +-8.73,10.93, +-8.44,11.14, +-7.32,11.92, +-7.02,12.11, +-6.15,12.88, +-4.46,14.26, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-2.47,14.76, +0,15.13, +0,15.55, +0,15.94, +0,15.94, +2.22,16, +2.94,16.65, +3.02,17.12, +3.33,17.76, +6.09,17.66, +6.52,17.9, +6.54,17.98, +6.8,18.68, +6.85,18.81, +7.08,19.45, +8.05,19.36, +10.5,18.18, +10.52,18.22, +10.85,18.79, +11.12,19.26, +14.41,18.26, +15.88,18.93, +17.72,17.71, +19.73,16.56, +19.94,16.73, +19.95,16.74, +19.61,16.46, +19.86,16.67, +20.72,17.39, +20.23,16.98, +19.23,16.14, +18.28,14.69, +18.84,12, +19.32,11.1, +20.47,9.85, +19.86,11.47, +20.01,11.55, +20.66,11.93, +21.46,10.59, +23.48,8.55, +23.56,8.58, +23.54,8.57, +22.47,8.18, +21.23,7.73, +20.31,7.39, +19.73,7.18, +19.67,7.16, +19.14,6.97, +18.81,6.85, +18.81,6.85, +18.78,6.84, +18.06,6.57, +17.9,6.52, +17.9,6.52, +17.9,6.52, +17.49,7.42, +17.88,6.56, +17.9,6.52, +17.87,6.5, +17.33,6.31, +16.68,6.07, +15.91,5.79, +15.74,5.73, +14.41,5.24, +14.66,2.91, +15.04,-1.95, +16.64,-2.51, +17.8,-3.14, +14.9,-3.51, +11.55,-5.92, +13.06,-6.28, +13.15,-3.73, +13.25,-2.34, +14.6,-2.58, +14.52,-3.88, +14.45,-4.31, +14.74,-2.6, +14.1,-2.49, +13.93,-2.46, +18.16,-3.2, +19.72,-3.48, +19.4,-3.42, +20.44,-3.6, +13.98,-3.8, +14.79,-4.85, +17,-5.16, +17.6,-4.41, +16.32,-5.46, +17.3,-5.24, +16.76,-5.55, +13.72,-6.52, +12.62,-7.34, +9.06,-8.86, +9.79,-9.52, +11.75,-8.44, +10.16,-8.53, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.03,-2.12, +-12.75,-2.25, +-12.32,-2.17, +-11.9,-2.05, +-12.04,-0.28, +-9.04,-0.05, +-4,-0.02, +-12.05,0, +-12.05,0, +-11.32,0, +-11.08,0, +-11.08,0, +-11.08,0.06, +-10.95,1.64, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-11.34,2, +-12.02,2.12, +-12.51,2.21, +-12.78,2.25, +-12.96,2.29, +-13.83,1.42, +-14,0, +-14,0, +-14.26,0, +-14.66,0, +-14.19,0, +-14.56,0, +-14.97,0, +-14.97,0, +-14.9,0, +-14.13,0, +-14,0, +-14,0, +-13.92,1.25, +-13.78,2.43, +-13.43,2.37, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.68,2.72, +-11.47,4.1, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.21,4.41, +-10.49,5.92, +-10.44,6.03, +-10.44,6.03, +-10.44,6.03, +-10.46,6.04, +-10.75,6.21, +-11.24,6.49, +-10.62,7.51, +-9.98,8.37, +-10,8.39, +-10.27,8.62, +-10.07,8.45, +-9.98,8.82, +-9.58,10.18, +-9,10.72, +-9,10.72, +-9,10.72, +-9,10.72, +-9,10.72, +-8.97,10.74, +-8.9,10.8, +-7.07,11.82, +-6.54,11.32, +-4.59,14.22, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-2.56,14.75, +-0.09,14.97, +0,15.02, +0,15.4, +0.28,15.93, +2.66,15.71, +2.89,16.39, +2.95,16.73, +5.3,17.2, +6.38,17.54, +6.52,17.9, +6.68,18.35, +6.85,18.81, +6.93,19.04, +7.18,19.73, +8.52,19.13, +10.61,18.37, +10.97,19, +10.98,19.02, +11.16,19.34, +14.33,18.32, +15.54,18.52, +16.89,19.08, +19.81,17.36, +20.01,16.79, +19.95,16.74, +19.4,16.27, +19.56,16.42, +21.03,17.65, +21.35,17.91, +20.79,17.45, +19.59,16.44, +18.01,14.99, +18.82,11.78, +19.06,11, +19.47,11.24, +19.86,11.47, +20.23,11.68, +20.79,11.84, +22.38,9.03, +23.52,8.56, +23.56,8.58, +23.06,8.39, +22.19,8.08, +20.99,7.64, +19.91,7.25, +19.73,7.18, +19.73,7.18, +19.48,7.09, +18.87,6.87, +18.81,6.85, +18.61,6.77, +18.02,6.56, +17.9,6.52, +17.9,6.52, +16.63,9.27, +17.07,8.56, +18.14,6.6, +18.06,6.57, +17.88,6.51, +17.31,6.3, +16.71,6.08, +16.35,5.95, +14.75,5.37, +14.58,3.73, +15.04,0.04, +15.97,-1.51, +17.59,-1.86, +16.66,-1.94, +12.95,-4.71, +13.54,-4.93, +13.53,-2.95, +13.3,-2.35, +14.14,-2.49, +15.22,-2.68, +15.34,-2.7, +14.74,-2.6, +13.78,-2.43, +12.71,-2.24, +15.37,-2.71, +13.4,-2.36, +18.64,-2.81, +18.32,-1.77, +11.17,-4.07, +14.67,-4.9, +15.68,-5.71, +16.8,-5.67, +16.69,-5.43, +16.53,-5.55, +16.61,-5.02, +12.96,-6.46, +10.55,-7.83, +8.18,-9.31, +11.59,-8.48, +11.17,-7.87, +11.52,-5.6, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.67,-2.23, +-12.8,-2.26, +-12.8,-2.26, +-12.53,-2.18, +-12.11,-1.36, +-10.71,-0.05, +-7.43,-0.08, +-12.05,0, +-12.05,0, +-11.37,0, +-11.08,0, +-11.08,0, +-11.08,0, +-10.97,1.44, +-10.96,1.91, +-10.91,1.92, +-10.91,1.92, +-11.2,1.97, +-11.94,2.11, +-12.73,2.24, +-12.83,2.26, +-12.91,2.28, +-13.69,2.26, +-13.98,0.49, +-14,0, +-14.56,0, +-14.9,0, +-14.75,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.17,0, +-14,0, +-14,0, +-13.99,0.3, +-13.82,2.13, +-13.53,2.39, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-11.81,3.18, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.33,4.12, +-11.3,4.19, +-10.89,5.11, +-10.97,6.31, +-11.11,6.41, +-11.04,6.37, +-11.11,6.41, +-10.64,6.14, +-10.56,6.1, +-10.87,6.91, +-9.98,8.37, +-9.98,8.37, +-10.49,8.8, +-10.56,8.86, +-10.17,8.53, +-10.32,9.3, +-9.25,10.5, +-9.02,10.7, +-9,10.72, +-8.98,10.7, +-8.98,10.7, +-9,10.72, +-8.86,10.83, +-7.65,11.04, +-5.92,12.47, +-5.09,14.08, +-2.64,14.73, +-2.6,14.74, +-2.6,14.74, +-2.6,14.74, +-0.37,14.95, +0,14.97, +0,14.97, +0.03,15.57, +2.77,15.7, +2.81,15.91, +2.94,16.65, +5.16,16.9, +6.25,17.17, +6.52,17.9, +6.7,18.4, +6.85,18.81, +6.96,19.13, +7.18,19.73, +9.08,18.86, +10.74,18.6, +10.98,19.02, +10.98,19.02, +11.05,19.14, +12.35,19.19, +15.6,18.59, +16.28,19.4, +19.23,18.29, +20.31,17.04, +19.99,16.77, +19.53,16.39, +19.79,16.6, +21.05,17.66, +21.44,17.99, +21.53,18.06, +20.35,17.08, +19.25,16.15, +18.49,14.65, +19.16,12.07, +19.27,11.12, +19.58,11.31, +19.88,11.48, +20.57,11.87, +20.79,11.79, +22.68,8.99, +23.56,8.58, +23.56,8.58, +22.71,8.27, +21.29,7.75, +20.62,7.5, +19.99,7.28, +19.73,7.18, +19.73,7.18, +19.55,7.11, +18.92,6.89, +18.81,6.85, +18.74,6.82, +18.52,6.74, +18.37,6.69, +17.98,8.27, +17.34,10.01, +18.07,8.48, +18.63,6.97, +18.21,6.63, +17.65,6.42, +16.99,6.18, +17.26,6.28, +15.9,6.31, +14.83,5.05, +15.42,2.72, +15.4,-1.97, +16.59,-0.77, +16.73,-1.16, +13.26,-4.39, +13.23,-4.49, +13.94,-2.46, +14.02,-2.47, +14.62,-2.58, +15.48,-2.73, +14.95,-2.64, +15.56,-2.74, +15.22,-2.68, +13.65,-2.41, +12.11,-2.14, +10.98,-1.94, +11.51,-2.03, +6.92,0.18, +7.25,-0.83, +8.77,-1.68, +12.74,-3.92, +15.94,-5.8, +17.45,-4.55, +18.38,-3.59, +16.74,-4.56, +14.61,-5.42, +11.04,-6.37, +9.86,-7.82, +9.7,-8.4, +10.34,-7.85, +8.56,-6.62, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.83,-2.26, +-12.83,-2.26, +-12.83,-2.26, +-12.81,-2.21, +-12.18,-0.38, +-12.05,0, +-12.1,0, +-12.16,0, +-12.03,0, +-11.49,0, +-11.08,0, +-11.08,0, +-11.08,0.02, +-11.28,0.67, +-11.22,1.98, +-10.91,1.92, +-10.94,1.93, +-11.51,2.03, +-11.87,2.09, +-12.4,2.19, +-12.91,2.28, +-13.52,2.38, +-13.89,1.43, +-13.99,0.1, +-14.25,0, +-14.85,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.92,0, +-14.12,0, +-14,0, +-14,0, +-14,0, +-13.9,1.36, +-13.53,2.39, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.37,3.25, +-11.33,4.1, +-11.33,4.12, +-11.33,4.12, +-11.51,4.19, +-11.37,4.14, +-11.33,4.12, +-11.32,4.19, +-11.52,5.59, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-10.88,6.28, +-10.44,6.03, +-10.64,6.43, +-9.84,8.23, +-9.98,8.37, +-10.07,8.45, +-10.52,8.82, +-10.67,8.95, +-10.72,9, +-10.6,9.13, +-9.81,9.95, +-9.18,10.56, +-8.91,10.62, +-8.64,10.29, +-9,10.72, +-8.97,10.69, +-8.48,10.1, +-7.19,10.95, +-4.89,13.44, +-2.9,14.26, +-2.58,14.64, +-2.55,14.47, +-2.58,14.64, +-1.28,14.65, +0,14.97, +0,14.97, +0.19,14.99, +2.55,15.66, +2.77,15.73, +2.92,16.53, +4.27,16.53, +6.18,16.99, +6.48,17.81, +6.55,18, +6.85,18.81, +6.91,19, +7.18,19.73, +9.73,18.55, +10.91,18.9, +10.98,19.02, +10.98,19.02, +11.04,19.13, +13.64,18.73, +15.6,18.59, +16.35,19.49, +17.78,18.97, +20.7,17.37, +20.4,17.12, +19.56,16.42, +19.7,16.53, +21.14,17.74, +21.76,18.26, +22.15,18.59, +21.55,18.08, +20.55,17.24, +19.61,16.46, +18.39,15.2, +18.52,13.4, +19.21,11.65, +19.86,11.47, +20.09,11.6, +20.71,11.95, +21.37,10.87, +23.54,8.57, +23.56,8.58, +23.12,8.41, +21.92,7.98, +20.74,7.55, +20.64,7.51, +20.12,7.32, +19.8,7.21, +19.73,7.18, +19.68,7.16, +19.16,6.97, +18.81,6.85, +18.81,6.85, +18.81,6.85, +18.27,8.08, +17.34,10.01, +17.34,10.01, +17.73,9.23, +18.75,6.95, +18.21,6.63, +17.78,6.47, +17.9,6.52, +16.82,7.08, +15.11,5.5, +15.43,3.77, +14.5,-1.36, +15.59,-0.52, +16.02,-0.94, +13.64,-3.29, +13.7,-2.72, +13.65,-2.41, +13.25,-2.34, +15.54,-2.74, +15.96,-2.81, +15.89,-2.8, +14.74,-2.6, +13.78,-2.43, +13.36,-2.36, +12.58,-1.87, +11.54,-1.73, +12.74,-1.4, +10.65,-0.35, +6.75,1.19, +8.15,0.62, +6.82,0.7, +9.82,-1.96, +15.22,-5.12, +14.83,-5.4, +14.89,-3.55, +13.25,-4.4, +13.05,-5.35, +11.87,-6.28, +10.31,-6.84, +9.04,-7.17, +10.35,-6.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.25,-2.16, +-12.59,-2.22, +-12.83,-2.26, +-12.83,-2.26, +-12.9,-1.69, +-12.27,-0.08, +-12.38,0, +-8.91,-0.15, +0,0, +-6.03,0, +-11.97,0, +-11.13,0, +-11.08,0, +-11.08,0, +-11.1,0.07, +-11.08,1.34, +-10.91,1.92, +-10.96,1.93, +-11.75,2.05, +-11.9,1.91, +-12.31,1.23, +-13.1,1.48, +-13.48,2.32, +-13.77,2.07, +-13.97,0.61, +-14.38,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.69,0, +-14.63,0, +-14.08,0, +-14,0, +-13.9,1.32, +-13.47,2.38, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.59,2.53, +-12.1,3.63, +-11.39,4.11, +-11.43,4.16, +-11.79,4.29, +-11.47,4.17, +-11.33,4.12, +-11.35,4.13, +-12.02,4.8, +-11.49,6.11, +-11.28,6.51, +-11.11,6.41, +-10.5,6.06, +-10.52,6.07, +-10.33,6.3, +-9.81,7.5, +-9.96,8.36, +-9.98,8.37, +-10.22,8.57, +-10.7,8.98, +-10.72,9, +-10.72,9, +-10.7,9.02, +-10.34,9.41, +-8.91,9.81, +-8.54,9.98, +-8.95,10.67, +-8.67,10.34, +-8.37,9.98, +-7.35,10.88, +-4.76,13.16, +-3.04,13.63, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-0.97,13.92, +0,14.49, +0,14.97, +0.12,14.97, +2.69,15.23, +2.77,15.7, +2.87,16.3, +3.62,16.53, +6.17,16.96, +6.28,17.25, +6.52,17.93, +6.72,18.47, +6.86,18.84, +7.06,19.41, +9.27,18.77, +10.68,18.5, +10.98,19.02, +10.99,19.04, +11.39,19.72, +12.48,19.37, +15.56,18.54, +16.13,19.23, +17.61,19.13, +20.64,17.32, +20.71,17.38, +20.05,16.83, +20.14,16.9, +21.04,17.65, +21.69,18.2, +22.19,18.62, +21.84,18.33, +21.27,17.85, +20.22,16.97, +19.16,16.27, +18.58,15.59, +18.02,15.12, +19.03,13.25, +20.49,11.83, +20.71,11.95, +20.74,11.9, +22.17,10.24, +23.56,8.58, +23.4,8.52, +22.03,8.02, +21.19,7.71, +20.64,7.51, +20.64,7.51, +20.53,7.47, +19.99,7.18, +19.92,6.66, +19.73,7.18, +19.32,7.03, +18.81,6.85, +18.81,6.85, +18.79,6.9, +17.53,9.63, +17.34,10.01, +17.34,10.01, +17.98,8.71, +18.71,6.81, +18.37,6.69, +18.68,6.8, +18.26,7.12, +15.92,7.22, +15.47,5.21, +15.62,2.13, +16.69,2.57, +16.66,0.08, +12.87,-2.52, +13.78,-2.43, +13.46,-2.37, +14.06,-1.16, +14.42,-2.54, +15.7,-2.77, +15.48,-2.73, +15.89,-2.8, +15.94,-2.81, +14.47,-2.55, +13.77,-0.99, +15.13,0, +15,0, +10.67,0.44, +7.34,0.84, +7.04,0.89, +7.14,0.17, +6.86,0.35, +6.46,0.41, +9.7,-2.59, +11.16,-3.96, +10.26,-3.69, +9.76,-4.66, +10.17,-5.1, +10.98,-5.62, +10.56,-6.1, +7.24,-4.18, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.83,-2.26, +-12.8,-2.26, +-12.83,-2.26, +-12.84,-2.16, +-12.95,-0.6, +-12.48,0, +-12.25,0, +-5.29,-0.07, +0,0, +0,0, +-11.88,0, +-11.19,0, +-11.08,0, +-11.08,0, +-11.08,0, +-11.45,0.91, +-10.98,1.82, +-11.18,1.97, +-11.98,1.11, +-12.03,1.33, +-12.71,0.23, +-13.03,1.6, +-13.43,1, +-13.83,1.48, +-14,0.03, +-14.21,0, +-14.94,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.6,0, +-14,0, +-13.94,0.9, +-13.63,2.4, +-12.94,2.28, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.82,2.29, +-12.55,2.89, +-12.29,4.09, +-12.18,4.43, +-12.19,4.44, +-11.77,4.28, +-11.51,4.19, +-12.06,4.39, +-11.81,5.41, +-11.3,6.47, +-11.24,6.49, +-11.19,6.46, +-11.18,6.46, +-11.19,6.46, +-10.44,7.59, +-9.96,8.36, +-9.98,8.37, +-9.99,8.39, +-10.66,8.94, +-10.72,9, +-10.72,9, +-10.72,9, +-10.61,8.9, +-9.96,8.43, +-9.25,9.13, +-8.82,10.52, +-8.61,10.26, +-8.37,9.98, +-7.48,10.77, +-4.79,13.15, +-3.67,13.47, +-2.5,13.77, +-2.43,13.78, +-2.43,13.78, +-1.95,13.84, +0,14, +0,14.56, +0,14.97, +2.43,14.79, +2.73,15.48, +2.78,15.77, +2.94,16.65, +5.07,16.51, +6.18,16.99, +6.35,17.45, +6.52,17.9, +6.84,18.79, +6.94,19.07, +8.88,18.95, +10.68,18.49, +10.98,19.02, +11.14,19.3, +11.47,19.86, +14.04,18.85, +15.59,18.58, +16.15,19.25, +17.32,19.3, +20.6,17.29, +20.8,17.46, +20.47,17.17, +20.53,17.22, +20.98,17.6, +21.44,17.99, +21.87,18.35, +22.16,18.6, +21.86,18.34, +21.12,17.72, +17.69,19.64, +17.4,18.8, +18.22,16.68, +18.32,15.37, +19.29,14.02, +20.68,12, +20.71,11.95, +21.19,11.63, +23.06,9.73, +23.08,8.4, +22.08,8.03, +21.37,7.78, +20.72,7.54, +20.64,7.51, +20.64,7.51, +21.08,5.99, +21.54,3.8, +20.25,6.48, +19.73,7.18, +19.65,7.15, +19.26,7.01, +19.02,6.92, +18.13,8.56, +17.49,10.1, +17.48,10.09, +17.82,10, +18.77,7.1, +18.81,6.85, +19.16,6.97, +19.05,6.93, +16.84,8.03, +16.05,5.84, +15.74,4.55, +16.21,2.45, +16.11,2.2, +13.53,-0.64, +13.57,-2.08, +13.29,-1.69, +13.14,-1.46, +13.51,-2.38, +14.26,-2.51, +16.37,-2.89, +17.58,-3.1, +16.82,-2.97, +15.7,-2.77, +16.15,-0.69, +13.31,-0.85, +13.02,-1.39, +9.89,-1.05, +6.44,0.81, +6.29,0.67, +7.85,0, +9.07,-0.78, +7.35,0.46, +6.45,0.56, +2.42,0.43, +2.23,-0.02, +9.37,-3.41, +11.02,-4.49, +10.44,-4.91, +11.35,-5.78, +13.14,-5.19, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.83,-2.26, +-12.84,-2.17, +-12.83,-2.26, +-12.95,-1.21, +-12.83,-0.03, +-12.33,0, +-12.05,0, +-12.18,0, +-2.74,0, +-4.02,0, +-12.05,0, +-11.9,0, +-11.52,0, +-11.12,0, +-11.08,0, +-11.51,0.02, +-11.08,1.01, +-11.42,1.96, +-11.96,1.28, +-12.11,0.4, +-13,0, +-13.01,0.43, +-13.54,0.05, +-13.54,0.39, +-14,0, +-14,0, +-14.75,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.75,0, +-14.13,0, +-13.95,0.82, +-13.82,2.16, +-13.4,2.36, +-13.15,2.32, +-13.28,2.34, +-13.14,2.32, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.82,2.29, +-12.35,4.12, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.04,4.38, +-12.21,4.44, +-12.18,4.59, +-11.65,5.76, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-10.97,7, +-10.02,8.32, +-9.99,8.35, +-10,8.39, +-10.49,8.8, +-10.72,9, +-10.72,9, +-10.72,9, +-10.59,8.89, +-10,8.39, +-9.7,8.67, +-8.84,10.23, +-8.57,10.21, +-8.37,9.98, +-7.58,10.68, +-4.79,13.15, +-4.76,13.16, +-2.93,13.66, +-2.43,13.78, +-2.43,13.78, +-1.85,13.84, +0,14, +0,14, +0.17,14.92, +2.6,14.74, +2.72,15.42, +2.77,15.7, +2.9,16.46, +5.12,16.28, +6.15,16.89, +6.28,17.25, +6.52,17.9, +6.71,18.45, +6.85,18.81, +9.43,18.6, +10.56,18.29, +10.98,19.02, +11.04,19.13, +11.89,19.59, +14.85,17.7, +15.38,18.33, +15.81,18.85, +16.31,19.43, +18.9,17.77, +20.57,17.26, +20.7,17.37, +20.7,17.37, +20.7,17.37, +20.8,17.46, +21.04,17.65, +21.42,17.98, +21.7,18.21, +21.31,17.88, +19.34,18.66, +16.77,19.99, +16.72,19.05, +17.55,17.28, +18.43,15.54, +18.86,14.66, +20.24,12.65, +20.97,12.11, +21.85,12.3, +23,9.23, +22.1,8.04, +21.55,7.84, +20.94,7.62, +20.64,7.51, +20.73,7.55, +20.8,6.99, +21.62,3.9, +21.25,4.78, +20.59,6.48, +19.83,7.22, +19.73,7.18, +19.73,7.18, +19.73,7.18, +18.4,10.02, +18.18,10.5, +18.18,10.5, +19.06,8.54, +19.38,7.05, +19.7,7.17, +19.67,7.16, +18.44,7.88, +16.64,7.79, +16.73,5.5, +16.54,3.35, +16.56,2.92, +13.59,1.82, +14.04,-0.91, +14.04,-0.91, +14.19,-0.57, +13.57,-1.16, +14.74,-2.6, +15.22,-2.68, +16.32,-2.88, +16.2,-2.86, +16.22,-2.86, +15.91,-2.25, +16.29,-1.39, +14.33,-2.53, +8.91,-0.67, +5.85,0.29, +6.57,-0.28, +6.44,-0.85, +6.4,0.05, +8.68,-0.19, +15.8,0.43, +7.96,0.52, +2.68,0.9, +7.81,-2.59, +9.58,-3.14, +12.71,-3.34, +11.01,-5.03, +10.94,-3.36, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.98,-0.87, +-12.97,-1.03, +-13.01,-0.4, +-13.02,-0.03, +-12.87,0, +-12.34,0, +-10.67,-0.05, +-4.52,0, +-12.12,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.01,0, +-11.58,0, +-11.1,0, +-11.63,0.26, +-11.9,0.7, +-11.93,0.78, +-12.04,0.23, +-12.34,0, +-13,0, +-13.02,0, +-13.13,0, +-13.75,0, +-13.95,0, +-14,0, +-14.21,0, +-14.94,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.33,0, +-14,0, +-13.98,0.42, +-13.51,1.82, +-13.18,2.29, +-13.74,2.42, +-13.71,2.42, +-13.14,2.32, +-12.97,2.29, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.75,2.63, +-12.31,4.24, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-11.89,5.26, +-11.37,6.34, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.09,6.81, +-10.52,7.64, +-10.13,8.24, +-10.47,8.78, +-10.72,9, +-10.72,9, +-10.72,9, +-10.55,8.85, +-9.98,8.37, +-9.98,8.37, +-9.67,8.74, +-8.51,9.89, +-8.37,9.98, +-6.84,11.34, +-4.79,13.15, +-4.79,13.15, +-4.44,13.26, +-2.43,13.78, +-2.43,13.78, +-2.43,13.78, +-0.31,13.99, +0,14, +0,14.22, +2.53,14.73, +2.6,14.77, +2.76,15.63, +2.79,15.81, +4.85,16.09, +5.91,16.23, +6.18,16.97, +6.37,17.51, +6.53,17.94, +6.83,18.76, +8.52,18.49, +10.5,18.18, +10.89,18.85, +10.98,19.02, +12.13,19.22, +14.74,17.57, +15.13,18.04, +15.38,18.33, +15.81,18.85, +17.74,18.05, +20,16.78, +20.43,17.15, +20.65,17.33, +20.23,16.98, +20.2,16.95, +20.6,17.28, +21.12,17.72, +21.19,18.42, +21.42,17.97, +20.86,17.81, +17.15,20.44, +16.74,19.95, +16.21,19.32, +16.48,18.81, +18.12,16.42, +18.5,15.77, +20.88,14.79, +22.73,13.12, +22.46,11.92, +21.99,8.89, +21.59,7.86, +21.37,7.78, +20.75,7.55, +21.29,7.75, +21.41,7.74, +21.6,6.17, +21.7,4.18, +21.31,5.09, +20.63,7.46, +20.41,7.43, +19.75,7.19, +19.73,7.18, +18.72,9.36, +18.18,10.5, +18.18,10.5, +18.4,10.06, +19.73,7.18, +19.73,7.18, +19.73,7.18, +19.73,7.18, +17.71,8.65, +17.96,6.96, +17.72,4.18, +16.69,2.94, +14.02,2.47, +14.48,0, +14.85,0, +14.48,0, +13.61,-0.75, +13.57,-2.06, +14.77,-2.23, +15.83,-2.79, +15.83,-2.79, +14.63,-4.09, +13.33,-4.11, +13.93,-2.71, +12.41,-1.99, +8.4,-0.52, +5.85,-0.15, +5.17,-0.63, +4.16,-2.12, +4.96,-2.69, +5.85,-1.19, +8.26,0.67, +17.12,3.44, +4.38,1.53, +4.5,-0.38, +10.2,-2.35, +10.28,-3.45, +11.61,-3.55, +11.68,-3.54, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.02,0, +-13.27,0, +-13.07,0, +-13,0, +-13,0, +-9.67,0, +-1.34,0, +0,0, +-12.2,-0.02, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12,0, +-11.6,0, +-11.61,0.57, +-12.04,0.34, +-12.04,0.37, +-12.05,0, +-12.76,0, +-13.02,0, +-13.02,0, +-13.05,0, +-13.28,0, +-13.7,0, +-14,0, +-14.19,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.12,0, +-14,0, +-14,0, +-13.94,0.27, +-13.55,2.36, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.4,2.36, +-13.13,2.31, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.83,2.26, +-12.49,3.61, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-11.74,5.6, +-11.33,6.41, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.27,6.53, +-11.02,6.92, +-10.79,7.51, +-10.82,8.68, +-10.72,9, +-10.72,9, +-10.54,8.85, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.5,8.89, +-8.4,9.95, +-7.97,10.33, +-4.79,13.15, +-4.79,13.15, +-4.79,13.15, +-3.41,13.54, +-2.43,13.78, +-2.43,13.78, +-0.43,13.97, +0,14, +0.16,13.99, +2.42,14.47, +2.6,14.74, +2.68,15.17, +2.94,15.66, +5.35,15.61, +5.79,15.91, +6.15,16.9, +6.27,17.23, +6.52,17.9, +6.73,18.49, +9.55,18.03, +10.5,18.18, +10.92,18.92, +10.98,19.02, +13.73,18.21, +14.74,17.57, +14.99,17.87, +15.24,18.17, +15.38,18.33, +15.64,18.63, +17.46,17.92, +19.13,17.2, +19.77,16.98, +19.95,16.74, +19.62,17.05, +19.75,16.97, +18.66,19.31, +18.13,21.32, +19.73,19.73, +21.26,18.16, +19.07,19.15, +17.01,20.27, +16.54,19.71, +16.64,19.83, +16.6,19.79, +18.98,19.59, +21.01,18.48, +22.52,16.5, +24.13,13.93, +21.9,11.94, +22.28,8.11, +21.58,7.85, +21.53,7.84, +21.39,7.79, +21.55,7.84, +21.55,7.84, +22.08,5.92, +22.05,4.34, +20.91,7.4, +20.64,7.51, +20.56,7.48, +20.34,7.4, +19.76,7.75, +18.21,10.44, +18.18,10.5, +18.18,10.5, +18.79,9.27, +20.08,7.36, +20.46,7.45, +20.05,7.3, +19.73,7.18, +19.04,6.93, +17.72,6.45, +16.73,5.07, +15.22,2.68, +14.52,1.75, +15.52,0, +14,0, +12.83,-0.65, +13.52,-0.67, +14.94,-0.32, +15.56,-1.14, +16.09,-2.49, +14.47,-2.55, +12.64,-4.6, +13.46,-2.37, +12.55,-2.04, +7.55,0, +6.52,-0.42, +4.21,-4.01, +3.26,-5.05, +3.67,-4.56, +10.74,-5.14, +15.55,-1.57, +14.18,5.41, +8.04,3.39, +3.89,1.51, +5.48,-0.9, +7.09,1.66, +8.92,-0.93, +9.42,-2.25, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.22,0, +-13.17,0, +-13.02,0, +-12.39,0, +-12.16,0, +-9.44,-0.02, +-7.75,0, +-6.8,-0.07, +-12.12,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12,0, +-11.94,0, +-12.05,0, +-12.05,0, +-12.42,0, +-12.94,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.26,0, +-13.85,0, +-14,0, +-14.02,0, +-14.56,0, +-14.9,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.38,0, +-14,0, +-14,0, +-13.99,0.06, +-13.93,1.03, +-13.82,2.15, +-13.82,2.16, +-13.78,2.43, +-13.78,2.43, +-13.66,2.41, +-12.99,2.29, +-12.9,2.27, +-12.85,2.27, +-12.83,2.26, +-12.8,2.4, +-12.45,4.07, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.2,4.55, +-12,5.02, +-11.34,6.4, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.3,6.97, +-11.34,7.95, +-10.74,8.97, +-10.59,8.89, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.89,8.47, +-8.6,9.77, +-6.99,11, +-4.84,12.71, +-4.77,13.1, +-4.75,13.05, +-3.82,13.08, +-2.35,13.33, +-2.35,13.33, +-1.01,13.72, +0,14, +0,14, +2.43,13.93, +2.58,14.63, +2.61,14.79, +3.38,15.27, +5.46,15, +5.73,15.73, +5.87,16.11, +6.18,16.99, +6.43,17.66, +6.67,18.32, +9.39,17.65, +10.48,18.16, +10.69,18.52, +10.98,19.02, +11.65,19.17, +14.43,17.81, +14.74,17.57, +15.19,18.1, +15.37,18.32, +15.37,18.32, +15.53,18.51, +16.11,19.05, +16.3,19.42, +16.46,19.1, +16.19,19.29, +16.8,19.7, +17.21,20.51, +17.88,21.31, +18.08,21.36, +18.15,21.63, +17.97,21.14, +17.83,21.25, +17.98,21.42, +18.72,22.31, +18.96,22.6, +19.05,22.71, +19.17,22.33, +21.93,18.62, +22.63,16.46, +22.99,13.27, +22.22,11.18, +21.86,8.35, +21.55,7.84, +20.69,7.53, +20.74,7.55, +21.12,7.69, +21.55,7.84, +21.9,6.6, +21.55,7.84, +21.31,7.76, +21.07,7.67, +20.69,7.53, +20.64,7.51, +19.91,9.07, +18.64,10.76, +18.24,10.53, +18.18,10.5, +18.88,10.65, +19.75,9.47, +20.62,7.57, +20.19,7.35, +19.73,7.18, +19.12,6.96, +17.77,6.47, +15.93,5.45, +14.98,1.44, +16.34,0.45, +14.76,-0.43, +13.11,-0.65, +14.12,0, +14.97,0, +16.05,0, +15.77,-0.37, +14.71,-1.58, +13.31,-2.42, +11.57,-1.04, +9.62,-0.86, +8.16,-1.01, +6.72,-1.8, +5.9,-4.21, +3.89,-5.75, +3.76,-5.29, +6.2,-5.46, +5.01,-6.63, +13.52,7.75, +11.74,4.73, +10.71,3.53, +7.39,1.95, +8.07,1.89, +6.72,2.53, +7.03,2.56, +8.82,3.21, +7.46,3.3, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.46,0, +-13.22,0, +-13.02,0, +-10.04,0, +-1.43,-0.08, +-6.7,0, +-12.08,0, +-12.16,0, +-12.42,0, +-12.1,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.42,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.24,0, +-13.26,0, +-13.95,0, +-14,0, +-14.55,0, +-14.3,0, +-14.87,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.82,0, +-14,0, +-14,0, +-14,0, +-13.98,0.49, +-13.97,0.39, +-13.97,0.7, +-13.86,1.78, +-13.78,2.43, +-13.77,2.38, +-13.32,2.35, +-13.5,2.38, +-12.87,2.27, +-13.14,2.32, +-12.9,2.27, +-13.44,3.04, +-12.3,4.48, +-12.25,4.42, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.21,4.52, +-11.54,6.01, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.38,6.81, +-10.68,8.61, +-10.55,8.86, +-10,8.39, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.69,8.69, +-7.96,10.26, +-6.17,11.46, +-4.46,12.26, +-4.45,12.24, +-4.45,12.24, +-2.62,12.75, +-2.26,12.83, +-0.71,13.03, +0,13.63, +0.1,13.99, +2.19,13.82, +2.48,14.05, +2.6,14.74, +4.31,14.56, +5.45,14.98, +5.61,15.42, +5.8,15.94, +6.12,16.83, +6.31,17.33, +6.58,18.08, +9.8,17.56, +10.48,18.14, +10.79,18.69, +10.98,19.02, +11.3,19.57, +11.7,19.69, +13.1,18.95, +14.4,18.89, +15.3,18.37, +15.18,18.46, +15.3,18.37, +15.37,18.32, +14.9,19.21, +14.34,19.94, +13.93,20.75, +14.15,21.31, +14.53,21.84, +15.01,22.97, +17.3,22.04, +18.62,22.19, +18.63,22.21, +18.91,22.53, +19.21,22.89, +19.67,23.44, +19.76,23.55, +19.64,23.41, +19.51,23.25, +20.27,21.68, +22.17,18.25, +23.06,15.17, +22.54,13.01, +21.37,11.87, +20.58,10.21, +19.79,9.91, +19.85,9.26, +20.37,8.11, +21.13,7.69, +21.55,7.84, +21.55,7.84, +21.55,7.84, +21.55,7.84, +21.26,7.74, +20.82,7.58, +20.48,7.86, +19.22,10.59, +18.69,10.79, +18.18,10.5, +18.94,10.93, +19.02,10.98, +19.25,10.53, +20.13,8.66, +19.84,7.22, +19.73,7.18, +18.81,6.85, +17.06,6.21, +13.32,4.16, +15.51,2.41, +16.77,2.56, +13.67,0, +15.11,0, +15.8,0, +16.8,0, +16.36,0, +15.55,-0.32, +12.86,-0.24, +10.35,0, +9.6,-0.47, +8.64,-1.7, +7.1,-3.57, +6.22,-4.74, +4.7,-5.33, +6.95,-7.09, +3.43,-6.37, +1.97,-0.5, +8.03,7.76, +27.85,8.37, +6.24,3.61, +3.75,3.25, +4.52,3.37, +7.57,3.13, +10.87,3.09, +9.42,3.86, +10.1,3.68, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-11.47,-0.09, +-4.88,0, +-10.02,0, +-12.25,0, +-12.05,0, +-12.66,0, +-12.2,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.44,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.09,0, +-13.83,0, +-13.97,0, +-14.11,0, +-14.02,0, +-14.5,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.83,0, +-14.04,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-13.99,0.12, +-13.96,0.73, +-13.98,0.42, +-13.83,2.11, +-13.4,2.36, +-13.1,2.31, +-13.69,2.41, +-13.29,2.34, +-13.63,2.72, +-13.45,2.85, +-12.5,3.76, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.12,4.73, +-11.47,6.14, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.57,6.68, +-11.43,6.6, +-11.28,6.51, +-11.66,6.79, +-10.94,7.84, +-10.41,8.29, +-10.18,8.43, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.96,8.39, +-7.74,9.79, +-6.51,11.28, +-5.66,11.68, +-4.45,12.24, +-4.45,12.24, +-2.9,12.66, +-2.26,12.83, +-1.39,12.91, +0,13.08, +0.03,13.61, +2.43,13.78, +2.43,13.78, +2.55,14.48, +4.48,14.26, +5.39,14.82, +5.47,15.03, +5.77,15.85, +5.97,16.41, +6.27,17.23, +6.57,18.06, +7.27,18.62, +10.3,18.25, +10.59,18.35, +10.98,19.02, +10.98,19.02, +11.21,19.42, +11.41,19.76, +11.57,19.94, +11.78,20.29, +12.2,20.58, +12.43,20.41, +12.63,20.76, +12.4,21.48, +12.48,21.62, +12.61,21.84, +12.94,22.41, +13.29,23.02, +13.67,23.67, +14.99,23.98, +18.02,22.71, +19.17,22.85, +19.39,23.11, +19.91,23.73, +20.49,24.42, +20.48,24.4, +20.08,23.93, +19.88,23.69, +19.62,23.38, +20.32,22.01, +22.5,18.15, +23.27,15.06, +22.27,12.92, +20.68,11.94, +19.43,11.32, +19.02,10.98, +19.18,10.69, +20.3,8.96, +21.51,7.83, +21.55,7.84, +22.1,8.04, +22.1,8.04, +21.84,7.95, +21.51,7.83, +21.03,7.66, +19.87,9.21, +19.02,10.98, +18.78,10.84, +19.02,10.98, +19.02,10.98, +19.02,10.98, +19.25,10.49, +20.41,7.43, +19.73,7.18, +19.61,7.14, +17.99,6.55, +14.52,5.29, +16,3.22, +15.29,2.7, +14.22,2.16, +15.59,0.79, +16.65,1.02, +17.41,0, +16.79,0, +15.94,0, +14.65,-0.41, +10.82,1.1, +10.73,-0.25, +9.35,-1.65, +8.2,-2.75, +6.8,-4.77, +6.16,-5.46, +5.12,-6.11, +2.48,-6.41, +2.03,4.18, +6.33,7.69, +17.32,7.05, +5.66,3.82, +4.33,4.62, +3.04,3.57, +4.63,4.36, +8.64,3.83, +12.33,4.49, +14.1,3.49, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13,0, +-12.38,0, +-12.05,0, +-12.35,0, +-12.59,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.22,0, +-12.92,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.05,0, +-13.05,0, +-13.87,0, +-14,0, +-14,0, +-14.09,0, +-14.94,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.79,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0.03, +-13.91,1.28, +-13.42,2.37, +-13.35,2.35, +-13.78,2.43, +-13.78,2.43, +-13.76,2.43, +-13.78,2.43, +-13.24,3.54, +-12.37,4.5, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-11.82,5.43, +-11.49,6.11, +-11.28,6.51, +-11.28,6.51, +-11.35,6.55, +-12.01,6.93, +-11.48,6.63, +-11.5,6.64, +-11.34,6.58, +-11.25,6.61, +-10.64,7.51, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-8.11,9.16, +-6.51,11.28, +-6.45,11.32, +-4.53,12.21, +-4.45,12.24, +-3.87,12.4, +-2.26,12.83, +-1.39,12.92, +0,13.02, +0.11,13.07, +2.21,13.52, +2.43,13.78, +2.45,13.89, +4.53,14.2, +5.22,14.34, +5.45,14.98, +5.72,15.71, +5.92,16.26, +6.27,17.23, +6.55,18, +6.88,18.89, +7.67,19.46, +9.85,18.52, +10.52,18.8, +10.98,19.02, +10.98,19.02, +11.27,19.53, +11.47,19.86, +11.74,20.33, +12.08,20.93, +12.47,21.6, +12.55,21.74, +12.71,22.01, +11.76,22.68, +12.48,22.75, +13.02,22.56, +13.24,22.94, +13.55,23.46, +14.05,24.34, +15.18,25.01, +16.43,25.03, +17.41,25.3, +17.97,26.37, +20.19,26.6, +21.51,25.63, +20.79,24.77, +20.4,24.31, +20.13,23.99, +20.4,23.29, +22.19,20.32, +22.07,18.38, +22.26,15.43, +20.35,14.34, +17.84,14.97, +17.69,14.27, +18.29,12.84, +18.89,11.66, +20.2,10.09, +21.31,8.36, +22.37,8.14, +22.47,8.18, +22.43,8.16, +22.01,8.01, +21.55,7.84, +20.93,7.91, +19.64,9.69, +19.02,10.98, +19.02,10.98, +19.02,10.98, +19.02,10.98, +19.61,11.32, +20.51,7.47, +19.96,7.26, +19.73,7.18, +19.04,6.93, +15.97,5.81, +15.55,4.37, +14.95,2.64, +14.06,2.48, +15.58,2.75, +17.61,3.11, +18.54,0, +17.08,0, +16.36,0, +15.8,0, +12.74,0.48, +11.06,-0.21, +10.28,-1.81, +8.31,-2.01, +7.56,-4.37, +6.48,-5.44, +5.15,-6.38, +2.34,-5.45, +4.28,3.06, +6.23,4.1, +7.48,5.3, +7.25,4.55, +11.31,5.97, +5.48,5.14, +4.87,4.94, +8.36,4.5, +8.94,4.73, +12.27,5.58, +3.51,1.57, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.02,0, +-12.96,0, +-12.91,0, +-12.97,0, +-12.81,0, +-12.58,0, +-12.3,0, +-12.07,0, +-12.05,0, +-12.07,0, +-12.27,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.75,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.67,0, +-14,0, +-14,0, +-14.05,0, +-14.81,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.85,0, +-14.02,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14.26,0, +-14.47,0, +-14,0, +-14,0, +-13.98,0.28, +-13.87,1.7, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.52,3.48, +-13.13,4.58, +-12.81,4.66, +-12.26,4.46, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-11.92,5.18, +-11.32,6.44, +-11.28,6.51, +-11.34,6.55, +-12.08,6.98, +-11.83,6.83, +-11.3,6.52, +-11.28,6.51, +-11.28,6.51, +-10.43,7.78, +-10.09,8.23, +-9.98,8.37, +-9.98,8.37, +-9.94,8.34, +-8.57,8.65, +-6.51,11.28, +-6.51,11.28, +-4.92,12.04, +-4.45,12.24, +-3.48,12.51, +-2.26,12.83, +-1.41,12.91, +0,13.02, +0.06,13.02, +2.32,13.18, +2.43,13.78, +2.46,13.93, +3.49,14.52, +5.24,14.41, +5.45,14.98, +5.66,15.55, +5.84,16.05, +6.18,16.99, +6.57,18.06, +6.82,18.73, +7.09,19.47, +7.18,19.73, +7.79,19.8, +10.21,19.41, +11.15,19.32, +11.44,19.82, +11.68,20.22, +11.97,20.73, +12.41,21.49, +12.68,21.97, +12.94,22.41, +13.02,22.56, +12.97,22.59, +13.02,22.56, +13.06,22.62, +13.43,23.26, +13.53,23.44, +14.09,24.41, +14.56,25.23, +14.97,25.92, +15.52,26.88, +16.53,28.64, +17.5,29.13, +18.85,28.21, +21.15,25.32, +20.94,24.95, +20.94,24.96, +20.97,24.99, +21.11,24.52, +21.49,21.18, +21.88,18.82, +20.91,17.55, +19.41,16.29, +18.59,15.6, +18.04,15.13, +18.19,14.15, +19.83,11.52, +20.5,10.15, +22.04,8.02, +22.47,8.18, +22.47,8.18, +22.47,8.18, +22.26,8.1, +21.62,7.87, +21.14,8, +19.21,10.59, +19.02,10.98, +19.12,11.04, +19.4,11.2, +19.86,11.47, +20.33,8.46, +19.73,7.18, +19.47,7.09, +17.65,6.42, +17.77,6.47, +15.6,5.68, +15.08,5.07, +14.98,4.03, +15.83,2.79, +17.06,2.13, +19.33,0, +18.93,0, +16.8,0, +16.52,0, +13.56,2.7, +12.2,1.25, +10.79,-1.66, +9.12,-1.13, +8.34,-3.04, +6.8,-5.7, +4.75,-7.33, +0.75,-5.33, +4.76,5.05, +6.84,3.75, +7.25,5.38, +6.65,4.34, +9.28,5.85, +8.38,5.36, +9,5.25, +6.85,5.75, +10.14,5.23, +11.29,4.13, +1.6,0.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.02,0, +-12.55,0, +-12.18,0, +-12.4,0, +-12.12,0, +-12.05,0, +-12.07,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.73,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.24,0, +-14,0, +-14,0, +-14.02,0, +-14.43,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.16,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14.14,0, +-14.75,0, +-14.04,0, +-14,0, +-14,0, +-13.93,1.1, +-13.79,2.41, +-13.89,2.45, +-13.91,2.45, +-13.78,2.43, +-13.78,2.43, +-13.75,2.58, +-13.69,2.87, +-13.13,4.49, +-12.32,4.48, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.23,4.48, +-11.63,5.82, +-11.41,6.27, +-11.42,6.6, +-12.02,6.94, +-12.1,6.99, +-11.74,6.78, +-11.28,6.51, +-11.28,6.51, +-11.23,6.6, +-10.91,7.08, +-9.98,8.37, +-9.98,8.37, +-9.98,8.37, +-8.62,8.78, +-6.65,11.19, +-6.51,11.28, +-5.95,11.55, +-4.45,12.24, +-3.86,12.4, +-2.26,12.83, +-1.81,12.88, +0,13.02, +0,13.02, +1.06,12.96, +2.43,13.78, +2.43,13.78, +2.69,14.5, +3.97,14.4, +5.44,14.94, +5.5,15.1, +5.77,15.85, +6.1,16.75, +6.43,17.67, +6.71,18.45, +6.89,18.94, +7.17,19.7, +7.18,19.73, +8.19,20.27, +9.38,20.21, +10.76,20.22, +11.81,20.57, +12.17,21.09, +12.59,21.81, +12.91,22.35, +13.02,22.56, +13.19,22.85, +13.4,23.21, +13.43,23.26, +13.51,23.4, +13.52,23.42, +13.72,23.76, +14.17,24.55, +14.6,25.29, +14.87,25.76, +15.33,26.56, +16.61,28.76, +17.01,29.46, +17.01,29.46, +19.13,27.79, +21.56,25.69, +21.6,25.74, +22.04,26.26, +22.29,26.57, +20.82,24.82, +20.29,22.12, +20.31,20.73, +19.56,19.39, +18.58,18.73, +17.93,17.61, +18.44,15.81, +18.01,14.77, +19.53,11.97, +20.31,10.69, +22.45,8.23, +22.47,8.18, +23.02,8.38, +22.56,8.21, +22.1,8.04, +21.55,7.84, +21.05,8.5, +19.84,11.45, +19.86,11.47, +19.86,11.47, +19.86,11.47, +20.13,8.89, +19.73,7.18, +18.04,7.74, +15.82,7.39, +16.85,6.13, +17.35,6.32, +15.8,5.75, +15.66,5.7, +16.99,3, +18.31,3.23, +20.17,1.54, +19.54,0, +17.49,0, +18.28,1.45, +13.32,10.63, +13.43,5.18, +10.74,0.41, +9.34,0.22, +8.55,-0.6, +6.53,-5.55, +2.61,-8.41, +-0.01,-5.98, +4.38,6.12, +6.8,4.13, +7.08,5.65, +6.79,4.57, +7.03,3.89, +7.07,3.96, +12.09,5.89, +8.65,5.39, +10.36,5.63, +13.13,5.98, +8.32,4.9, +6.58,2.39, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.78,1.27, +-12.01,1, +-12.05,0.12, +-12.08,0.09, +-12.05,0, +-12.05,0, +-12.05,0, +-12.02,0, +-11.97,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.03,0, +-11.96,0.31, +-12.02,0.63, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.8,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.11,0, +-13.96,0, +-14,0, +-14,0, +-14.36,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.25,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14.08,0, +-14.9,0, +-14.36,0, +-14.04,0, +-14.09,0, +-14.04,0.11, +-14.18,1.39, +-14.44,2.55, +-14.21,2.51, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.54,2.97, +-12.45,3.75, +-12.26,4.4, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.27,4.49, +-11.92,5.53, +-12.13,6.75, +-12.12,7, +-12.12,7, +-11.78,6.8, +-11.3,6.52, +-11.28,6.51, +-11.28,6.51, +-10.7,7.37, +-9.98,8.37, +-9.98,8.37, +-9.91,8.32, +-9,8.29, +-6.66,11.19, +-6.51,11.28, +-5.69,11.68, +-4.45,12.24, +-3.48,12.52, +-2.26,12.83, +-1.69,12.89, +0,13.02, +0,13.02, +0.26,13.01, +1.7,13.79, +2.43,13.78, +2.55,14.45, +2.73,14.71, +3.6,15.03, +4.29,15.3, +4.24,15.94, +5.13,16.51, +6.17,17.23, +5.29,18.17, +5.33,19.09, +5.32,19.47, +6.47,19.91, +6.75,19.89, +7.4,20.34, +7.58,20.82, +8.01,21.47, +8.38,21.97, +9.72,22.23, +11.83,22.23, +12.84,22.35, +13.02,22.56, +13.34,23.1, +13.53,23.44, +13.8,23.9, +14.15,24.5, +14.36,24.87, +14.7,25.46, +14.95,25.9, +15,25.98, +15.87,27.48, +16.72,28.95, +17.01,29.46, +17.01,29.46, +21.5,26.35, +21.87,26.06, +22.1,26.34, +22.45,26.76, +22.49,26.8, +21.94,26.14, +20.52,24.46, +19.65,23.42, +19.13,22.8, +18.26,21.76, +17.31,20.63, +17.4,18.69, +18.2,16.08, +18.46,14.85, +20.23,12.23, +21.34,10.68, +22.45,8.23, +23.25,8.46, +23.22,8.45, +22.65,8.24, +22.38,8.14, +21.84,7.95, +20.83,9.54, +19.86,11.47, +19.86,11.47, +19.86,11.47, +19.02,10.98, +19.34,8.01, +17.78,8.29, +15.06,5.9, +16.58,6.03, +17.35,6.32, +16.94,6.17, +16.8,6.12, +18.4,3.24, +19.72,3.48, +20.56,3.62, +19.76,2.98, +18.78,1.44, +18.99,4, +13.85,11.62, +13.51,11.34, +12.41,9.59, +9.8,5.84, +8.19,3.08, +5.88,6.23, +-1.34,-7.45, +-2.32,-8.31, +4.52,6.19, +5.97,4.48, +6.7,4.27, +8.2,5.64, +6.91,4.19, +9.33,5.08, +10.53,5.81, +10.19,5.83, +9.38,5.55, +8.37,5.31, +14.21,5.19, +8.09,3.81, +3.96,1.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.46,2.2, +-12.02,2, +-11.99,1.88, +-12.24,1.36, +-12.02,0.63, +-12.03,0.42, +-11.98,0.19, +-11.53,0, +-11.23,0, +-11.79,0, +-11.75,0, +-11.86,0, +-11.47,0, +-11.25,0.2, +-11.99,0.73, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.61,0, +-12.93,0, +-12.79,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.22,0, +-13.8,0, +-14,0, +-14,0, +-14.47,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.52,0, +-14.06,0, +-14,0, +-14,0, +-14.63,0, +-14.24,0, +-14.1,0, +-14.68,0, +-14.95,0, +-14.55,0, +-14.8,0, +-14.88,0, +-14.88,0.14, +-14.75,1.98, +-14.77,2.44, +-14.62,2.58, +-13.93,2.46, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.26,2.34, +-12.81,2.34, +-12.47,3.71, +-12.25,4.43, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-12.45,4.53, +-13.01,4.79, +-13.1,4.92, +-12.25,6.76, +-12.12,7, +-12.1,6.99, +-11.35,6.55, +-11.28,6.51, +-11.28,6.51, +-11.25,6.56, +-10.43,7.77, +-10,8.35, +-9.73,8.16, +-9.02,8.3, +-7.84,10.37, +-6.51,11.28, +-6.36,11.36, +-4.45,12.22, +-4.27,11.81, +-2.15,12.2, +-2.22,12.58, +-0.33,13, +0,13.02, +0,13.02, +0.28,13.78, +2.11,13.83, +2.46,13.97, +2.57,14.57, +2.61,14.79, +2.71,15.36, +2.78,15.74, +2.9,16.43, +3.08,17.29, +3.16,17.93, +3.32,18.8, +3.46,19.62, +3.49,19.77, +4.07,20.46, +5.77,20.14, +7.25,20.52, +7.52,20.67, +7.84,21.53, +8.13,22.34, +8.39,23.04, +10.23,22.96, +12.05,22.85, +13.08,23, +13.54,23.45, +13.98,24.22, +14.3,24.77, +14.55,25.2, +14.9,25.8, +14.97,25.92, +15.08,26.12, +16.47,28.53, +17.03,29.5, +17.05,29.53, +17.01,29.46, +20.22,27.25, +22.13,26.37, +22.51,26.82, +22.81,27.19, +23.01,27.42, +22.68,27.03, +21.23,25.3, +20.06,23.91, +19.49,23.23, +19.12,22.78, +18.04,21.5, +17.12,20.4, +16.76,19.47, +18.49,16.49, +18.76,15.2, +20.21,12.69, +21.18,11.03, +22.97,8.42, +23.56,8.58, +23.52,8.56, +22.49,8.19, +22.47,8.18, +22.03,8.44, +20.13,11.15, +19.86,11.47, +19.86,11.47, +19.55,11.29, +18.95,8.84, +18.39,7.18, +16.31,4.87, +17.99,3.7, +17.15,6.24, +17.75,6.46, +18.02,6.56, +18.79,4.27, +19.72,3.48, +20.04,3.53, +19.85,3.5, +19.72,3.48, +19.88,3.5, +14.31,12.01, +14.17,11.89, +13.83,11.6, +10.81,12.89, +7.23,6.42, +5.57,9.22, +3.11,6.67, +2.73,3.91, +4.98,6.5, +5.54,5.68, +6.42,5.16, +7.15,4.41, +6.44,3.54, +7.03,4.36, +7.97,5.17, +13.97,6.07, +7.81,5.19, +7.33,4.85, +10.26,5.32, +8.05,4.17, +7.15,2.48, +1.26,0.22, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.83,2.26, +-12.36,2.18, +-12.32,2.17, +-11.94,2.11, +-11.87,2.09, +-11.87,2.09, +-11.56,1.95, +-11.23,0.37, +-11.1,0, +-11.08,0, +-11.08,0, +-11.22,0, +-11.23,0, +-11.08,0, +-11.23,0, +-11.99,0.02, +-11.99,0.37, +-12.04,0.33, +-12.05,0, +-12.05,0, +-12.05,0, +-12.07,0, +-12.22,0, +-12.07,0, +-12.85,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.52,0, +-14,0, +-14,0, +-14.28,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.94,0, +-14.34,0, +-14,0, +-14,0, +-14.13,0, +-14.84,0, +-14.97,0, +-14.72,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.96,0.22, +-14.91,1.16, +-14.76,2.47, +-14.72,2.59, +-14.19,2.5, +-13.81,2.43, +-13.78,2.43, +-13.78,2.43, +-13.08,2.31, +-12.83,2.26, +-12.69,2.88, +-12.24,4.45, +-12.26,4.46, +-12.24,4.45, +-12.26,4.46, +-12.69,4.62, +-13.15,4.79, +-13.1,4.91, +-12.54,6.15, +-12.12,7, +-12.09,6.98, +-11.56,6.67, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-10.97,6.99, +-9.98,8.37, +-9.62,8.07, +-8.95,8.49, +-8.06,10.19, +-6.37,11.04, +-5.92,10.74, +-4.17,11.31, +-3.84,11.42, +-2.14,11.86, +-2.1,11.91, +-1.77,12.63, +-0.05,13.02, +0,13.02, +0,13.55, +1.46,13.89, +2.43,13.78, +2.48,14.07, +2.56,14.52, +2.61,14.81, +2.72,15.42, +2.83,16.04, +2.97,16.82, +3.11,17.61, +3.26,18.49, +3.37,19.1, +3.48,19.72, +3.56,20.18, +3.65,20.68, +3.85,20.86, +5.17,21.28, +6.25,21.54, +7.71,21.83, +8.18,22.47, +8.33,22.88, +8.68,23.68, +8.97,24.07, +10.23,24.35, +11.89,24.35, +12.32,24.99, +14.06,24.35, +13.71,25.05, +14.93,25.86, +15.5,26.85, +16.72,28.95, +17.15,29.71, +17.14,29.68, +17.44,29.18, +21.8,26.21, +22.49,26.8, +22.77,27.14, +23.24,27.7, +23.12,27.55, +22.68,27.03, +21.76,25.93, +20.71,24.69, +19.95,23.78, +19.38,23.1, +18.32,21.83, +17.67,21.06, +16.99,20.25, +16.29,19.32, +18.09,17.01, +18.72,15.01, +20.21,12.69, +21.71,9.93, +22.66,8.5, +23.56,8.58, +23.41,8.52, +22.79,8.29, +22.47,8.18, +22.15,8.62, +19.86,11.47, +19.86,11.47, +19.23,11.1, +18.4,10.02, +18.7,6.81, +17.9,6.52, +18.55,4.11, +16.19,6.97, +16.74,6.86, +17.55,7.27, +17.51,6.37, +18.69,4.81, +19.42,4.6, +19.38,4.74, +19.07,4.83, +19.2,4.87, +16.13,11.09, +14.91,12.51, +12.8,13.36, +11.25,13.4, +10.76,13.39, +6.37,11.04, +5.77,10, +4.83,8.37, +5.3,6.66, +5.29,5.77, +5.57,4.44, +5.66,4.07, +6.59,3.8, +8.68,5.87, +6.54,3.62, +13.97,6.1, +11.83,5.74, +7.14,5.17, +7.76,5.22, +6.55,3.58, +6.86,2.93, +9.68,2.3, +3.38,3.69, +2.62,4.55, +1.44,2.49, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.96,2.11, +-11.92,2.1, +-11.87,2.09, +-11.87,2.09, +-11.85,2.09, +-11.76,2.07, +-11.44,2.02, +-10.98,1.37, +-11.07,0.28, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0, +-11.2,0, +-11.47,0, +-11.37,0.58, +-12.01,0.7, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.64,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.5,0, +-13.97,0, +-14,0, +-14.48,0, +-14.97,0, +-14.97,0, +-14.8,0, +-14.29,0, +-14,0, +-14,0, +-14,0, +-14.3,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0.07, +-14.81,2.08, +-14.74,2.6, +-14.72,2.59, +-14.14,2.49, +-13.78,2.43, +-13.78,2.43, +-13.35,2.35, +-13.04,2.3, +-12.95,2.32, +-12.69,3.81, +-12.92,4.7, +-13.01,4.74, +-12.63,4.6, +-12.66,4.61, +-13.15,4.79, +-13.15,4.79, +-13.08,4.96, +-12.53,6.19, +-12.12,7, +-11.81,6.82, +-11.28,6.51, +-11.28,6.51, +-11.28,6.51, +-11.05,6.83, +-10.07,8.02, +-9.62,8.07, +-8.99,8.38, +-8.07,9.61, +-6.33,10.24, +-6.03,10.44, +-4.4,11.21, +-4.07,11.34, +-2.42,11.79, +-2.09,11.87, +-2.15,12.2, +-1.39,12.92, +0,13.02, +0,13.06, +0.03,13.5, +1.77,13.77, +2.39,13.74, +2.45,13.88, +2.52,14.29, +2.61,14.81, +2.72,15.42, +2.84,16.12, +2.99,16.94, +3.15,17.87, +3.31,18.79, +3.43,19.48, +3.48,19.74, +3.63,20.57, +3.65,20.68, +3.78,21.41, +3.83,21.7, +4.17,22.52, +6.73,22.43, +8.19,22.51, +8.53,23.43, +8.61,23.67, +8.92,24.5, +9.18,25.23, +9.41,25.85, +9.77,26.2, +9.8,26.51, +11.07,27.35, +13.86,28.39, +16.33,29.1, +17.17,29.73, +17.3,29.96, +17.04,29.52, +19.1,28.65, +22.27,27.01, +23.04,27.46, +23.33,27.8, +23.12,27.55, +22.81,27.19, +21.85,26.04, +20.9,24.91, +20.1,23.96, +19.2,22.88, +18.46,22, +17.71,21.11, +17.22,20.52, +16.66,19.85, +16.36,19.39, +18.47,16.85, +19.16,15.23, +20.53,12.86, +21.19,11, +22.58,8.35, +22.95,8.35, +23.09,8.41, +22.47,8.18, +22.47,8.18, +21.81,9.44, +20.22,11.68, +19.36,11.18, +18.5,10.68, +17.22,9.94, +18.29,7.12, +17.73,6.89, +14.55,7.05, +14.71,8.49, +15.6,9.01, +16.13,6.71, +17.9,6.52, +18.59,6.76, +18.41,6.7, +18.03,6.56, +17.9,6.52, +16.39,12.33, +15.62,13.1, +14.19,13.82, +12.07,14.38, +11.69,13.93, +8.54,13.95, +9.02,12.04, +5.7,7.56, +5.66,6.74, +5.35,5.36, +5.36,4.5, +5.36,4.5, +5.35,4.23, +5.99,3.81, +6.43,3.94, +7.07,4.51, +8.81,6.03, +7.17,5.63, +7.35,5.07, +10.85,4.43, +6.87,3.08, +7.69,2.54, +6.97,3.42, +4.55,7.54, +4.31,7.46, +3.42,5.92, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.87,2.09, +-11.58,2.04, +-11.15,1.97, +-11.27,1.99, +-10.91,1.92, +-11.03,0.83, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0, +-11.08,0.02, +-11.08,0, +-11.08,0.14, +-11.21,1.13, +-12,0.03, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.51,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.85,0, +-14,0, +-14.21,0, +-14.46,0, +-14.56,0, +-14.09,0, +-14.3,0, +-14.34,0, +-14,0, +-14,0, +-14.19,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-15.05,0, +-15.49,0, +-15.04,0, +-14.97,0, +-14.97,0, +-14.92,1.01, +-14.86,1.45, +-14.77,2.41, +-14.61,2.58, +-13.78,2.43, +-13.78,2.43, +-13.76,2.43, +-13.78,2.43, +-13.74,2.42, +-13.61,3.21, +-13.33,4.19, +-13.16,4.75, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.04,5.07, +-12.36,6.54, +-11.99,6.93, +-11.3,6.53, +-11.28,6.51, +-11.28,6.51, +-11.04,6.37, +-10.5,7.29, +-9.68,8.05, +-8.86,8.12, +-7.75,9.23, +-6.46,10.15, +-6.03,10.44, +-4.36,11.22, +-4.12,11.33, +-2.66,11.73, +-2.09,11.87, +-2.12,12.01, +-2.04,12.86, +0,13.02, +0,13.02, +0,13.02, +0.03,13.05, +1.7,13.19, +2.42,13.7, +2.45,13.88, +2.57,14.58, +2.68,15.21, +2.8,15.86, +2.95,16.71, +3.13,17.78, +3.27,18.57, +3.41,19.36, +3.48,19.72, +3.59,20.39, +3.65,20.68, +3.74,21.23, +3.82,21.69, +3.95,22.4, +4.23,22.96, +5.22,23.29, +7.14,23.62, +8.4,23.62, +8.84,24.27, +8.92,24.51, +9.19,25.25, +9.37,25.76, +9.57,26.3, +9.99,27.45, +10.53,28.94, +12.14,30.68, +15.76,30.18, +17.48,30.28, +17.49,30.3, +17.52,30.35, +18.85,30.33, +20.11,29.68, +19.93,29.8, +19.55,30.11, +21.99,27.37, +21.58,25.72, +20.87,24.87, +20.1,23.95, +19.44,23.17, +18.59,22.15, +18.08,21.55, +17.46,20.81, +17.15,20.44, +16.76,19.97, +16.98,19.39, +18.94,16.77, +19.76,15.06, +20.65,12.17, +21.48,10.36, +22.42,8.29, +22.49,8.19, +22.47,8.18, +22.47,8.18, +22.47,8.18, +21.57,10.19, +19.53,11.27, +18.9,10.91, +17.22,9.94, +16.39,9.46, +14.98,8.65, +12.02,7.57, +12.45,7.65, +13.92,8.03, +16.06,9.27, +17.1,9.87, +17.6,9.16, +17.2,9.93, +17.69,7.78, +18.4,6.7, +17.66,6.76, +15.68,10.92, +14.23,15.34, +12.87,15.34, +12.35,14.72, +11.78,14.03, +11.19,13.34, +7.98,9.51, +6.08,6.34, +5.22,5.42, +4.93,4.93, +5.11,4.74, +4.78,4.69, +4.92,4.55, +5.75,4.37, +6.04,4.59, +6.96,5.84, +6.66,5.3, +6.03,4.74, +8.63,4.98, +6.63,3.3, +6.97,2.54, +6.34,3.89, +4.4,7.62, +4.05,7.01, +3.8,6.59, +2.58,6.49, +1.37,3.76, +0.3,0.82, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.87,2.09, +-11.87,2.09, +-11.84,2.09, +-11.28,1.99, +-10.98,1.94, +-10.91,1.92, +-10.97,1.93, +-10.91,1.92, +-11.02,0.96, +-11.07,0.3, +-11.08,0, +-11.08,0, +-11.08,0, +-11.06,0.46, +-10.97,1.48, +-10.99,1.33, +-11.07,0.73, +-11.63,0.22, +-12.01,0.37, +-12.05,0.07, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.61,0, +-12.89,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.05,0, +-13.41,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14.66,0, +-14.86,0, +-14.3,0, +-14.17,0, +-14.43,0, +-14.97,0, +-14.97,0, +-15.14,0, +-15.01,0, +-14.97,0, +-15.12,0, +-15.91,0, +-15.57,0, +-15.11,0, +-15.09,0, +-14.97,0, +-14.97,0, +-14.79,2.25, +-14.74,2.6, +-14.51,2.56, +-13.89,2.45, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.76,2.55, +-13.29,4.35, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-12.62,5.99, +-12.3,6.65, +-11.47,6.62, +-11.28,6.51, +-11.28,6.51, +-11.06,6.39, +-10.54,6.11, +-10.42,6.67, +-9.61,7.07, +-7.75,9.23, +-6.82,9.89, +-6.03,10.44, +-4.67,11.09, +-4.12,11.33, +-3.43,11.53, +-2.09,11.87, +-2.09,11.87, +-2.14,12.14, +-1.63,12.87, +-0.32,12.97, +0,13.02, +0,13.02, +0.25,13, +2.31,13.1, +2.43,13.78, +2.51,14.24, +2.61,14.82, +2.76,15.68, +2.91,16.49, +3.05,17.27, +3.21,18.21, +3.31,18.78, +3.47,19.67, +3.53,19.99, +3.65,20.68, +3.7,20.99, +3.81,21.63, +3.93,22.28, +4,22.66, +4.15,23.55, +4.19,23.79, +6.33,24.18, +8.69,23.88, +8.91,24.48, +9.09,24.97, +9.26,25.43, +9.54,26.21, +9.84,27.03, +10.57,29.04, +11.27,30.96, +11.82,31.89, +16.45,30.55, +17.49,30.3, +17.51,30.32, +17.83,30.88, +17.98,31.14, +17.98,31.14, +17.92,31.03, +19.2,29.04, +21.66,25.82, +20.88,24.89, +20.21,24.08, +19.42,23.15, +18.69,22.27, +18.13,21.61, +17.74,21.14, +17.44,20.78, +17.15,20.44, +16.89,20.12, +17.66,19.81, +19.95,16.95, +19.65,15.51, +20.5,12.58, +20.97,11.45, +22.45,8.23, +22.47,8.18, +22.47,8.18, +22.47,8.18, +22.22,8.75, +19.2,12.12, +19.14,11.05, +18.08,10.44, +17.46,10.08, +15.67,9.88, +12.42,10.42, +11.84,9.93, +15.15,9.68, +16.92,9.77, +17.43,10.06, +17.62,10.17, +17.56,9.73, +18.73,6.94, +18.63,6.78, +17.77,6.51, +15.6,8.33, +14.89,11.87, +13.94,16.61, +13.05,15.55, +12.38,14.76, +11.05,13.17, +7.95,7.9, +7.72,6.48, +5.86,5.17, +4.74,5.11, +4.32,5.15, +4.62,5.3, +3.72,4.89, +4.26,4.62, +4.87,4.99, +6.53,5.48, +7.68,6.13, +6.01,5.04, +6.12,4.62, +6.06,3.5, +6.71,2.87, +6.33,3.22, +4.05,7.01, +3.99,6.9, +3.99,6.9, +3.25,6.33, +2.39,6.58, +-1.6,-4.38, +-0.34,-0.94, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.87,2.09, +-11.83,2.09, +-11.21,1.98, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.81,1.91, +-10.49,1.81, +-10.89,1.21, +-10.69,0.5, +-10.67,0.02, +-10.51,0.14, +-10.8,0.8, +-10.93,1.78, +-10.98,1.42, +-10.94,1.68, +-10.93,1.8, +-11.57,1.96, +-12.03,0.54, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.29,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.63,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14.63,0, +-14.97,0, +-14.92,0, +-14.97,0, +-14.92,0, +-14.97,0, +-15.14,0, +-15.83,0, +-15.42,0, +-14.97,0, +-15.07,0, +-15.94,0, +-15.83,0, +-15.72,0, +-15.67,0, +-15.08,0, +-14.96,0.2, +-14.78,2.31, +-14.74,2.6, +-14.72,2.59, +-13.87,2.45, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.77,2.49, +-13.51,3.56, +-13.23,4.55, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-12.9,5.4, +-11.71,6.52, +-11.28,6.51, +-11.28,6.51, +-11.19,6.46, +-10.49,6.05, +-10.46,6.04, +-9.76,6.83, +-7.75,9.23, +-7.01,9.72, +-5.87,10.17, +-4.89,10.67, +-4.12,11.33, +-4.12,11.33, +-3.01,11.64, +-2.09,11.87, +-2.09,11.87, +-2.1,11.89, +-1.08,12.07, +0,12.16, +0,12.76, +0,13.02, +1.89,12.88, +2.43,13.78, +2.49,14.09, +2.61,14.81, +2.76,15.64, +2.88,16.35, +3.03,17.16, +3.16,17.91, +3.31,18.76, +3.45,19.55, +3.52,19.97, +3.65,20.68, +3.7,20.97, +3.81,21.63, +3.89,22.06, +3.99,22.61, +4.11,23.3, +4.17,23.64, +4.31,24.45, +5.41,24.48, +8.33,24.54, +8.93,24.52, +9.18,25.23, +9.36,25.73, +9.71,26.69, +10.2,28.03, +11.04,30.34, +11.6,31.86, +12.14,32.09, +16.67,30.69, +17.49,30.3, +17.58,30.45, +17.97,31.12, +17.98,31.14, +17.61,30.49, +17.99,29.13, +21.32,25.41, +20.81,24.8, +20.01,23.84, +19.51,23.25, +19.01,22.65, +18.47,22.02, +18.05,21.52, +17.84,21.26, +17.4,20.74, +17.46,20.81, +17.6,20.97, +18.2,20.24, +19.6,16.67, +19.5,15.27, +20.49,12.51, +20.93,11.52, +21.93,9.36, +22.47,8.18, +22.56,8.21, +23.08,8.4, +19.44,11.81, +17.56,13.49, +17.59,11.76, +16.75,11.7, +14.59,12.25, +13.81,11.58, +14.02,11.76, +14.59,12.25, +15.8,11.58, +16.96,11.82, +18.52,10.69, +17.7,10.22, +18.33,7.97, +18.81,6.85, +18.18,6.62, +16.78,7.83, +14.46,8.35, +14.16,14.48, +13.73,16.36, +13.18,15.71, +13.66,13.96, +13.27,11.14, +7.33,6.15, +5.87,5.35, +4.71,5.15, +4,5.54, +3.43,5.94, +3.5,6.06, +4.25,5.54, +5.83,5.57, +7.63,6.4, +7.53,6.53, +7.1,6.34, +5.98,6.76, +5.56,5.75, +5.65,3.91, +5.05,4.89, +3.76,6.27, +3.99,6.9, +3.99,6.9, +3.68,6.38, +2.87,6.36, +2.39,6.58, +-1.56,-4.28, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.87,2.09, +-11.87,2.09, +-11.41,2.01, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.89,1.92, +-10.45,1.84, +-10.04,1.77, +-9.78,1.73, +-10.14,1.62, +-9.94,1.53, +-9.9,0.27, +-9.86,0.91, +-10,1.04, +-10.61,1.41, +-10.89,1.92, +-10.91,1.92, +-10.91,1.92, +-11.15,1.97, +-11.99,1, +-12.05,0.21, +-12.05,0.11, +-12.05,0, +-12.05,0, +-12.05,0, +-12.17,0, +-12.76,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.71,0, +-14,0, +-14,0, +-14,0, +-14,0, +-14.66,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-15.07,0, +-15.77,0, +-15.94,0, +-15.52,0, +-15.41,0, +-15.19,0, +-15.9,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.36,0, +-14.93,0.81, +-14.86,1.63, +-14.74,2.6, +-14.74,2.6, +-14.48,2.55, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.53,3.44, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-12.68,5.38, +-11.72,5.62, +-11.39,6.31, +-11.33,6.43, +-10.76,6.21, +-10.44,6.03, +-9.76,6.83, +-7.75,9.23, +-7.03,9.09, +-5.54,9.6, +-4.74,9.98, +-3.88,10.66, +-4.12,11.33, +-4.12,11.33, +-3.31,11.56, +-2.09,11.87, +-2.09,11.87, +-1.68,11.92, +0,12.05, +0,12.3, +0,12.98, +1.75,12.88, +2.41,13.64, +2.46,13.93, +2.6,14.74, +2.73,15.46, +2.84,16.08, +2.95,16.72, +3.13,17.78, +3.26,18.5, +3.38,19.14, +3.48,19.72, +3.62,20.53, +3.65,20.71, +3.79,21.5, +3.84,21.8, +3.98,22.59, +4.02,22.82, +4.15,23.55, +4.23,24, +4.35,24.7, +4.87,24.98, +8.8,24.51, +9.08,24.96, +9.31,25.57, +9.61,26.41, +10.09,27.72, +10.92,30.01, +11.53,31.69, +11.63,31.97, +13.01,31.71, +17.1,30.07, +17.49,30.3, +17.53,30.36, +17.55,30.4, +17.31,29.99, +20.02,27.2, +21.22,25.29, +20.6,24.55, +19.94,23.76, +19.52,23.26, +19.07,22.73, +18.62,22.19, +18.1,21.57, +17.99,21.44, +17.91,21.35, +17.99,21.44, +17.99,21.44, +17.89,21.31, +18.19,20.02, +19.58,17.04, +19.52,15.66, +20.72,12.02, +20.78,11.82, +21.72,9.84, +22.52,8.81, +23.56,8.58, +21.64,11.24, +17.5,13.88, +16.72,14.03, +16.08,13.49, +15.43,12.95, +14.78,12.4, +14.59,12.25, +14.87,12.48, +16.21,12.89, +19.87,12.04, +19.67,11.36, +18.74,10.82, +18.33,10.53, +18.84,8.54, +18.2,7.94, +16.27,9.39, +15.21,8.78, +14.49,11.76, +14.21,16.93, +13.67,16.29, +15.59,13.08, +10.25,9.74, +7.93,7.54, +5.03,6, +4.5,5.36, +4.12,5.92, +3.57,6.18, +3.5,6.06, +4.25,5.87, +5.03,6, +5.25,7.16, +5.38,7.11, +6,7.72, +5.56,8.16, +5.32,7.55, +4.53,7.85, +3.99,6.9, +3.99,6.9, +3.99,6.9, +3.99,6.9, +3.62,6.27, +3.5,6.06, +2.6,6.36, +1.42,3.91, +-0.45,-1.25, +0.29,0.81, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.87,2.09, +-11.76,2.07, +-11.04,1.95, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.46,1.84, +-9.84,1.73, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.79,1.53, +-9.87,0.81, +-9.79,1.53, +-9.83,1.19, +-10.06,1.75, +-10.65,1.88, +-10.91,1.92, +-10.91,1.92, +-11.02,1.94, +-11.81,1.98, +-11.89,1.94, +-12.03,0.44, +-12.05,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.75,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.33,0, +-13.82,0, +-13.91,0, +-14,0, +-14,0, +-14.36,0, +-14.95,0, +-14.97,0, +-14.97,0, +-14.97,0, +-15.05,0, +-15.89,0, +-15.94,0, +-15.43,0, +-15.82,0, +-15.88,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.67,0, +-14.97,0.11, +-14.79,2.28, +-14.74,2.6, +-14.74,2.6, +-14.45,2.55, +-13.93,2.46, +-14.09,2.49, +-14.06,2.48, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.73,2.7, +-13.33,4.19, +-13.15,4.79, +-13.15,4.79, +-13.15,4.79, +-13.2,4.8, +-13.25,4.82, +-13.15,4.79, +-12.99,4.73, +-12.22,4.57, +-12.17,4.64, +-11.76,5.54, +-10.79,6.06, +-10.44,6.03, +-9.88,6.72, +-7.47,8.9, +-6.54,8.92, +-5.54,9.6, +-4.98,9.87, +-3.79,10.41, +-3.89,10.69, +-4.12,11.33, +-4.12,11.33, +-2.35,11.81, +-2.09,11.87, +-1.49,11.93, +0,12.05, +0,12.05, +0,12.91, +1.36,12.92, +2.41,13.69, +2.47,14, +2.6,14.74, +2.73,15.48, +2.79,15.82, +2.94,16.65, +3.08,17.46, +3.22,18.27, +3.32,18.81, +3.47,19.68, +3.59,20.36, +3.65,20.68, +3.79,21.49, +3.81,21.63, +3.96,22.47, +3.98,22.59, +4.12,23.35, +4.18,23.68, +4.31,24.46, +4.39,24.89, +7.41,24.79, +9.06,24.89, +9.23,25.36, +9.51,26.12, +9.67,26.58, +10.34,28.4, +11.33,31.13, +11.63,31.94, +11.63,31.97, +14.87,30.49, +17.15,29.7, +17.24,29.87, +17.19,29.77, +17.64,28.78, +20.8,25.68, +20.73,24.7, +20.33,24.23, +19.82,23.62, +19.24,22.93, +19.17,22.85, +18.65,22.22, +18.53,22.09, +18.1,21.57, +17.99,21.44, +17.99,21.44, +18.34,21.85, +18.24,21.74, +17.87,21.29, +18.21,19.77, +19.75,16.72, +19.83,14.52, +20.63,12.56, +20.71,11.95, +20.82,12.02, +22.14,11.71, +21.74,12.49, +21.69,12.52, +20.52,12.82, +20.21,12.9, +19.6,12.9, +18.17,12.78, +18.19,12.8, +19.35,12.67, +21.72,12.54, +21.44,12.38, +20.64,11.92, +19.7,11.37, +18.92,10.92, +18.35,10.59, +17.72,10.23, +17.09,9.87, +16.29,9.4, +15.35,9.87, +14.65,17.46, +14.8,16.15, +15.76,13.23, +12.78,11.44, +8.22,8.41, +5.3,6.32, +5.12,6.11, +4.61,6.91, +3.99,6.9, +3.92,6.8, +4.74,6.37, +5.04,6.01, +4.47,7.74, +4.47,7.74, +4.91,7.73, +5.68,8.12, +5.04,7.74, +4.62,8.01, +4.19,7.26, +4.11,7.11, +3.82,6.62, +3.66,6.34, +3.5,6.06, +3.5,6.06, +3.34,6.13, +3.08,5.34, +2.18,5.61, +1.92,4.26, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-11.29,1.99, +-11.41,2.01, +-10.88,1.92, +-10.53,1.86, +-10.18,1.86, +-10.05,1.91, +-9.93,1.75, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.75,1.7, +-9.79,1.35, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-10.27,1.81, +-10.81,1.91, +-10.89,1.92, +-10.91,1.92, +-11.42,2.01, +-11.87,1.72, +-12,1.05, +-12.03,0.07, +-12.03,0, +-12.05,0, +-12.05,0, +-12.35,0, +-12.93,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.15,0, +-13.49,0, +-13.84,0, +-14,0, +-14,0, +-14.17,0, +-14.94,0, +-14.97,0, +-14.97,0, +-15.24,0, +-15.36,0, +-15.9,0, +-15.94,0, +-15.84,0, +-15.92,0, +-15.94,0, +-16,0, +-15.97,0, +-15.94,0, +-15.94,0, +-15.92,0, +-15.57,0.09, +-14.96,1.83, +-14.81,2.05, +-14.74,2.6, +-14.74,2.6, +-14.55,2.57, +-14.72,2.6, +-14.64,2.58, +-14.19,2.5, +-13.81,2.43, +-13.78,2.43, +-13.78,2.43, +-13.67,2.9, +-13.44,4.22, +-13.17,4.8, +-13.24,4.82, +-13.68,4.98, +-13.65,4.97, +-13.15,4.79, +-12.94,4.71, +-12.24,4.45, +-12.24,4.45, +-12.24,4.45, +-11.46,4.8, +-11.09,4.68, +-9.9,6.28, +-7.12,8.49, +-7.08,8.52, +-5.54,9.6, +-5.52,9.61, +-4.1,10.27, +-3.79,10.41, +-3.94,10.84, +-4.12,11.33, +-3.19,11.59, +-2.09,11.87, +-1.65,11.92, +-0.02,12.05, +0,12.11, +0,13, +0.84,12.97, +2.41,13.64, +2.45,13.88, +2.58,14.63, +2.64,15, +2.77,15.73, +2.89,16.38, +2.99,16.98, +3.13,17.78, +3.27,18.57, +3.38,19.17, +3.5,19.82, +3.64,20.65, +3.69,20.91, +3.81,21.63, +3.84,21.8, +3.98,22.57, +4.02,22.81, +4.15,23.55, +4.21,23.86, +4.35,24.67, +7.79,24.27, +8.91,24.48, +9.16,25.17, +9.33,25.62, +9.58,26.32, +10.16,27.92, +10.91,29.98, +11.41,31.35, +11.58,31.81, +11.91,31.41, +15.93,29.57, +16.74,29, +16.79,29.08, +16.62,28.79, +16.96,27.52, +20.51,24.45, +20.19,24.07, +19.59,23.34, +19.24,22.93, +19.08,22.73, +18.63,22.21, +18.62,22.19, +18.62,22.19, +18.43,21.96, +18.52,22.08, +18.62,22.19, +18.52,22.08, +18.14,21.61, +18.05,21.52, +19.49,18.96, +20.08,17.02, +20.33,14.92, +21.64,12.5, +21.57,12.45, +21.72,12.54, +21.72,12.54, +21.72,12.54, +21.72,12.54, +21.72,12.54, +21.93,12.66, +22.45,12.96, +22.56,13.02, +22.51,13, +21.95,12.67, +21.69,12.52, +21.21,12.25, +20.56,11.87, +20.06,11.58, +19.36,11.18, +18.67,10.78, +17.8,10.27, +17.11,9.88, +15.98,9.85, +14.59,15.5, +14.57,17.36, +15.35,14.74, +14.95,13.09, +8.37,8.54, +5.36,6.38, +5.66,6.74, +5.93,7.06, +5.01,7.07, +3.99,6.9, +4.53,6.81, +4.8,6.92, +4.39,7.6, +3.99,6.9, +5.38,6.91, +5.4,7.67, +4.35,7.53, +4.39,7.6, +4.17,7.22, +3.99,6.9, +3.5,6.06, +3.5,6.06, +3.5,6.06, +3.5,6.06, +3.5,6.06, +3.29,5.7, +2.88,5.28, +3.16,5.12, +3.11,4.08, +0.41,0.48, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.91,1.92, +-10.94,1.93, +-10.66,1.88, +-9.87,1.84, +-9.47,2.91, +-9.37,3.23, +-9.61,2.39, +-9.76,1.72, +-9.76,1.72, +-9.66,1.7, +-9.49,1.67, +-9.17,1.62, +-8.98,1.58, +-9.59,1.69, +-9.76,1.72, +-9.76,1.72, +-10.25,1.81, +-9.95,1.76, +-10.55,1.86, +-10.91,1.92, +-10.93,1.93, +-11.43,1.91, +-11.92,1.7, +-11.49,0.2, +-11.69,0, +-12.05,0, +-12.05,0, +-12.05,0, +-12.51,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.07,0, +-13.24,0, +-13.8,0, +-14,0, +-14,0, +-14.04,0, +-14.72,0, +-14.97,0, +-14.97,0, +-15.21,0, +-15.87,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.96,0, +-16.5,0, +-16.06,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.92,0.03, +-15.14,0.13, +-14.83,1.92, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.64,2.58, +-14.29,2.52, +-13.85,2.44, +-14.08,2.48, +-14.28,2.52, +-14.7,2.64, +-14.19,4.28, +-14.01,5.1, +-14.02,5.1, +-13.93,5.07, +-13.22,4.81, +-13.03,4.74, +-12.29,4.47, +-12.24,4.45, +-12.24,4.45, +-12.11,4.41, +-11.37,4.14, +-10.65,4.92, +-7.14,8.48, +-7.04,8.56, +-5.65,9.53, +-5.54,9.6, +-5.49,9.62, +-4.57,10.06, +-3.79,10.41, +-3.93,10.8, +-3.37,11.11, +-2.08,11.8, +-2.02,11.88, +-0.33,12.04, +0,12.29, +0,13.02, +0.03,13.02, +1.36,13.71, +2.43,13.78, +2.57,14.55, +2.6,14.74, +2.76,15.65, +2.83,16.05, +2.94,16.68, +3.11,17.61, +3.21,18.19, +3.36,19.04, +3.48,19.72, +3.59,20.38, +3.66,20.77, +3.79,21.49, +3.82,21.68, +3.94,22.37, +3.98,22.59, +4.12,23.35, +4.15,23.55, +4.46,24.38, +7.92,23.77, +8.84,24.29, +8.96,24.63, +9.23,25.37, +9.38,25.78, +9.66,26.53, +10.35,28.45, +10.92,30.01, +11.22,30.83, +11.14,30.62, +11.28,30.76, +14.9,29.26, +16.38,28.38, +16.21,28.07, +15.91,27.56, +18.11,25.22, +19.79,23.58, +19.24,22.93, +19.19,22.87, +18.8,22.41, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.52,22.08, +18.3,21.16, +20.19,18.45, +20.1,16.78, +20.58,14.66, +21.72,12.54, +21.72,12.54, +21.72,12.54, +21.72,12.54, +21.84,12.61, +22.31,12.88, +22.56,13.02, +22.56,13.02, +22.56,13.02, +22.56,13.02, +22.56,13.02, +22.11,13.28, +19.78,15.46, +19.69,14.8, +19.78,13.28, +19.12,12.86, +19.33,11.23, +18.58,11.23, +17.84,11.07, +16.23,11.92, +15.23,13.98, +14.57,17.36, +14.86,15.58, +12.96,15.44, +8.62,10.27, +5.9,7.04, +5.75,6.85, +5.83,6.94, +6.28,7.49, +5.39,7.95, +4.47,7.74, +4.47,7.74, +4.23,7.32, +3.99,6.9, +5.04,6.36, +5.59,6.66, +4.39,7.6, +4.05,7.01, +3.99,6.9, +3.82,6.62, +3.5,6.06, +3.5,6.06, +3.5,6.06, +3.5,6.06, +3.5,6.06, +3.43,5.94, +3.43,5.6, +3.34,4.99, +3.87,4.62, +2.12,2.53, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.53,2.73, +-9.32,3.39, +-9.32,3.39, +-9.26,3.29, +-9.21,2.13, +-9.23,1.64, +-9.1,1.61, +-8.81,1.55, +-8.81,1.55, +-8.81,1.55, +-9.08,1.6, +-9.73,1.71, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.98,1.76, +-10.83,1.91, +-10.91,1.92, +-11.29,1.99, +-11.25,1.98, +-11.19,1.12, +-11.2,0, +-11.66,0, +-12.05,0, +-12.05,0, +-12.2,0, +-12.93,0, +-13.02,0, +-13.02,0, +-13.05,0, +-13.46,0, +-13.51,0, +-14,0, +-14,0, +-14,0, +-14.49,0, +-14.97,0, +-14.97,0, +-15.39,0, +-15.69,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-16.44,0, +-16.88,0, +-16.5,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.66,0, +-14.87,1.53, +-14.88,1.24, +-14.78,2.34, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.52,2.56, +-14.41,2.54, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.57,3.33, +-14.17,4.79, +-14.07,5.12, +-14.04,5.11, +-13.22,4.81, +-13.15,4.79, +-12.89,4.69, +-12.26,4.46, +-12.24,4.45, +-12.15,4.42, +-11.37,4.14, +-10.32,5.19, +-7.48,8.14, +-6.95,8.35, +-5.45,9.13, +-5.28,9.15, +-5.53,9.57, +-5.54,9.6, +-4.3,10.18, +-3.79,10.41, +-3.65,10.45, +-2.01,11.39, +-2.09,11.87, +-0.81,11.98, +0,12.19, +0,13.02, +0,13.02, +0.03,13.44, +1.91,13.84, +2.48,14.08, +2.6,14.74, +2.69,15.26, +2.78,15.75, +2.9,16.46, +2.99,16.94, +3.16,17.9, +3.28,18.62, +3.4,19.29, +3.51,19.88, +3.64,20.65, +3.7,20.99, +3.8,21.58, +3.84,21.8, +3.98,22.56, +4,22.67, +4.3,23.47, +6.48,23.12, +8.58,23.56, +8.71,23.93, +8.91,24.48, +9.16,25.16, +9.28,25.5, +9.57,26.3, +9.87,27.11, +10.42,28.63, +10.78,29.62, +10.73,29.48, +10.88,29.89, +11.45,29.82, +14.5,28, +15.56,26.96, +15.45,26.77, +15.22,26.36, +16.37,25.33, +18.6,23.18, +18.7,22.29, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.51,22.06, +18.69,21.3, +20.33,18.34, +20.35,17.08, +21.36,14.42, +22.05,12.73, +22.05,12.73, +22.22,12.83, +22.52,13, +22.56,13.02, +22.56,13.02, +22.56,13.02, +22.56,13.02, +22.62,13.06, +22.56,13.02, +21.04,15.22, +19.59,16.44, +19.21,16.12, +18.78,15.76, +18.32,15.37, +17.9,14.96, +17.36,14.46, +16.68,13.99, +16.32,13.7, +16.13,13.54, +14.95,17.12, +14.03,16.72, +13.18,15.71, +12.01,14.31, +9.04,10.78, +6.21,7.4, +5.59,6.96, +5.85,6.97, +5.39,7.95, +4.53,7.85, +4.47,7.74, +4.35,7.53, +3.9,6.76, +4.41,6.6, +4.36,6.9, +4.2,6.92, +3.68,6.38, +3.58,5.8, +3.78,6.54, +3.5,6.06, +3.5,6.06, +3.5,6.06, +3.43,5.94, +3.56,5.87, +3.5,6.06, +3.35,5.28, +3.38,4.96, +2.21,2.64, +1.45,1.73, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.81,1.55, +-8.81,1.55, +-8.81,1.55, +-8.81,1.55, +-8.81,1.55, +-8.95,1.58, +-9.66,1.7, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.88,1.74, +-10.64,1.88, +-10.94,1.93, +-10.96,1.93, +-10.91,1.92, +-11,1.06, +-11.08,0, +-11.23,0, +-11.98,0, +-12.05,0, +-12.05,0, +-12.77,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.1,0, +-13.63,0, +-13.88,0, +-14,0, +-14,0, +-14.21,0, +-14.75,0, +-14.48,0, +-15.21,0, +-15.91,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-16.47,0, +-16.76,0, +-16.16,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.72,0, +-15.02,0.29, +-14.96,0.19, +-14.85,1.74, +-14.76,2.5, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.68,2.89, +-14.57,3.36, +-14.13,4.59, +-13.37,4.38, +-13.17,4.74, +-13.04,4.75, +-12.28,4.47, +-12.24,4.45, +-12.19,4.44, +-11.44,4.16, +-10.92,4.55, +-7.06,8.01, +-6.5,7.74, +-5.54,8.18, +-4.96,8.59, +-5.09,8.82, +-5.51,9.54, +-5.17,9.79, +-3.79,10.41, +-3.71,10.44, +-2.11,10.96, +-2.09,11.87, +-1.96,11.89, +-0.07,12.22, +0,13.02, +0,13.02, +0,13.32, +0.21,13.99, +2.03,13.83, +2.58,14.61, +2.61,14.83, +2.77,15.7, +2.82,15.98, +2.94,16.65, +3.12,17.71, +3.25,18.45, +3.34,18.95, +3.48,19.72, +3.61,20.46, +3.65,20.68, +3.79,21.5, +3.82,21.66, +3.94,22.33, +3.98,22.59, +5.7,22.8, +8.19,22.49, +8.51,23.38, +8.61,23.64, +8.88,24.4, +9.03,24.8, +9.24,25.39, +9.36,25.73, +9.6,26.38, +9.92,27.24, +10.17,27.95, +10.48,28.78, +10.57,29.04, +10.57,29.04, +10.57,29.04, +12.82,27.96, +15.14,26.36, +15.03,26.03, +14.97,25.92, +15.38,24.77, +16.67,23.58, +18.27,22.46, +18.33,22.41, +18.62,22.19, +18.68,22.26, +18.96,22.6, +18.78,22.38, +18.81,22.42, +18.99,22.63, +18.67,22.25, +18.62,22.19, +18.62,22.19, +18.6,22.17, +19.28,20.55, +20.82,18, +20.49,16.97, +21.35,15.13, +22.47,13.28, +22.56,13.02, +22.56,13.02, +22.56,13.02, +22.73,13.12, +22.89,13.22, +23.36,13.49, +23.35,13.48, +22.93,13.58, +20.18,16.68, +19.95,16.74, +19.39,16.27, +19.21,16.12, +18.61,15.62, +18.24,15.31, +17.65,14.81, +17.27,14.49, +16.83,14.12, +16.83,14.12, +15.32,15.82, +14.03,16.72, +13.1,15.62, +12.16,14.49, +10.33,12.32, +8.97,10.68, +6.33,7.54, +5.75,6.85, +5.15,8.15, +4.88,8.45, +4.47,7.74, +4.19,7.26, +3.74,6.48, +4.64,6.45, +3.82,6.62, +3.64,5.96, +4.39,5.73, +5.54,7.72, +3.77,6.26, +3.5,6.06, +3.5,6.06, +3.5,6.06, +3.38,5.85, +3.56,5.32, +4.36,5.46, +3.98,4.74, +2.91,3.46, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-8.81,1.55, +-9.12,1.61, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.76,1.72, +-9.98,1.76, +-10.91,1.92, +-10.91,1.92, +-10.91,1.92, +-10.98,1.32, +-11.02,0.89, +-11.1,0, +-11.88,0.03, +-12.05,0, +-12.05,0, +-12.44,0, +-13.02,0, +-13,0, +-13.02,0, +-13.24,0, +-13.39,0, +-13.63,0, +-14,0, +-14,0, +-14.02,0, +-14.02,0, +-14.65,0, +-15.16,0, +-15.58,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.96,0, +-15.96,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.66,0, +-15.26,0, +-14.97,0, +-14.93,0.75, +-14.85,1.79, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.73,2.63, +-14.48,2.58, +-13.79,2.52, +-13.57,3.32, +-13.41,3.65, +-12.41,4.27, +-12.24,4.45, +-12.24,4.45, +-11.87,4.32, +-10.21,5.2, +-6.74,7.33, +-6.37,7.59, +-5.93,7.92, +-4.96,8.59, +-4.96,8.59, +-4.99,8.65, +-5.37,9.47, +-3.79,10.41, +-3.77,10.42, +-2.3,10.99, +-2.09,11.87, +-2.09,11.87, +-1.11,11.97, +0,12.96, +0,13.02, +0,13.02, +0,13.73, +0.17,13.99, +1.92,14.18, +2.6,14.74, +2.69,15.27, +2.8,15.86, +2.93,16.61, +3.07,17.4, +3.15,17.89, +3.31,18.76, +3.44,19.53, +3.49,19.79, +3.64,20.65, +3.67,20.84, +3.81,21.61, +3.84,21.8, +4.47,22.46, +7.83,21.77, +8.18,22.47, +8.27,22.73, +8.56,23.51, +8.73,23.97, +8.92,24.5, +9.22,25.33, +9.24,25.39, +9.54,26.2, +9.58,26.32, +9.91,27.22, +10.16,27.92, +10.42,28.62, +10.26,28.18, +10.26,28.18, +10.29,28.27, +10.79,27.9, +14.03,26.39, +14.97,25.92, +14.7,25.46, +14.48,25.08, +14.48,25.08, +15.57,24.33, +18.22,22.5, +18.83,22.64, +18.96,22.6, +19.02,22.67, +19.24,22.93, +19.24,22.93, +19.1,22.76, +18.63,22.21, +18.62,22.19, +18.82,22.43, +18.62,22.19, +18.79,21.45, +21.16,18.16, +20.83,17.47, +22.32,14.27, +22.94,13.25, +23.09,13.33, +23.25,13.42, +23.4,13.51, +23.4,13.51, +23.4,13.51, +23.18,13.86, +21.18,16.7, +20.63,17.31, +20.05,16.82, +19.74,16.56, +19.25,16.15, +19.02,15.96, +18.34,15.39, +18.22,15.29, +17.55,14.73, +17.27,14.49, +16.9,15.09, +14.41,15.44, +14.17,16.89, +13.49,16.08, +12.05,14.36, +9.04,10.78, +9.17,10.93, +8.28,9.87, +7.18,8.56, +5.05,8.75, +4.8,8.31, +4.41,7.64, +4.05,7.02, +4.13,6.8, +4.71,5.89, +4.54,5.92, +3.87,5.8, +4.36,5.46, +4.36,6.47, +4.01,6.21, +3.5,6.06, +3.5,6.06, +3.5,6.06, +3.67,5.94, +4.11,4.9, +4.05,4.83, +3.94,4.7, +2.91,3.46, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-9.76,1.72, +-9.76,1.72, +-10.07,1.78, +-10.62,1.87, +-10.89,1.92, +-10.91,1.92, +-10.91,1.92, +-10.97,1.44, +-11.08,1.17, +-11.95,0.88, +-12.05,0, +-12.05,0, +-12.05,0, +-12.6,0, +-12.63,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.11,0, +-13.85,0, +-14,0, +-14,-0.03, +-13.99,-0.21, +-14.45,-0.03, +-14.97,-0.03, +-15.48,-0.18, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.92,0, +-15.41,0, +-14.97,0, +-14.97,0, +-14.96,0.26, +-14.77,2.41, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.69,2.59, +-14.11,2.49, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.21,2.83, +-12.38,4.02, +-12.35,4.11, +-11.74,4.27, +-10.86,4.4, +-6.73,7.25, +-6.37,7.59, +-6.37,7.59, +-5.25,8.4, +-4.96,8.59, +-4.96,8.59, +-4.93,8.76, +-3.84,10.37, +-3.79,10.41, +-2.56,10.84, +-2.09,11.87, +-2.09,11.87, +-2.07,11.89, +-0.52,12.87, +0,13.02, +0,13.02, +0,13.34, +0,14, +0,14.13, +1.16,14.89, +2.68,15.2, +2.77,15.7, +2.9,16.44, +3.01,17.06, +3.14,17.8, +3.27,18.54, +3.36,19.08, +3.48,19.72, +3.58,20.32, +3.65,20.68, +3.79,21.49, +3.86,21.62, +7.53,21.4, +7.84,21.55, +8.13,22.35, +8.18,22.47, +8.41,23.11, +8.58,23.59, +8.87,24.37, +8.98,24.66, +9.24,25.39, +9.24,25.39, +9.55,26.25, +9.61,26.41, +9.91,27.24, +10.08,27.7, +10.24,28.13, +10.24,28.13, +10.24,28.13, +10.24,28.13, +10.79,27.9, +14.61,26.11, +14.61,25.3, +14.48,25.08, +14.48,25.08, +14.51,25.13, +14.95,25.77, +15.83,25.34, +18.74,22.56, +19.14,23.01, +19.45,23.18, +19.65,23.42, +19.34,23.04, +19.06,22.72, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +20.45,20, +21.48,18.1, +21.24,17.39, +22.01,15.58, +23.34,13.61, +23.4,13.51, +23.4,13.51, +23.24,13.76, +22.41,14.98, +20.88,17.14, +20.7,17.37, +20.7,17.37, +20.51,17.21, +20.01,16.79, +19.67,16.51, +19.21,16.12, +18.97,15.92, +17.6,16.12, +16.32,16.91, +15.06,17.32, +14.87,17.72, +13.89,16.55, +13.07,16.27, +13.49,16.08, +11.38,13.99, +10.9,12.99, +9.2,10.97, +7.93,9.45, +6.73,8.76, +5.55,8.57, +4.75,8.23, +4.31,7.46, +3.99,6.9, +4.21,6.6, +4.5,5.36, +4.58,5.45, +4.5,5.36, +4.5,5.36, +4.58,5.45, +4,5.71, +3.5,6.06, +4.12,5.62, +4.5,5.36, +4.43,5.28, +3.96,4.72, +3.87,4.62, +3.87,4.62, +3.87,4.62, +0.48,0.58, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-10.91,1.92, +-10.91,1.92, +-10.92,1.89, +-10.91,1.92, +-11.78,1.58, +-12.05,0.03, +-12.05,0, +-12.05,0, +-12.47,0, +-12.98,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.05,0, +-13.88,-0.3, +-13.97,-0.46, +-13.97,-0.49, +-13.97,-1.56, +-14.75,-0.49, +-14.86,-1.53, +-15.35,-1.48, +-15.94,-0.03, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.65,0, +-15.02,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.9,1.07, +-14.79,2.23, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.38,2.54, +-13.8,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-12.77,3.16, +-12.81,2.33, +-12.12,3.29, +-9.87,4.63, +-7.08,6.9, +-6.33,7.54, +-5.98,7.13, +-5.78,7.65, +-4.96,8.59, +-4.96,8.59, +-4.89,8.65, +-3.65,10.02, +-3.79,10.41, +-2.41,10.81, +-2.06,11.68, +-2.09,11.87, +-2.09,11.87, +-1.76,12.43, +-0.03,13.02, +0,13.02, +0,13.17, +0,13.91, +0,14.02, +0.04,14.88, +0.73,15.02, +2.7,15.68, +2.84,16.08, +2.94,16.7, +3.13,17.77, +3.19,18.09, +3.32,18.84, +3.43,19.43, +3.5,19.84, +3.63,20.57, +3.68,20.84, +5.67,21.14, +7.55,20.73, +7.84,21.53, +7.89,21.68, +8.16,22.43, +8.32,22.86, +8.58,23.56, +8.77,24.09, +8.91,24.48, +9.1,25, +9.24,25.39, +9.41,25.85, +9.58,26.32, +9.75,26.79, +9.91,27.22, +10.16,27.92, +10.24,28.13, +10.24,28.13, +10.24,28.13, +10.24,28.13, +11.67,27.5, +14.7,25.47, +14.48,25.08, +14.48,25.08, +14.74,25.52, +14.89,25.8, +14.81,25.66, +15.96,25.18, +14.97,25.92, +18.3,24.61, +19.87,23.68, +19.55,23.3, +19.24,22.93, +18.8,22.4, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +19.78,20.87, +21.52,18.05, +21.29,17.8, +21.67,16.52, +22.19,15.33, +22.38,15.03, +21.08,16.82, +20.7,17.37, +20.7,17.37, +20.7,17.37, +20.7,17.37, +20.7,17.37, +20.46,17.16, +20,16.78, +19.48,16.85, +17.43,17.97, +15.98,19.04, +15.38,18.33, +15.34,18.28, +15.31,18.25, +12.59,18.02, +11.87,17.19, +13.94,16.61, +13.18,15.71, +11.75,14, +10.18,12.14, +8.22,9.79, +6.87,8.19, +6.02,7.84, +4.53,7.85, +4.26,7.38, +4.41,6.6, +4.93,6.24, +4.59,5.47, +4.5,5.36, +4.5,5.36, +4.11,4.9, +4.14,4.94, +4.23,5.35, +4.5,5.36, +4.36,5.46, +4.5,5.36, +4.05,4.83, +3.87,4.62, +3.87,4.62, +3.87,4.62, +3.98,4.51, +0.97,1.15, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-12.04,0.49, +-12.05,0, +-12.05,0, +-12.63,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.34,-0.48, +-13.92,-0.88, +-13.85,-1.92, +-13.96,-2.41, +-14.75,-2.34, +-14.74,-2.6, +-14.82,-2.55, +-15.73,-1.68, +-15.94,-0.06, +-15.94,0, +-15.94,0, +-15.96,0, +-16.04,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.92,0, +-15.21,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.86,1.59, +-14.76,2.45, +-14.75,2.53, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-13.97,2.46, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.76,2.43, +-13.04,2.3, +-12.83,2.26, +-12.7,2.24, +-11.52,2.59, +-7.11,6.76, +-5.91,7.04, +-5.75,6.85, +-5.73,6.87, +-4.81,7.94, +-4.94,8.56, +-4.86,8.64, +-3.57,9.71, +-3.79,10.41, +-2.69,10.72, +-2.07,11.73, +-2.09,11.87, +-2.09,11.87, +-2.12,12.03, +-0.98,12.93, +0,13.02, +0,13.02, +0,13.87, +0,14, +0,14.47, +0,14.97, +1.46,15.47, +2.8,15.89, +2.94,16.65, +3.04,17.23, +3.15,17.85, +3.28,18.59, +3.36,19.06, +3.48,19.72, +3.57,20.22, +3.65,20.68, +6.81,20.32, +7.52,20.66, +7.76,21.31, +7.86,21.6, +8.14,22.36, +8.22,22.57, +8.58,23.56, +8.63,23.72, +8.88,24.41, +8.96,24.61, +9.19,25.26, +9.3,25.55, +9.55,26.24, +9.57,26.3, +9.73,26.72, +9.91,27.22, +10.17,27.95, +10.22,28.08, +9.96,27.36, +10.08,27.7, +10.24,28.13, +14.55,25.44, +14.48,25.08, +14.71,25.48, +14.96,25.9, +14.68,25.42, +14.59,25.27, +14.97,25.92, +14.97,25.92, +15.33,26.56, +15.91,26.48, +18.7,24.13, +19.18,22.86, +18.71,22.3, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.62,22.19, +19.68,20.82, +21.25,18.26, +21.44,17.99, +21.31,17.88, +20.89,17.53, +20.7,17.37, +20.7,17.37, +20.74,17.4, +21,17.62, +21.14,17.74, +20.94,17.57, +20.5,17.93, +18.72,19.31, +17.1,19.85, +16.6,19.78, +16.2,19.3, +15.9,18.95, +14.93,18.9, +12.93,20.41, +10.83,19.58, +8.12,17.75, +13.98,16.66, +13.14,15.66, +11.87,14.15, +10.09,12.02, +8.68,10.35, +7.08,8.44, +6.06,7.22, +4.4,7.62, +4.33,6.81, +4.47,6.56, +5.03,6, +4.5,5.36, +4.5,5.36, +4.14,4.94, +3.87,4.62, +4.08,4.86, +4.01,4.78, +4.43,5.28, +4.19,4.99, +4.11,4.9, +3.87,4.62, +3.87,4.62, +3.87,4.62, +4.12,4.37, +3.98,4.51, +3,3.37, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.02,0, +-13.05,-0.09, +-13.66,-0.3, +-13.8,-2.34, +-13.9,-2.45, +-14.74,-2.6, +-14.74,-2.6, +-14.89,-2.63, +-15.73,-2.56, +-15.92,-0.42, +-15.94,0, +-15.94,0, +-16.11,0, +-16.58,0, +-16.33,0, +-16.06,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.7,0, +-14.99,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0.04, +-14.93,1.04, +-15.03,2.04, +-15.17,2.67, +-14.96,2.64, +-14.74,2.6, +-14.74,2.6, +-14.74,2.6, +-14.57,2.57, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-12.97,2.29, +-12.83,2.26, +-12.83,2.26, +-10.86,3.8, +-7.15,6.2, +-5.79,6.81, +-5.75,6.85, +-5.75,6.85, +-4.84,7.49, +-4.57,7.91, +-4.87,8.61, +-3.57,9.24, +-3.75,10.31, +-3.21,10.58, +-2.12,11.47, +-2.09,11.87, +-2.09,11.87, +-2.09,11.87, +-1.72,12.55, +-0.17,13.02, +0,13.02, +0,13.55, +0,14, +0,14.25, +0,14.94, +0.48,15.3, +2.69,15.68, +2.88,16.36, +2.97,16.82, +3.1,17.56, +3.19,18.09, +3.28,18.62, +3.38,19.14, +3.48,19.75, +4.23,20.06, +7.19,19.75, +7.39,20.3, +7.53,20.7, +7.81,21.45, +7.95,21.85, +8.18,22.47, +8.47,23.28, +8.58,23.56, +8.73,23.97, +8.91,24.48, +9.12,25.04, +9.24,25.39, +9.46,26, +9.57,26.3, +9.57,26.3, +9.84,27.03, +10.05,27.61, +10.24,28.13, +10.03,27.56, +9.99,27.45, +10.24,28.13, +13.65,25.88, +14.55,25.21, +14.97,25.92, +14.77,25.59, +14.48,25.08, +14.48,25.08, +14.76,25.57, +14.97,25.92, +15.06,26.09, +15.39,26.66, +15.91,25.48, +18.53,23.13, +18.62,22.19, +18.6,22.16, +18.21,21.7, +18.28,21.78, +18.62,22.19, +18.62,22.19, +18.62,22.19, +18.55,21.61, +19.82,19.65, +21.23,18.24, +21.44,17.99, +21.38,17.94, +21.38,17.94, +21.42,17.98, +20.78,18.71, +20.15,19.29, +19.35,20.13, +18.31,21.16, +17.63,21.01, +17.3,20.61, +16.92,20.17, +16.27,20.09, +14.44,20.75, +12.95,21.45, +12.41,21.49, +9.24,20.35, +5.87,18.81, +14.12,16.83, +13.58,16.19, +12.48,14.87, +11.66,13.12, +8.84,10.54, +7.01,8.36, +5.85,6.97, +5.19,7.09, +4.9,6.41, +4.9,6.11, +4.5,5.36, +4.5,5.36, +4.19,4.99, +3.87,4.62, +3.87,4.62, +3.87,4.62, +3.87,4.62, +4.08,4.86, +3.87,4.62, +4.19,4.99, +4.05,4.83, +3.87,4.62, +3.87,4.62, +3.61,4.1, +3.81,4.34, +2.12,2.14, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.02,0, +-13.52,-0.56, +-13.81,-2.26, +-14,-2.47, +-14.72,-2.59, +-14.74,-2.6, +-14.77,-2.6, +-15.63,-2.73, +-15.9,-0.75, +-15.94,0, +-15.94,0, +-15.96,0, +-16.61,0, +-16.84,0, +-16.11,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.25,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-15.04,0, +-15.38,0.04, +-15.67,2.15, +-15.72,2.65, +-15.37,2.47, +-14.77,2.41, +-14.76,2.49, +-14.74,2.6, +-14.71,2.59, +-14.34,2.53, +-13.84,2.44, +-13.78,2.43, +-13.78,2.43, +-13.76,2.43, +-13.02,2.3, +-12.83,2.26, +-12.83,2.26, +-11.6,3.12, +-6.92,5.86, +-5.92,6.69, +-5.75,6.85, +-5.75,6.85, +-5.32,7.16, +-4.47,7.74, +-4.78,8.29, +-3.66,9.22, +-3.66,10.05, +-3.79,10.41, +-2.56,11.18, +-2.09,11.87, +-2.09,11.87, +-2.09,11.87, +-2.12,12.04, +-0.49,12.99, +0,13.02, +0,13.2, +0,14, +0,14, +0,14.77, +0.11,14.97, +2.53,15.51, +2.78,15.75, +2.91,16.51, +2.97,16.82, +3.1,17.59, +3.16,17.95, +3.3,18.71, +3.47,19.04, +6.71,18.86, +7.02,19.29, +7.2,19.79, +7.51,20.64, +7.69,21.12, +7.87,21.62, +8.13,22.34, +8.27,22.73, +8.58,23.56, +8.58,23.59, +8.87,24.36, +8.91,24.48, +9.2,25.27, +9.33,25.62, +9.57,26.3, +9.57,26.3, +9.57,26.3, +9.84,27.03, +10.9,27.48, +10.77,27.89, +10.07,27.67, +10.36,28.46, +11.29,27.28, +14.91,25.82, +14.79,25.61, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.89,25.78, +14.97,25.92, +15.04,26.04, +14.97,25.92, +14.96,25.03, +17.6,22.88, +18.21,21.7, +17.99,21.44, +17.99,21.44, +18.32,21.83, +18.25,22.43, +18.09,22.57, +17.97,22.11, +17.99,21.44, +18.04,21.4, +18.39,21.06, +18.86,20.63, +19.44,20.05, +18.53,20.94, +18.05,21.4, +18.07,21.54, +18.05,21.52, +18.1,21.57, +17.98,21.52, +17.5,21.51, +15.48,22.23, +13.48,22.88, +13.08,22.65, +12.79,22.16, +11.48,22.2, +7.99,21.62, +4.46,19.39, +9.41,17.24, +13.49,16.08, +12.51,14.91, +11.25,13.4, +9.09,10.83, +7.1,8.47, +5.75,6.85, +5.59,6.66, +5.12,6.11, +4.78,6.01, +4.12,5.62, +4.27,5.35, +4.01,4.78, +3.87,4.62, +4,4.49, +4.15,4.34, +4.15,4.34, +3.87,4.62, +3.39,4.79, +3.63,4.79, +3.5,4.87, +3.77,4.69, +3.87,4.62, +3.64,3.82, +3.48,3.64, +1.11,0.93, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.74,-2.6, +-14.74,-2.6, +-14.93,-2.63, +-15.63,-2.76, +-15.81,-1.91, +-15.94,0, +-15.94,0, +-15.94,0, +-16.33,0, +-16.09,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.73,0, +-15,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.99,0, +-15.62,0, +-15.85,0.16, +-15.89,0.33, +-15.49,0.31, +-14.96,0.26, +-14.89,1.24, +-14.85,1.66, +-14.77,2.43, +-14.74,2.6, +-14.29,2.52, +-13.78,2.43, +-13.78,2.43, +-13.78,2.43, +-12.99,2.29, +-12.83,2.26, +-12.83,2.26, +-11.14,3.28, +-6.85,5.75, +-6.11,6.49, +-5.75,6.85, +-5.75,6.85, +-5.68,6.91, +-4.47,7.74, +-4.75,8.23, +-4.16,8.97, +-3.65,10.02, +-3.79,10.41, +-3.72,10.48, +-2.12,11.69, +-2.09,11.87, +-2.09,11.87, +-2.09,11.87, +-1.42,12.44, +0,13.02, +0,13.02, +0,13.61, +0,14, +0,14.12, +0,14.9, +2.36,14.89, +2.72,15.44, +2.79,15.83, +2.87,16.27, +2.95,16.73, +3.05,17.28, +4,17.64, +6.1,17.72, +6.56,18.02, +6.81,18.72, +7.03,19.31, +7.26,19.95, +7.51,20.64, +7.71,21.19, +7.93,21.8, +8.18,22.47, +8.43,23.15, +8.58,23.56, +8.67,23.82, +8.91,24.48, +9.06,24.89, +9.25,25.41, +9.55,26.25, +9.57,26.3, +10.47,25.92, +13.62,24.72, +14.54,25.18, +14.43,26.17, +13.98,26.6, +14.2,27.12, +14.02,26.47, +14.98,25.95, +14.52,25.16, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.86,25.74, +14.97,25.92, +14.86,25.74, +14.48,25.08, +14.77,24.6, +17.16,22.07, +17.93,21.49, +17.41,21.89, +16.18,22.74, +14.71,23.81, +14.28,24.73, +14.36,24.87, +16.31,22.65, +17.1,22.31, +18.01,21.47, +18.15,21.63, +18.28,21.78, +18.42,21.95, +18.49,22.04, +18.28,22.34, +17.69,22.85, +16.43,23.92, +15.18,24.57, +14.24,24.66, +13.94,24.14, +13.59,23.54, +12.99,23.45, +10.4,24.02, +8.86,23.39, +6.34,21.73, +1.82,19.08, +5.77,17.32, +13.88,16.55, +12.78,15.23, +11.43,13.63, +9.31,11.09, +6.8,8.1, +5.75,6.85, +5.12,6.11, +5.12,6.11, +4.8,5.93, +4.36,5.46, +4.22,5.26, +4.03,4.8, +3.87,4.62, +4.2,4.29, +4.62,3.87, +4.62,3.87, +4.51,3.98, +3.87,4.62, +3.5,4.87, +3.01,5.22, +3.63,4.79, +3.87,4.62, +3.6,3.52, +1.22,1.45, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-15.94,-0.09, +-15.94,-0.07, +-15.94,0, +-15.96,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.66,0, +-15.58,0, +-15.1,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-15.38,0, +-15.61,0, +-15.01,0, +-14.97,0, +-14.97,0, +-14.97,0.03, +-14.93,0.75, +-14.78,1.34, +-13.82,2.4, +-13.8,2.32, +-13.78,2.43, +-13.76,2.43, +-13.04,2.3, +-12.83,2.26, +-12.83,2.26, +-11.33,3.13, +-6.85,5.75, +-6.65,5.96, +-5.75,6.85, +-5.75,6.85, +-5.69,6.89, +-4.49,7.73, +-4.61,7.99, +-4.41,8.84, +-3.45,9.47, +-3.79,10.41, +-3.79,10.41, +-2.93,10.7, +-2.05,11.61, +-2.09,11.87, +-2.09,11.87, +-2.06,11.87, +-0.35,12.65, +0,13.02, +0,13.02, +0,13.61, +0,14, +0.1,14.08, +2.43,14.59, +2.6,14.74, +2.7,15.31, +2.77,15.72, +3.14,15.99, +5.26,15.96, +5.93,16.28, +6.17,16.95, +6.39,17.56, +6.6,18.13, +6.86,18.84, +7.14,19.62, +7.38,20.28, +7.6,20.89, +7.84,21.55, +8.07,22.17, +8.29,22.76, +8.55,23.49, +8.58,23.56, +8.84,24.29, +8.93,24.52, +9.17,25.21, +9.36,25.73, +10.67,25.83, +13.75,24.37, +14,24.24, +14.35,24.86, +14.83,25.68, +15.37,26.62, +15.82,27.4, +15.4,26.68, +14.65,25.38, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.51,25.13, +14.82,25.67, +14.79,25.61, +14.48,25.08, +14.29,24.75, +14,24.24, +14.08,24.17, +13.81,23.79, +13.66,23.66, +13.89,24.05, +14.2,24.6, +14.48,25.08, +14.36,24.87, +14.48,25.08, +15.27,24.56, +15.24,24.63, +15.61,24.58, +17.27,23.62, +15.78,25.15, +15.66,25.23, +14.86,25.74, +14.98,25.95, +14.85,25.71, +14.51,25.73, +13.33,25.54, +12.19,25.27, +9.54,25.87, +9.14,25.12, +8.77,24.09, +4.9,21.7, +1.3,19.26, +-3.15,16.54, +13.94,16.61, +13.1,15.62, +11.6,13.83, +10.14,12.09, +7.73,9.21, +5.9,7.04, +5.2,6.2, +4.4,6.32, +4.68,5.57, +4.5,5.36, +3.86,6.18, +3.95,4.71, +3.5,4.17, +3.09,2.98, +4.49,4, +3.96,3.32, +3.62,3.62, +3.87,4.62, +2.97,4.63, +2.26,3.91, +2.64,3.38, +3.08,3.67, +2.01,1.94, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.75,0, +-15.01,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-15.05,0, +-15,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.06,0.79, +-13.94,1.04, +-13.86,1.82, +-13.79,2.37, +-13.59,2.4, +-12.83,2.26, +-12.83,2.26, +-11.33,3.13, +-6.85,5.75, +-6.81,5.79, +-5.78,6.82, +-5.75,6.85, +-5.75,6.85, +-4.73,7.57, +-4.68,8.1, +-4.9,8.62, +-3.54,9.28, +-3.66,10.05, +-3.79,10.41, +-3.44,10.51, +-2,10.92, +-2.03,11.49, +-2.09,11.87, +-2.09,11.87, +-0.45,12.12, +0,12.52, +0,12.98, +0,13.08, +0,13.41, +1.02,13.72, +2.43,13.78, +2.51,14.23, +2.6,14.77, +3.13,14.77, +5.25,14.92, +5.46,15, +5.72,15.71, +5.9,16.2, +6.11,16.78, +6.35,17.44, +6.6,18.14, +6.83,18.77, +7.15,19.65, +7.41,20.37, +7.6,20.88, +7.86,21.6, +8.12,22.31, +8.37,22.99, +8.58,23.56, +8.6,23.62, +8.88,24.41, +9.01,24.75, +9.31,25.39, +13.01,24.6, +14,24.24, +14.43,25, +14.48,25.08, +14.92,25.84, +15.77,27.31, +16.18,28.02, +15.65,27.1, +15.02,26.02, +14.96,25.9, +14.83,25.69, +14.52,25.15, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.76,25.56, +14.7,25.46, +14.48,25.08, +14.38,24.91, +14,24.24, +13.8,23.9, +13.51,23.4, +13.51,23.4, +13.72,23.76, +14.07,24.36, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.54,25.19, +14.82,25.67, +14.96,25.9, +14.98,25.95, +15.08,26.12, +15.28,26.47, +15.38,26.65, +15.39,26.65, +12.72,27.64, +10.99,27.79, +10.11,27.56, +9.87,27.13, +9.74,26.76, +9.12,26.12, +7.45,24.95, +3.75,21.89, +-1.06,18.15, +-4.24,16.31, +2.3,16.61, +13.18,15.71, +12.16,14.49, +10.5,12.51, +7.79,9.29, +5.64,6.73, +5.03,6, +4.21,5.56, +3.64,5.96, +3.94,5.16, +3.94,4.7, +3.98,4.74, +1.94,2.31, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.94,0, +-15.36,0, +-14.97,0, +-14.97,0, +-15.02,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.86,0, +-14.05,0, +-14,0.02, +-13.99,0.31, +-13.82,2.16, +-13.49,2.35, +-12.83,2.26, +-12.83,2.26, +-11.12,3.26, +-6.85,5.75, +-6.85,5.75, +-5.99,6.62, +-5.75,6.85, +-5.75,6.85, +-4.73,7.57, +-4.62,8, +-4.96,8.59, +-3.66,9.2, +-3.41,9.38, +-3.7,10.17, +-3.79,10.41, +-2.2,10.84, +-1.92,10.91, +-1.99,11.29, +-2.09,11.87, +-0.97,11.98, +0,12.05, +0,12.12, +0,12.66, +0.03,13.02, +1.71,12.88, +2.34,13.25, +2.43,13.78, +2.9,13.8, +4.88,13.96, +5.14,14.11, +5.36,14.72, +5.46,15, +5.73,15.75, +5.92,16.28, +6.18,16.99, +6.43,17.68, +6.67,18.33, +6.92,19.02, +7.21,19.81, +7.49,20.59, +7.7,21.14, +7.93,21.78, +8.17,22.44, +8.44,23.18, +8.58,23.56, +8.65,23.77, +8.91,24.48, +9.93,24.74, +13.63,23.61, +14,24.24, +14.09,24.41, +14.35,24.86, +14.7,25.46, +15.5,26.84, +15.98,27.67, +15.36,26.6, +14.94,25.88, +14.62,25.31, +14.65,25.37, +14.46,25.04, +14.47,25.06, +14.38,24.91, +14.21,24.62, +14.41,24.96, +14.63,25.35, +14.52,25.15, +14.48,25.08, +14.48,25.08, +14.13,24.47, +13.79,23.88, +13.51,23.4, +13.51,23.4, +13.58,23.53, +12.23,25.08, +12.21,25.99, +13.25,25.69, +13.75,25.46, +14.89,25.78, +14.74,26.05, +14.79,26.22, +15.31,26.53, +14.13,27.43, +13.92,28.24, +12.86,28.94, +11.35,29.59, +10.58,29.07, +10.54,28.96, +10.26,28.2, +8.88,28.19, +6.87,28.09, +5.13,27.71, +4.64,26.32, +3.33,22.8, +-1.5,18.04, +-4.01,16.38, +-6.36,15.77, +12.09,16.47, +12.14,14.47, +10.42,12.42, +8.64,10.3, +5.93,7.06, +5.05,6.02, +3.78,5.86, +3.5,6.06, +3.69,5.04, +3.87,4.62, +3.87,4.62, +0.55,0.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-15.45,0, +-15.02,0, +-15.19,0, +-15.26,0, +-14.97,0, +-15.14,0, +-15,0, +-15.05,0, +-14.99,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.66,0, +-14.02,0, +-14,0, +-14,0.03, +-13.97,0.55, +-13.65,1.26, +-12.91,1.51, +-12.83,2.26, +-11.34,2.2, +-6.85,5.75, +-6.85,5.75, +-6.63,5.97, +-5.8,6.81, +-5.75,6.85, +-4.71,7.58, +-4.52,7.83, +-4.8,8.31, +-3.92,9.08, +-3.39,9.32, +-3.39,9.32, +-3.58,9.84, +-3.3,10.53, +-1.92,10.91, +-1.92,10.91, +-1.93,10.93, +-1.01,11.3, +0,11.69, +0,11.73, +0,12.05, +0.5,12.09, +2.16,12.28, +2.23,12.66, +2.34,12.97, +4,12.98, +4.74,13.01, +4.94,13.59, +5.14,14.12, +5.27,14.48, +5.47,15.02, +5.68,15.6, +5.92,16.26, +6.22,17.09, +6.42,17.63, +6.68,18.35, +6.95,19.09, +7.21,19.82, +7.5,20.62, +7.82,21.48, +7.96,21.86, +8.21,22.55, +8.48,23.29, +8.58,23.56, +8.81,24.22, +10.12,24.07, +13.51,23.4, +13.98,24.22, +14,24.24, +14.02,24.29, +14.4,24.94, +14.83,25.68, +15.15,26.24, +14.94,25.88, +14.49,25.1, +14.47,25.06, +14.2,24.6, +14.09,24.41, +14.01,24.27, +14.02,24.28, +14,24.24, +14.12,24.45, +14.49,25.1, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.19,24.58, +13.79,23.88, +13.51,23.4, +12.91,23.71, +10.37,24.89, +9.5,26.09, +9.61,26.39, +9.9,27.2, +9.99,27.45, +10.66,27.96, +10.6,27.99, +10.47,28.77, +10.95,28.89, +10.67,29.3, +10.97,30.14, +10.97,30.14, +10.95,30.09, +10.6,29.12, +10.36,29.04, +7.08,29.03, +5.34,29.45, +5.11,28.95, +5.02,28.46, +4.81,27.29, +1.24,22.95, +-1.85,17.95, +-4.65,16.2, +-6.29,16.29, +1.41,15.49, +11.02,15.74, +10.33,12.86, +8.46,10.08, +5.32,7.65, +3.78,6.54, +3.43,5.94, +2.8,6.23, +0.44,0.76, +1.66,1.98, +1.11,1.32, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.97,0, +-15.81,0, +-15.42,0, +-15.47,0, +-15.48,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.85,0, +-14.27,0, +-14,0, +-14,0, +-13.97,0, +-13.14,0.06, +-12.98,0.84, +-12.7,1.24, +-10.82,1.23, +-6.94,5.64, +-6.85,5.75, +-6.85,5.75, +-6.2,6.4, +-5.75,6.85, +-4.84,7.49, +-4.47,7.74, +-4.51,7.81, +-4.21,8.35, +-3.38,9.28, +-3.39,9.32, +-3.39,9.32, +-3.53,9.91, +-2.29,10.79, +-1.92,10.91, +-1.92,10.91, +-0.72,11, +0,11.06, +0,11.08, +0.02,11.18, +1.6,11.34, +2.07,11.75, +2.11,11.94, +3.09,11.99, +4.34,12.07, +4.48,12.32, +4.75,13.05, +4.88,13.4, +5.11,14.04, +5.3,14.57, +5.5,15.12, +5.77,15.86, +5.99,16.46, +6.28,17.26, +6.5,17.87, +6.75,18.54, +7.09,19.48, +7.28,19.99, +7.55,20.74, +7.82,21.48, +7.99,21.96, +8.26,22.7, +8.5,23.35, +8.58,23.59, +10.25,23.63, +13.22,22.89, +13.59,23.53, +13.98,24.22, +14,24.24, +14.29,24.75, +14.2,24.6, +14.49,25.1, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14,24.24, +14,24.24, +14,24.24, +14,24.24, +14,24.24, +14.17,24.54, +14.48,25.08, +14.48,25.08, +14.48,25.08, +14.48,25.08, +13.15,25.16, +11,25.14, +9.99,25.06, +9.28,25.14, +9.23,25.36, +9.28,25.49, +9.58,26.33, +9.85,27.06, +9.94,27.31, +10.24,28.13, +10.29,28.26, +10.56,29.02, +10.65,29.27, +10.91,29.98, +10.97,30.14, +10.97,30.14, +10.97,30.14, +8.94,29.68, +6.25,30.14, +5.2,29.48, +5.2,29.48, +5.2,29.48, +4.1,28.76, +1.41,28.44, +0,24.07, +-2.34,18.43, +-5.11,16.09, +-6.28,16.37, +-5.84,15.07, +-3.51,14.12, +-4.49,12.37, +4.84,10.43, +4.69,8.13, +3.12,6.85, +2.18,6.49, +1.19,5.87, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-15.55,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.97,0, +-14.6,0, +-14,0, +-14,0, +-13.75,0, +-13.07,0, +-13.02,0, +-12.69,0, +-11.57,0.5, +-7.11,5.39, +-6.85,5.75, +-6.85,5.75, +-6.76,5.85, +-6.04,6.57, +-5.2,7.25, +-4.47,7.74, +-4.47,7.74, +-4.37,7.8, +-3.32,8.37, +-3.34,9.17, +-3.39,9.32, +-3.39,9.32, +-2.25,9.78, +-1.78,10.09, +-1.81,10.25, +-0.46,10.18, +0,10, +0,10.26, +0.39,10.53, +1.88,10.65, +1.94,11.01, +1.96,11.13, +2.48,11.4, +3.82,11.45, +3.87,11.78, +4.3,12.24, +4.66,12.81, +4.8,13.2, +5.07,13.93, +5.26,14.46, +5.46,15, +5.72,15.71, +5.94,16.31, +6.25,17.17, +6.52,17.9, +6.77,18.61, +7.1,19.5, +7.31,20.09, +7.56,20.78, +7.84,21.55, +7.99,21.95, +8.21,22.57, +8.56,23.52, +10.27,22.92, +12.97,22.46, +13.23,22.92, +13.7,23.74, +13.97,24.2, +14,24.24, +14.01,24.26, +14.41,24.96, +14.02,24.29, +14.37,24.89, +14.48,25.08, +14.05,24.34, +14,24.24, +14,24.24, +14,24.24, +14,24.24, +14.04,24.31, +14.45,25.02, +14.48,25.08, +14.48,25.08, +14.35,24.85, +10.09,26.09, +9.42,25.89, +9.24,25.39, +9.21,25.31, +9.19,25.26, +9.24,25.39, +9.55,26.25, +9.77,26.85, +9.98,27.42, +10.24,28.13, +10.29,28.27, +10.57,29.04, +10.69,29.38, +10.97,30.14, +10.36,30.31, +8.21,30.9, +6.59,31.31, +5.38,30.51, +5.29,30, +5.2,29.48, +4.6,29.54, +1.71,29.81, +0,29.3, +0,28.85, +-0.85,24.9, +-3.81,18.44, +-5.94,16.31, +-6.21,15.83, +-8.46,14.65, +-8.12,14.06, +-7.33,12.7, +-2.9,11.23, +4.07,9.66, +2.48,7.98, +1.09,6.72, +0,3.66, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-14.97,0, +-14.51,0, +-14,0, +-14,0, +-13.54,0, +-13.02,0, +-13,0, +-12.33,0, +-10.21,1.92, +-7.6,4.68, +-7.34,5.07, +-7.06,5.46, +-6.85,5.75, +-6.13,6.48, +-5.56,6.9, +-4.47,7.74, +-4.47,7.74, +-4.47,7.74, +-3.79,8.08, +-3.1,8.52, +-3.34,9.18, +-3.39,9.32, +-2.77,9.49, +-1.72,9.76, +-1.72,9.76, +-0.42,9.76, +0,9.72, +0,9.83, +1.22,9.75, +1.74,9.86, +1.8,10.19, +1.87,10.6, +1.92,10.88, +2.13,11.06, +2.01,11.39, +2.62,11.83, +4.42,12.14, +4.65,12.79, +4.8,13.18, +5.06,13.91, +5.28,14.51, +5.48,15.06, +5.78,15.89, +6.08,16.7, +6.31,17.33, +6.61,18.15, +6.88,18.91, +7.14,19.61, +7.44,20.44, +7.61,20.9, +7.84,21.55, +8.04,22.1, +8.21,22.56, +11.08,22.08, +12.55,21.74, +12.85,22.26, +13.24,22.94, +13.51,23.4, +13.9,24.07, +14,24.24, +14.01,24.27, +14,24.24, +14.06,24.35, +14.14,24.49, +14,24.24, +14,24.24, +13.98,24.22, +13.69,23.72, +13.56,23.48, +13.83,23.95, +14.28,24.73, +14.38,24.91, +14.34,24.83, +13.65,24.49, +10.24,25.89, +9.27,25.48, +9.24,25.39, +9.24,25.39, +9.24,25.39, +9.24,25.39, +9.49,26.07, +9.72,26.71, +9.93,27.28, +10.21,28.04, +10.33,28.38, +10.57,29.04, +10.6,29.32, +7.81,31.03, +6.12,31.47, +5.57,31.59, +5.49,31.12, +5.37,30.44, +5.25,29.77, +4.22,29.41, +0.81,29.09, +0,29.64, +0,29.35, +0,28.8, +-2.61,25.09, +-5.32,19.38, +-6.27,17.22, +-7.36,15.41, +-8.46,14.65, +-8.22,14.24, +-7.55,13.08, +-7.04,12.19, +-1.67,12.13, +1.09,10.92, +0.17,0.98, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-13.02,0, +-12.98,0, +-12.41,0, +-11.57,0.5, +-7.74,4.47, +-7.74,4.47, +-7.63,4.65, +-6.95,5.6, +-6.14,5.71, +-5.12,6.11, +-4.41,6.72, +-4.17,7.22, +-4.4,7.62, +-4.47,7.74, +-3.65,8.14, +-3.1,8.52, +-3.18,8.75, +-2.91,9, +-1.68,9.29, +-1.6,9.06, +-0.43,8.93, +0,8.94, +0.09,8.94, +1.55,8.95, +1.62,9.21, +1.68,9.55, +1.81,9.63, +1.74,9.86, +1.79,10.15, +1.88,10.67, +2.62,11.04, +4.16,11.43, +4.35,11.94, +4.57,12.56, +4.83,13.28, +5,13.75, +5.24,14.41, +5.52,15.16, +5.78,15.89, +6.12,16.8, +6.41,17.61, +6.71,18.45, +7,19.23, +7.22,19.84, +7.51,20.62, +7.68,21.11, +7.84,21.55, +9.5,21.33, +11.69,20.84, +12.29,21.29, +12.57,21.78, +12.83,22.22, +13.22,22.89, +13.58,23.52, +13.86,24, +14,24.24, +14,24.24, +14,24.24, +14,24.24, +14,24.24, +13.98,24.22, +13.56,23.49, +13.3,23.04, +13.41,23.23, +13.56,23.48, +13.97,24.19, +13.91,24.09, +13.91,24.09, +12.77,24.29, +9.31,25.57, +9.24,25.39, +9.24,25.39, +9.24,25.39, +9.24,25.39, +9.24,25.39, +9.48,26.05, +9.63,26.46, +9.91,27.22, +10.21,28.05, +10.31,28.34, +10.56,29.02, +8.21,29.85, +5.57,31.59, +5.57,31.59, +5.52,31.32, +5.4,30.6, +5.33,30.25, +4.28,29.61, +0.35,29.37, +0,28.96, +0,28.96, +0,28.96, +0,28.96, +-4.41,25.34, +-6.41,20.38, +-6.74,17.91, +-8.91,15.44, +-8.57,14.84, +-8.42,14.58, +-8.01,13.87, +-7.65,13.25, +-5.54,13.88, +-2.96,8.14, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-7.74,4.47, +-7.74,4.47, +-7.74,4.47, +-7.66,4.42, +-6.29,5.2, +-5.59,5.65, +-5.12,6.11, +-4.61,6.48, +-3.98,6.9, +-4.03,6.98, +-4.42,7.65, +-4.44,7.76, +-3.23,8.33, +-3.06,8.4, +-2.87,8.46, +-1.55,8.81, +-1.55,8.77, +-0.39,8.57, +0,8.33, +0.67,8.31, +1.5,8.53, +1.55,8.78, +1.82,8.74, +1.86,8.96, +1.58,8.97, +1.66,9.4, +1.74,9.89, +3.09,10.31, +3.88,10.66, +4.13,11.35, +4.34,11.93, +4.59,12.6, +4.86,13.36, +5.06,13.91, +5.36,14.74, +5.68,15.6, +5.94,16.31, +6.24,17.15, +6.59,18.11, +6.81,18.7, +7.08,19.45, +7.27,19.97, +7.5,20.62, +9.07,20.53, +11.44,19.82, +11.63,20.14, +11.95,20.71, +12,20.78, +12.35,21.4, +12.74,22.07, +13.12,22.73, +13.49,23.36, +13.55,23.47, +13.76,23.84, +13.77,23.85, +13.75,23.81, +13.76,23.84, +13.13,22.75, +12.81,22.19, +12.73,22.05, +12.9,22.35, +13.02,22.56, +13.42,23.25, +13.45,23.3, +13.38,23.18, +10.92,24.08, +9.04,24.84, +9.09,24.97, +9.15,25.14, +9.24,25.39, +9.24,25.39, +9.25,25.41, +9.55,26.25, +9.64,26.49, +9.91,27.22, +10.21,28.04, +10.24,28.13, +10.02,28.91, +5.89,30.32, +5.4,30.61, +5.46,30.94, +5.38,30.53, +5.37,30.44, +5.17,29.71, +1.48,29.61, +0,28.99, +0,28.77, +0,28.96, +0,28.71, +-4.01,27.66, +-4.64,25.46, +-7.68,21.54, +-8.43,18.67, +-9.34,16.18, +-9.08,15.72, +-8.62,14.93, +-8.31,14.39, +-7.97,13.8, +-5.6,14.92, +-4.67,12.84, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-5.72,5.52, +-5.12,6.11, +-5.12,6.11, +-4.28,6.7, +-3.98,6.9, +-4.12,7.13, +-4.39,7.6, +-4.17,7.8, +-2.96,8.07, +-2.85,7.88, +-1.86,7.9, +-1.39,7.89, +-0.73,7.91, +0,7.95, +0.66,7.83, +1.39,7.88, +1.5,7.9, +2.36,7.84, +2.15,7.79, +1.46,8.3, +1.52,8.64, +1.58,8.97, +2.94,9.29, +3.57,9.81, +3.8,10.44, +4.09,11.22, +4.32,11.87, +4.6,12.65, +4.89,13.43, +5.2,14.27, +5.47,15.03, +5.77,15.85, +6.09,16.73, +6.37,17.51, +6.64,18.24, +6.88,18.89, +7.92,19.39, +10.14,18.96, +10.98,19.02, +11.29,19.55, +11.47,19.86, +11.51,19.93, +11.74,20.33, +12.05,20.87, +12.44,21.55, +12.95,22.43, +13.4,23.21, +13.51,23.4, +13.51,23.4, +13.2,22.87, +12.93,22.39, +12.55,21.74, +12.07,20.9, +12.12,21, +12.05,20.87, +12.41,21.5, +12.64,21.88, +12.95,22.43, +12.95,22.44, +12.24,22.42, +8.51,23.38, +8.45,23.22, +7.64,24.27, +5.5,25.5, +7.21,25.83, +8.68,25.56, +9.34,25.66, +9.57,26.3, +9.68,26.6, +9.91,27.22, +10.11,27.79, +9.61,28.32, +5.77,29.73, +5.36,30.38, +5.37,30.44, +5.37,30.44, +5.36,30.39, +5.24,29.74, +2.47,29.6, +0,29.13, +0,28.65, +0,27.99, +0,28.16, +-1.89,27.88, +-4.8,27.24, +-4.98,25.43, +-8.1,22.26, +-9.53,19.47, +-9.96,17.26, +-9.37,16.22, +-8.93,15.46, +-8.44,14.62, +-7.62,13.97, +-5.57,15.29, +-4.09,11.23, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.98,6.9, +-3.94,6.95, +-2.84,7.44, +-2.73,7.49, +-1.64,7.57, +-1.28,7.27, +-0.76,7.26, +0,7.02, +1.01,6.92, +1.34,7.33, +2.57,7.16, +2.58,7.1, +2.03,7.33, +1.32,7.51, +1.38,7.82, +2.09,8.25, +3.12,8.56, +3.34,9.19, +3.6,9.9, +3.9,10.72, +4.16,11.42, +4.48,12.31, +4.83,13.28, +5.1,14.02, +5.43,14.91, +5.65,15.53, +5.93,16.28, +6.26,17.19, +6.51,17.87, +6.9,18.61, +9.65,18.01, +10.5,18.18, +10.91,18.9, +11.02,19.09, +11.31,19.59, +11.47,19.86, +11.47,19.86, +11.71,20.29, +12.27,21.25, +12.65,21.91, +13.05,22.6, +13.38,23.17, +13.51,23.4, +13.12,22.73, +12.73,22.05, +12.34,21.38, +11.95,20.71, +11.77,20.81, +11.64,20.87, +11.99,20.77, +12.34,21.38, +12.5,21.64, +12.02,21.87, +9.41,22.78, +8.23,22.62, +8.22,22.58, +5.69,24.43, +4.5,25.53, +4.67,26.51, +7.58,26.3, +9.53,26.19, +9.57,26.3, +9.83,27.01, +9.91,27.22, +9.7,27.56, +6.47,29.19, +5.2,29.48, +5.23,29.65, +5.24,29.7, +5.27,29.86, +5.2,29.48, +4.13,29.35, +0,29.07, +0,28.7, +0,28.09, +0,27.97, +0,27.68, +-3.83,26.89, +-4.62,26.23, +-6.72,24.14, +-8.14,22.03, +-10.42,19.63, +-10.29,17.82, +-9.39,16.27, +-8.59,14.88, +-7.8,13.52, +-6.92,13.22, +-5.36,14.51, +-5.31,14.59, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-3.34,7.15, +-2.47,6.8, +-1.96,6.69, +-1.2,6.83, +-0.66,6.72, +0,6.61, +1.08,6.74, +1.39,6.85, +2.39,6.58, +2.33,6.41, +1.86,6.58, +1.22,6.82, +1.53,7.05, +2.6,7.43, +2.84,7.81, +3.11,8.54, +3.37,9.26, +3.62,9.94, +4,10.98, +4.35,11.95, +4.63,12.72, +4.98,13.68, +5.25,14.43, +5.54,15.21, +5.84,16.05, +6.16,16.92, +6.46,17.74, +8.1,17.66, +9.98,17.39, +10.5,18.18, +10.72,18.57, +10.98,19.02, +11.15,19.32, +11.47,19.86, +11.47,19.86, +11.77,20.39, +12.05,20.87, +12.53,21.7, +12.93,22.39, +13.02,22.56, +13.28,23, +13.02,22.56, +12.86,22.27, +12.03,21.9, +9.57,21.95, +8.61,22.27, +8.27,22.43, +9.1,22.12, +9.34,22.02, +9.37,22.06, +8.4,22.38, +8.19,22.49, +8.21,22.56, +8.28,23.49, +4.68,25.05, +4.57,25.94, +4.7,26.64, +7.62,26.8, +9.57,26.3, +9.63,26.46, +9.88,27.15, +9.91,27.22, +6.57,28.14, +5.1,28.91, +5.18,29.36, +5.2,29.48, +5.18,29.4, +5.13,29.12, +5,28.78, +2.16,28.71, +0,28.4, +0,28.05, +0,27.7, +0,27.07, +-0.34,26.98, +-4.45,26.03, +-4.43,25.15, +-6.82,23.08, +-9.2,21.04, +-10.79,18.69, +-10.05,17.41, +-7.76,13.44, +-7.89,13.66, +-6.66,11.53, +-6.29,10.89, +-4.92,12.85, +-2.76,8.47, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.11,6.32, +-0.45,5.99, +0.13,6.02, +1.06,5.99, +1.6,6.04, +2.14,5.87, +2.06,5.66, +2.05,5.64, +1.61,5.89, +2.19,6.26, +2.44,6.7, +2.68,7.38, +2.83,7.78, +3.16,8.69, +3.49,9.58, +3.86,10.59, +4.2,11.53, +4.6,12.63, +4.85,13.31, +5.19,14.27, +5.5,15.11, +5.76,15.82, +6.11,16.78, +6.33,17.38, +7.04,17.75, +9.96,17.34, +10.3,17.84, +10.53,18.24, +10.96,18.98, +10.98,19.02, +11.28,19.55, +11.47,19.86, +11.55,20, +11.95,20.71, +12.44,21.55, +12.69,21.98, +13.02,22.56, +13.05,22.6, +13.02,22.56, +12.89,22.33, +9.45,23.19, +8.45,23.22, +8.21,22.56, +8.32,22.86, +8.49,23.33, +8.66,23.79, +8.64,23.75, +8.59,23.59, +8.57,23.54, +8.64,23.75, +8.75,24.04, +7.68,25.11, +5.81,26.27, +8.23,26.4, +9.58,26.33, +9.57,26.3, +9.74,26.75, +9.91,27.22, +8.59,27.6, +5.03,28.52, +5.03,28.52, +5.03,28.52, +5.03,28.55, +5.04,28.59, +5.03,28.52, +4.53,28.15, +0.13,28.13, +0,27.85, +0,27.46, +0,27.02, +0,26.73, +-2.13,26.05, +-4.43,25.1, +-4.87,23.84, +-7.75,21.28, +-6.69,14.38, +-2.86,4.95, +0,0, +0,0, +-1.65,2.86, +-5.75,9.96, +-5.39,9.33, +-4.23,11.01, +-3.2,12.13, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.06,5.66, +2.06,5.66, +1.89,5.19, +1.79,4.91, +1.92,5.27, +2.05,5.64, +2.19,6.03, +2.4,6.6, +2.66,7.31, +2.94,8.09, +3.22,8.85, +3.66,10.07, +4.01,11.01, +4.42,12.16, +4.75,13.05, +5.06,13.91, +5.36,14.71, +5.68,15.59, +5.95,16.35, +6.2,17.03, +6.5,17.86, +9.07,17.37, +10.14,17.56, +10.5,18.18, +10.76,18.64, +10.98,19.02, +11.23,19.46, +11.47,19.86, +11.66,20.2, +11.95,20.71, +12.44,21.55, +12.54,21.72, +12.9,22.34, +13.02,22.56, +13.02,22.56, +12.77,22.64, +9.31,23.35, +8.58,23.56, +8.55,23.5, +8.56,23.52, +8.83,24.25, +9.03,24.82, +9.09,24.98, +9.15,24.96, +10.47,24.45, +9.17,25, +9.21,25.3, +9.38,25.78, +9.35,26.29, +9.78,26.87, +9.88,27.14, +9.83,27.01, +9.91,27.22, +9.91,27.22, +8.26,27.67, +5.03,28.52, +5.02,28.5, +4.99,28.28, +4.95,28.04, +4.91,27.84, +4.87,27.59, +2.74,27.66, +0,27.31, +0,27.05, +0,26.98, +0,26.33, +0,25.98, +-3.96,25.34, +-4.28,24.28, +-5.13,23.09, +-7.29,20.03, +-0.84,2.31, +0,0, +0,0, +0,0, +0,0, +-0.56,0.97, +-2.9,4.95, +-3.9,8.98, +-2.04,7.71, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +1.73,4.75, +1.86,5.11, +2.06,5.66, +2.24,6.14, +2.42,6.6, +2.74,7.53, +3.08,8.47, +3.41,9.37, +3.86,10.61, +4.25,11.69, +4.54,12.47, +4.9,13.46, +5.21,14.33, +5.46,15, +5.77,15.85, +6.1,16.75, +6.32,17.35, +7.94,17.39, +10,17.32, +10.24,17.74, +10.52,18.22, +10.91,18.9, +10.98,19.02, +11.37,19.7, +11.48,19.89, +11.92,20.64, +12.26,21.23, +12.54,21.72, +12.57,21.78, +13.02,22.56, +13.02,22.56, +12.94,22.41, +12.02,21.97, +8.81,23.47, +8.58,23.56, +8.58,23.56, +8.9,24.45, +8.95,24.58, +9.24,25.39, +11.88,24.19, +13.53,23.44, +11.27,24.86, +9.48,26.04, +9.59,26.35, +9.77,26.85, +10.09,27.71, +10.13,27.83, +9.95,27.33, +9.91,27.22, +9.89,27.16, +8.95,27.16, +4.93,27.98, +4.88,27.65, +4.86,27.57, +4.81,27.3, +4.79,27.16, +4.7,26.98, +0.2,27.01, +0,26.7, +0,26.39, +0,26.08, +0,25.85, +0,25.29, +-3.63,24.17, +-4.12,23.38, +-5.33,18.49, +-4.94,13.59, +-0.93,2.56, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.81,4.78, +-1.65,7.35, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.42,6.46, +2.54,6.99, +2.87,7.88, +3.28,9.01, +3.68,10.11, +4.01,11.03, +4.37,12.02, +4.68,12.86, +5,13.73, +5.27,14.47, +5.56,15.27, +5.9,16.21, +6.17,16.96, +7.1,17.43, +9.74,16.88, +10.12,17.53, +10.5,18.18, +10.62,18.39, +10.96,18.98, +11.15,19.31, +11.4,19.74, +11.75,20.35, +12.94,20.25, +15.56,19.31, +14.39,20.46, +13.02,22.26, +13.02,22.56, +12.89,22.33, +12.54,21.72, +11.95,22.01, +10.43,22.72, +9.4,23.4, +8.87,24.36, +10.31,24.12, +11.86,24.15, +13.43,23.61, +13.98,24.22, +13.56,24.46, +11.62,25.41, +10.1,27.01, +10.02,27.53, +10.38,28.52, +10.45,28.7, +10.18,27.97, +9.92,27.24, +9.75,26.78, +9.31,26.39, +5.45,27.41, +4.84,27.45, +4.75,26.94, +4.68,26.55, +4.66,26.42, +3.86,26.5, +0,26.35, +0,26.03, +0,25.51, +0,25.35, +0,25.05, +-0.83,24.16, +-4.08,23.13, +-2.84,16.08, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-1.03,4.99, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +3.48,9.56, +3.9,10.7, +4.12,11.33, +4.38,12.03, +4.75,13.04, +5.01,13.75, +5.31,14.59, +5.63,15.46, +5.9,16.2, +6.38,16.94, +9.46,16.39, +9.75,16.88, +10.12,17.53, +10.47,18.14, +10.62,18.4, +10.95,18.97, +11.06,19.15, +11.45,19.84, +12.01,20.15, +15.5,18.55, +16.12,19.21, +16.02,19.32, +13.87,20.91, +12.58,21.79, +12.54,21.72, +12.54,21.72, +12.57,21.77, +12.93,22.39, +12.36,22.89, +13.3,23.18, +13.55,23.46, +14,24.24, +14,24.24, +14.01,24.26, +14.26,24.7, +14.52,25.29, +12.97,26.86, +12.85,27.99, +10.86,28.85, +10.3,28.29, +10.01,27.51, +9.81,26.96, +9.57,26.3, +8.31,26.36, +4.68,26.54, +4.63,26.28, +4.54,25.75, +4.49,25.44, +3.16,25.67, +0,25.42, +0,25.08, +0,24.98, +0,24.47, +0,23.84, +-0.5,23.2, +-3.9,22.13, +-3.27,18.52, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +-0.34,1.91, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +4.26,11.69, +4.45,12.24, +4.8,13.19, +5.09,13.97, +5.38,14.77, +5.74,15.67, +8.35,15.53, +9.11,15.78, +9.52,16.5, +9.94,17.21, +10.09,17.48, +10.47,18.14, +10.63,18.41, +10.89,18.85, +11.03,19.11, +11.55,19.65, +14.27,18.7, +15.63,18.63, +16.1,19.19, +16.12,19.21, +15.38,19.77, +13.41,21.56, +12.87,22.29, +12.98,22.47, +13.06,22.62, +13.38,23.18, +13.54,23.45, +13.92,24.11, +14,24.24, +14.07,24.37, +14.48,25.08, +14.48,25.08, +14.83,25.68, +15.25,26.41, +15.69,27.18, +15.58,27.3, +11.83,27.98, +10.06,27.65, +9.81,26.96, +9.48,26.05, +9.21,25.31, +5.29,25.74, +4.47,25.36, +4.37,24.8, +4.35,24.66, +3.67,24.61, +0,24.52, +0,24.1, +0,23.96, +0,23.66, +0,23.08, +-1.37,22.31, +-3.73,21.17, +-3.03,17.2, +-4.18,18.82, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +5.38,14.38, +7.49,14.23, +8.39,14.53, +8.72,15.11, +9.16,15.87, +9.48,16.43, +9.79,16.96, +10.1,17.49, +10.45,18.1, +10.58,18.33, +10.89,18.85, +11,19.05, +12.92,18.87, +15.27,18.2, +15.74,18.76, +16.12,19.21, +16.17,19.27, +16.73,19.93, +15.31,20.98, +14.4,21.76, +13.84,22.89, +13.81,23.21, +13.68,23.69, +14,24.24, +14,24.24, +14.26,24.7, +14.48,25.08, +14.59,25.27, +15.02,26.01, +15.4,26.67, +15.67,27.15, +15.65,27.1, +13.23,27.39, +10.08,27.69, +9.62,26.42, +9.21,25.31, +8.98,24.66, +6.3,24.78, +4.32,24.5, +4.26,24.15, +4.21,23.86, +2.72,23.59, +0,23.71, +0,23.46, +0,23.03, +0,22.8, +0,22.27, +-1.39,21.47, +-3.6,20.44, +-3.49,19.8, +-3.87,18.72, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +8.84,15.32, +9.01,15.6, +9.33,16.16, +9.59,16.62, +9.9,17.15, +10.13,17.55, +10.47,18.13, +10.51,18.21, +10.87,18.83, +11.59,18.94, +14.12,17.95, +15.24,18.17, +15.71,18.72, +16.03,19.1, +16.31,19.43, +16.74,19.95, +16.81,20.04, +17.37,20.7, +16.84,21.07, +16.67,21.88, +16.89,22.21, +16.61,22.44, +14.91,24.01, +14.32,24.81, +14.53,25.17, +14.9,25.8, +15.16,26.26, +15.3,26.49, +15.2,26.32, +13.16,26.73, +9.81,26.97, +9.37,25.76, +9.04,24.84, +8.77,24.09, +5.89,24.29, +4.17,23.64, +4.15,23.55, +4.13,23.45, +3.95,22.76, +1.69,22.82, +0,22.94, +0,22.38, +0,22.04, +0,21.33, +-2.61,20.52, +-3.5,19.85, +-3.41,19.31, +-3.32,18.82, +-1.68,9.52, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +10.01,17.34, +10.01,17.34, +10.11,17.51, +10.5,18.18, +10.57,18.3, +10.95,18.96, +12.29,18.73, +14.85,17.7, +15.21,18.12, +15.59,18.58, +16.05,19.12, +16.28,19.4, +16.73,19.94, +17.15,20.44, +17.33,20.66, +17.37,20.7, +17.47,20.83, +17.8,21.21, +17.41,21.79, +13.95,24.16, +14.04,24.31, +14.26,24.7, +14.4,24.93, +14.59,25.28, +13.93,25.49, +10.03,26.8, +9.48,26.04, +9.25,25.42, +8.95,24.59, +8.68,23.85, +7.63,23.83, +4.2,23.81, +4.15,23.55, +4.04,22.89, +3.98,22.59, +3.41,22.62, +0,22.24, +0,21.94, +0,21.24, +0,20.56, +-2.11,19.77, +-3.38,19.17, +-3.29,18.66, +-3.22,18.23, +-2.97,16.82, +-0.68,3.83, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +10.5,18.18, +10.75,18.62, +10.98,19.02, +12.17,18.6, +14.4,17.86, +15.14,18.04, +15.42,18.37, +15.88,18.93, +16.31,19.43, +16.65,19.84, +16.98,20.23, +17.15,20.44, +16.69,20.83, +15,22, +13.66,23.19, +13.35,23.12, +13.38,23.17, +13.64,23.63, +13.67,23.68, +13.84,23.97, +10.29,25.61, +9.34,25.66, +9.25,25.42, +9.08,24.96, +8.91,24.48, +8.71,23.93, +8.58,23.56, +6.33,23.96, +4.23,23.99, +4.16,23.6, +4.08,23.14, +3.97,22.54, +1.98,21.92, +0,21.36, +0,20.7, +0,19.88, +-0.66,19.11, +-3.11,18.32, +-3.16,17.93, +-3.12,17.69, +-2.95,16.75, +-0.69,5.82, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +11.03,19.11, +12.55,18.95, +14.37,17.85, +14.92,17.78, +15.31,18.25, +15.67,18.68, +16.02,19.1, +16.39,19.53, +16.56,19.74, +14.14,21.28, +12.81,22.18, +12.87,22.29, +12.91,22.37, +12.81,22.18, +12.93,22.39, +13.08,22.66, +10.37,24.14, +9.02,24.79, +9.02,24.79, +9,24.73, +8.91,24.48, +8.91,24.48, +8.91,24.48, +8.79,24.16, +8.58,23.56, +7.21,23.95, +4.48,24.27, +4.15,23.52, +4.03,22.83, +3.82,21.66, +1.15,20.94, +0,20.27, +0,19.27, +0,18.27, +-0.43,17.87, +-2.39,17.16, +-2.88,16.76, +-2.04,16.16, +-0.09,15.91, +0,9.21, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +14.74,17.57, +15.23,18.26, +14.57,18.84, +14.75,19.37, +12.93,20.67, +12.16,21.07, +12.16,21.07, +12.2,21.13, +12.36,21.4, +12.42,21.52, +12.51,21.67, +12.6,21.82, +10.84,22.96, +8.89,24.42, +8.91,24.48, +8.91,24.48, +8.91,24.48, +8.91,24.48, +8.91,24.48, +8.79,24.14, +8.58,23.56, +8.58,23.56, +6.29,24.21, +4.32,24.48, +4.11,23.33, +3.86,21.89, +3.37,20.54, +0.5,19.62, +0,18.47, +0,17.72, +0,17.02, +-0.04,16.69, +-0.46,16.17, +-0.32,15.95, +0,15.75, +0,14.73, +0,3.97, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +11.76,20.37, +11.88,20.58, +11.7,20.27, +11.68,20.22, +11.69,20.24, +11.94,20.68, +11.95,20.71, +12.03,20.83, +12.36,21.4, +12.15,21.92, +9.48,23.19, +8.7,23.9, +8.86,24.35, +8.91,24.48, +8.91,24.48, +8.87,24.37, +8.67,23.81, +8.58,23.56, +8.58,23.56, +8.42,23.62, +4.83,24.56, +4.16,23.6, +3.93,22.27, +3.67,20.79, +1.91,19.21, +0,18.13, +0,17.05, +0,16.35, +0,15.83, +0,15.81, +0,15.47, +0,14.97, +0,14.97, +0,9.31, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +11.47,19.86, +11.47,19.86, +11.48,19.89, +11.69,20.24, +11.95,20.71, +11.97,20.73, +12.2,21.13, +12.14,21.91, +9.72,23.07, +8.63,23.7, +8.82,24.23, +8.83,24.26, +8.67,23.82, +8.58,23.56, +8.58,23.56, +8.58,23.56, +8.58,23.56, +7,24.01, +4.22,23.93, +4.01,22.73, +3.68,20.89, +3.03,19.06, +0.77,17.85, +0,16.62, +0,15.66, +0,15.07, +0,14.85, +0,14.9, +0,14.97, +0.15,14.96, +1.42,11.06, +1.05,5.93, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +11.78,20.41, +11.95,20.71, +12.01,20.8, +12.37,21.43, +12.54,21.72, +10.63,22.64, +9.09,23.33, +8.58,23.56, +8.58,23.56, +8.58,23.56, +8.57,23.54, +8.52,23.4, +8.51,23.38, +8.53,23.44, +4.47,23.55, +4.03,22.83, +3.72,21.08, +3.39,19.2, +2.69,17.62, +0,16.2, +0,15.11, +0,14.36, +0,14, +0,14.27, +0.66,14.83, +1.76,14.83, +2.6,14.74, +2.61,12.86, +1.36,3.74, +1.4,3.84, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +2.51,4.34, +0,0, +0,0, +0,0, +1.23,3.37, +0,0, +0,0, +0,0, +0,0, +0.92,2.52, +0,0, +0,0, +0,0, +0.42,2.39, +0,0, +0,0, +0,0, +0,0, +0,1.75, +0,0, +0,0, +0,0, +0.37,2.11, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0, +0,0 +] +}; \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/util/ColorPaletteGenerator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/util/ColorPaletteGenerator.java index 4b5b28c8204..f2c85b4dfd6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/util/ColorPaletteGenerator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/util/ColorPaletteGenerator.java @@ -3,7 +3,7 @@ package com.sap.sailing.gwt.ui.simulator.util; public class ColorPaletteGenerator implements ColorPalette { //private String[] colors = {"Green","Red","Blue","Yellow","Black"}; - private String[] colors = {"#00ED00","#FFBB00","#FFEA00","#0092C7","#0092C7","#666666","Blue","Blue","Yellow","Black"}; + private String[] colors = {"#00ED00","#FFBB00","#FFEA00","#0bf","#0bf","#808080","Blue","Blue","Yellow","Black"}; private int currentIndex = -1; public ColorPaletteGenerator() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/WindPatternSetting.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/WindPatternSetting.java index 9f0aeee36c8..cc144bc25fd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/WindPatternSetting.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/WindPatternSetting.java @@ -2,7 +2,7 @@ package com.sap.sailing.gwt.ui.simulator.windpattern; import java.util.List; -import com.sap.sailing.domain.common.Named; +import com.sap.sse.common.Named; public interface WindPatternSetting extends Named { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/EventOverviewPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/EventOverviewPanel.java deleted file mode 100644 index 84d99343601..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/EventOverviewPanel.java +++ /dev/null @@ -1,646 +0,0 @@ -package com.sap.sailing.gwt.ui.spectator; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Date; -import java.util.List; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.FontWeight; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; -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.user.cellview.client.CellTable; -import com.google.gwt.user.cellview.client.Column; -import com.google.gwt.user.cellview.client.ColumnSortEvent; -import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; -import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.TextBox; -import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.NoSelectionModel; -import com.google.gwt.view.client.SelectionChangeEvent; -import com.google.gwt.view.client.SingleSelectionModel; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.PlacemarkOrderDTO; -import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.domain.common.impl.NaturalComparator; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.URLEncoder; -import com.sap.sailing.gwt.ui.shared.EventDTO; -import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sse.gwt.client.player.Timer; -import com.sap.sse.gwt.client.player.Timer.PlayModes; - -/** - * An overview panel for sailing events - * @author Frank Mittag (C5163874) - */ -public class EventOverviewPanel extends FormPanel { - - interface AnchorTemplates extends SafeHtmlTemplates { - @SafeHtmlTemplates.Template("{1}") - SafeHtml anchor(String url, String displayName); - } - - public static final String STYLE_NAME_PREFIX = "eventOverviewPanel-"; - private static final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); - - private final SailingServiceAsync sailingService; - private final ErrorReporter errorReporter; - private final StringMessages stringMessages; - - private TextBox locationTextBox; - private TextBox nameTextBox; - private TextBox fromTextBox; - private TextBox untilTextBox; - private CheckBox onlyLiveCheckBox; - - private CellTable eventsTable; - private ListDataProvider eventsDataProvider; - private SingleSelectionModel eventSelectionModel; - - private FlowPanel eventDetailsPanel; - private HTML eventDescriptionHTML; - private CellTable leaderboardsTable; - private ListDataProvider leaderboardsDataProvider; - private SingleSelectionModel leaderboardsSelectionModel; - - private FlowPanel leaderboardDetailsPanel; - private CellTable racesTable; - private NoSelectionModel racesSelectionModel; - private ListDataProvider racesDataProvider; - - private List availableEvents; - private final boolean showRaceDetails; - private final Timer timerForClientServerOffset; - - private final LeaderboardGroupOverviewTableResources tableResources = GWT.create(LeaderboardGroupOverviewTableResources.class); - - public EventOverviewPanel(SailingServiceAsync sailingService, ErrorReporter errorReporter, - StringMessages stringMessages, boolean showRaceDetails) { - this.showRaceDetails = showRaceDetails; - this.sailingService = sailingService; - this.errorReporter = errorReporter; - this.stringMessages = stringMessages; - this.timerForClientServerOffset = new Timer(PlayModes.Replay); - availableEvents = new ArrayList(); - - FlowPanel mainPanel = new FlowPanel(); - this.setWidget(mainPanel); - mainPanel.setSize("100%", "100%"); - - mainPanel.add(createSearchGUI()); - mainPanel.add(createEventsGUI(tableResources)); - mainPanel.add(createEventDetailsGUI(tableResources)); - - loadEvents(); - } - - private Panel createSearchGUI() { - FlowPanel searchPanel = new FlowPanel(); - searchPanel.setWidth("100%"); - - FlowPanel header = new FlowPanel(); - searchPanel.add(header); - Label headerLabel = new Label(stringMessages.searchEvents()); - headerLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); - header.add(headerLabel); - - Label locationLabel = new Label(this.stringMessages.location() + ":"); - locationLabel.setStyleName(STYLE_NAME_PREFIX + "searchLabel"); - locationLabel.getElement().getStyle().setFloat(Style.Float.LEFT); - searchPanel.add(locationLabel); - locationTextBox = new TextBox(); - locationTextBox.setStyleName(STYLE_NAME_PREFIX + "searchInputField"); - locationTextBox.getElement().getStyle().setFloat(Style.Float.LEFT); - locationTextBox.addKeyUpHandler(new KeyUpHandler() { - @Override - public void onKeyUp(KeyUpEvent event) { - onSearchCriteriaChange(); - } - }); - searchPanel.add(locationTextBox); - - Label nameLabel = new Label(this.stringMessages.name() + ":"); - nameLabel.setStyleName(STYLE_NAME_PREFIX + "searchLabel"); - nameLabel.getElement().getStyle().setFloat(Style.Float.LEFT); - searchPanel.add(nameLabel); - nameTextBox = new TextBox(); - nameTextBox.setStyleName(STYLE_NAME_PREFIX + "searchInputField"); - nameTextBox.getElement().getStyle().setFloat(Style.Float.LEFT); - nameTextBox.addKeyUpHandler(new KeyUpHandler() { - @Override - public void onKeyUp(KeyUpEvent event) { - onSearchCriteriaChange(); - } - }); - searchPanel.add(nameTextBox); - - onlyLiveCheckBox = new CheckBox(this.stringMessages.onlyLiveEvents()); - onlyLiveCheckBox.setStyleName(STYLE_NAME_PREFIX + "searchCheckBox"); - onlyLiveCheckBox.getElement().getStyle().setFloat(Style.Float.LEFT); - onlyLiveCheckBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - onCheckBoxLiveChange(); - } - }); - searchPanel.add(onlyLiveCheckBox); - - Label fromDateLabel = new Label(this.stringMessages.from() + ":"); - fromDateLabel.setStyleName(STYLE_NAME_PREFIX + "searchLabel"); - fromDateLabel.getElement().getStyle().setFloat(Style.Float.LEFT); - searchPanel.add(fromDateLabel); - fromTextBox = new TextBox(); - fromTextBox.setStyleName(STYLE_NAME_PREFIX + "searchInputField"); - fromTextBox.getElement().getStyle().setFloat(Style.Float.LEFT); - fromTextBox.addKeyUpHandler(new KeyUpHandler() { - @Override - public void onKeyUp(KeyUpEvent event) { - onSearchCriteriaChange(); - } - }); - searchPanel.add(fromTextBox); - - Label toDateLabel = new Label(this.stringMessages.until() + ":"); - toDateLabel.setStyleName(STYLE_NAME_PREFIX + "searchLabel"); - toDateLabel.getElement().getStyle().setFloat(Style.Float.LEFT); - searchPanel.add(toDateLabel); - untilTextBox = new TextBox(); - untilTextBox.setStyleName(STYLE_NAME_PREFIX + "searchInputField"); - untilTextBox.getElement().setPropertyString("clear", "right"); - untilTextBox.addKeyUpHandler(new KeyUpHandler() { - @Override - public void onKeyUp(KeyUpEvent event) { - onSearchCriteriaChange(); - } - }); - searchPanel.add(untilTextBox); - - return searchPanel; - } - - private Panel createEventsGUI(LeaderboardGroupOverviewTableResources tableResources) { - FlowPanel eventsPanel = new FlowPanel(); - eventsPanel.setStyleName(STYLE_NAME_PREFIX + "eventsPanel"); - - FlowPanel header = new FlowPanel(); - eventsPanel.add(header); - Label headerLabel = new Label(stringMessages.events()); - headerLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); - header.add(headerLabel); - - AnchorCell eventNameAnchorCell = new AnchorCell(); - Column eventNameColumn = new Column(eventNameAnchorCell) { - @Override - public SafeHtml getValue(EventDTO event) { - SafeHtmlBuilder builder = new SafeHtmlBuilder(); - builder.appendHtmlConstant(event.getName()); - return builder.toSafeHtml(); - } - }; - eventNameColumn.setSortable(true); - - TextColumn eventVenueColumn = new TextColumn() { - @Override - public String getValue(EventDTO event) { - return event.venue.getName(); - } - }; - eventVenueColumn.setSortable(true); - - TextColumn eventLeaderboardsColumn = new TextColumn() { - @Override - public String getValue(EventDTO event) { - String result = ""; - if(event.leaderboardGroup != null) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (StrippedLeaderboardDTO leaderboard : event.leaderboardGroup.leaderboards) { - if (!first) { - sb.append(", "); - } - sb.append(leaderboard.name); - first = false; - } - result = sb.toString(); - } - return result; - } - }; - eventLeaderboardsColumn.setSortable(false); - - TextColumn eventStartDateColumn = new TextColumn() { - @Override - public String getValue(EventDTO event) { - if(event.leaderboardGroup != null) { - Date startDate = event.leaderboardGroup.getGroupStartDate(); - return startDate == null ? EventOverviewPanel.this.stringMessages.untracked() - : DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT).format(startDate); - } else { - return ""; - } - } - }; - eventStartDateColumn.setSortable(true); - - eventsTable = new CellTable(10000, tableResources); - eventsTable.setWidth("100%"); - eventSelectionModel = new SingleSelectionModel(); - eventSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - onEventSelectionChanged(); - } - }); - eventsTable.setSelectionModel(eventSelectionModel); - - eventsTable.addColumn(eventNameColumn, this.stringMessages.name()); - eventsTable.addColumn(eventVenueColumn, this.stringMessages.venue()); - eventsTable.addColumn(eventLeaderboardsColumn, this.stringMessages.leaderboards()); - eventsTable.addColumn(eventStartDateColumn, this.stringMessages.startDate()); - eventsPanel.add(eventsTable); - - eventsDataProvider = new ListDataProvider(); - eventsDataProvider.addDataDisplay(eventsTable); - - ListHandler eventsListHandler = new ListHandler(eventsDataProvider.getList()); - eventsListHandler.setComparator(eventNameColumn, new Comparator() { - @Override - public int compare(EventDTO e1, EventDTO e2) { - return new NaturalComparator().compare(e1.getName(), e2.getName()); - } - }); - eventsListHandler.setComparator(eventVenueColumn, new Comparator() { - @Override - public int compare(EventDTO e1, EventDTO e2) { - return e1.venue.getName().compareTo(e2.venue.getName()); - } - }); - eventsListHandler.setComparator(eventStartDateColumn, new Comparator() { - @Override - public int compare(EventDTO e1, EventDTO e2) { - Date d1 = e1.leaderboardGroup != null ? e1.leaderboardGroup.getGroupStartDate() : null; - Date d2 = e2.leaderboardGroup != null ? e2.leaderboardGroup.getGroupStartDate() : null; - if (d1 == null || d2 == null) { - return d1 == null && d2 == null ? 0 : d1 == null ? -1 : 1; - } else { - return d1.compareTo(d2); - } - } - }); - eventsTable.addColumnSortHandler(eventsListHandler); - - return eventsPanel; - } - - private Panel createEventDetailsGUI(LeaderboardGroupOverviewTableResources tableResources) { - eventDetailsPanel = new FlowPanel(); - eventDetailsPanel.setStyleName(STYLE_NAME_PREFIX + "eventDetailsPanel"); - eventDetailsPanel.setVisible(false); - - FlowPanel header = new FlowPanel(); - eventDetailsPanel.add(header); - Label headerLabel = new Label(stringMessages.details()); - headerLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); - header.add(headerLabel); - - //Build group details GUI - FlowPanel detailsPanel = new FlowPanel(); - detailsPanel.setVisible(false); - detailsPanel.setSize("100%", "100%"); - detailsPanel.setStyleName(STYLE_NAME_PREFIX + "detailsPanel"); - eventDetailsPanel.add(detailsPanel); - - eventDescriptionHTML = new HTML(); - eventDescriptionHTML.setStyleName(STYLE_NAME_PREFIX + "eventDescription"); - detailsPanel.add(eventDescriptionHTML); - - Label eventDetailsLabel = new Label(stringMessages.leaderboards()+ ": "); - eventDetailsLabel.getElement().getStyle().setPadding(5, Unit.PX); - detailsPanel.add(eventDetailsLabel); - - TextColumn leaderboardsLocationColumn = new TextColumn() { - @Override - public String getValue(StrippedLeaderboardDTO leaderboard) { - PlacemarkOrderDTO leaderboardPlaces = leaderboard.getPlaces(); - return leaderboardPlaces == null ? EventOverviewPanel.this.stringMessages - .notAvailable() : leaderboardPlaces.placemarksAsString(); - } - }; - - AnchorCell leaderboardsNameAnchorCell = new AnchorCell(); - Column leaderboardsNameColumn = new Column(leaderboardsNameAnchorCell) { - @Override - public SafeHtml getValue(StrippedLeaderboardDTO leaderboard) { - EventDTO selectedGroup = eventSelectionModel.getSelectedObject(); - String debugParam = Window.Location.getParameter("gwt.codesvr"); - String link = URLEncoder.encode("/gwt/Leaderboard.html?name=" + leaderboard.name - + (showRaceDetails ? "&showRaceDetails=true" : "") - + "&leaderboardGroupName=" + selectedGroup.getName() + "&root=overview" - + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); - return ANCHORTEMPLATE.anchor(link, leaderboard.name); - } - }; - - TextColumn leaderboardsRacesColumn = new TextColumn() { - @Override - public String getValue(StrippedLeaderboardDTO leaderboard) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (RaceColumnDTO race : leaderboard.getRaceList()) { - if (!first) { - sb.append(", "); - } - sb.append(race.getRaceColumnName()); - first = false; - } - return sb.toString(); - } - }; - - TextColumn leaderboardsStartDateColumn = new TextColumn() { - @Override - public String getValue(StrippedLeaderboardDTO leaderboard) { - Date leaderboardStart = leaderboard.getStartDate(); - return leaderboardStart == null ? EventOverviewPanel.this.stringMessages.untracked() - : DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT).format(leaderboardStart); - } - }; - - leaderboardsTable = new CellTable(10000, tableResources); - leaderboardsTable.setWidth("100%"); - leaderboardsSelectionModel = new SingleSelectionModel(); - leaderboardsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { - - @Override - public void onSelectionChange(SelectionChangeEvent event) { - onLeaderboardSelectionChanged(); - } - }); - leaderboardsTable.setSelectionModel(leaderboardsSelectionModel); - - leaderboardsTable.addColumn(leaderboardsLocationColumn, this.stringMessages.location()); - leaderboardsTable.addColumn(leaderboardsNameColumn, this.stringMessages.name()); - leaderboardsTable.addColumn(leaderboardsRacesColumn, this.stringMessages.races()); - leaderboardsTable.addColumn(leaderboardsStartDateColumn, this.stringMessages.startDate()); - detailsPanel.add(leaderboardsTable); - - leaderboardsDataProvider = new ListDataProvider(); - leaderboardsDataProvider.addDataDisplay(leaderboardsTable); - - //Build leaderboard details GUI - leaderboardDetailsPanel = new FlowPanel(); - leaderboardDetailsPanel.setSize("100%", "100%"); - leaderboardDetailsPanel.setStyleName(STYLE_NAME_PREFIX + "leaderboardDetailsPanel"); - leaderboardDetailsPanel.setVisible(false); - leaderboardDetailsPanel.getElement().setPropertyString("clear", "right"); - eventDetailsPanel.add(leaderboardDetailsPanel); - - Label leaderboardDetailsLabel = new Label(stringMessages.racesInLeaderboard() + ":"); - leaderboardDetailsLabel.getElement().getStyle().setPadding(5, Unit.PX); - leaderboardDetailsPanel.add(leaderboardDetailsLabel); - - TextColumn racesLocationColumn = new TextColumn() { - @Override - public String getValue(RaceColumnDTO race) { - PlacemarkOrderDTO racePlaces = race.getPlaces(); - return racePlaces == null ? EventOverviewPanel.this.stringMessages.locationNotAvailable() : racePlaces.placemarksAsString(); - } - }; - - AnchorCell racesNameAnchorCell = new AnchorCell(); - Column racesNameColumn = new Column(racesNameAnchorCell) { - @Override - public SafeHtml getValue(RaceColumnDTO race) { - SafeHtml name = null; - Iterable fleets = race.getFleets(); - boolean singleFleet = Util.size(fleets) < 2; - for (FleetDTO fleet : fleets) { - String raceDisplayName; - if (singleFleet) { - raceDisplayName = race.getRaceColumnName(); - } else { - raceDisplayName = race.getRaceColumnName() + "("+fleet+")"; - } - if (race.getRaceIdentifier(fleet) != null) { - EventDTO selectedGroup = eventSelectionModel.getSelectedObject(); - StrippedLeaderboardDTO selectedLeaderboard = leaderboardsSelectionModel.getSelectedObject(); - RegattaNameAndRaceName raceId = (RegattaNameAndRaceName) race.getRaceIdentifier(fleet); - String debugParam = Window.Location.getParameter("gwt.codesvr"); - String link = URLEncoder.encode("/gwt/RaceBoard.html?leaderboardName=" - + selectedLeaderboard.name + "&raceName=" + raceId.getRaceName() + "®attaName=" - + raceId.getRegattaName() + "&leaderboardGroupName=" + selectedGroup.getName() - + "&root=overview" - + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); - name = ANCHORTEMPLATE.anchor(link, raceDisplayName); - } else { - name = new SafeHtmlBuilder().appendHtmlConstant(raceDisplayName).toSafeHtml(); - } - } - return name; - } - }; - - TextColumn racesStartDateColumn = new TextColumn() { - @Override - public String getValue(RaceColumnDTO race) { - StringBuilder result = new StringBuilder(); - boolean first = true; - for (FleetDTO fleet : race.getFleets()) { - Date raceStart = race.getStartDate(fleet); - if (first) { - first = false; - } else { - result.append(", "); - } - result.append(raceStart == null ? EventOverviewPanel.this.stringMessages.untracked() - : DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT).format(raceStart)); - } - return result.toString(); - } - }; - - racesTable = new CellTable(200, tableResources); - racesTable.setWidth("100%"); - racesSelectionModel = new NoSelectionModel(); - racesTable.setSelectionModel(racesSelectionModel); - - racesTable.addColumn(racesLocationColumn, this.stringMessages.location()); - racesTable.addColumn(racesNameColumn, this.stringMessages.name()); - racesTable.addColumn(racesStartDateColumn, this.stringMessages.startDate()); - leaderboardDetailsPanel.add(racesTable); - - racesDataProvider = new ListDataProvider(); - racesDataProvider.addDataDisplay(racesTable); - - return eventDetailsPanel; - } - - private void loadEvents() { - final long clientTimeWhenRequestWasSent = System.currentTimeMillis(); - sailingService.getEvents(new AsyncCallback>() { - @Override - public void onSuccess(List result) { - final long clientTimeWhenResponseWasReceived = System.currentTimeMillis(); - if (result != null && !result.isEmpty()) { - timerForClientServerOffset.adjustClientServerOffset(clientTimeWhenRequestWasSent, - result.get(result.size() - 1).getCurrentServerTime(), clientTimeWhenResponseWasReceived); - } - availableEvents = new ArrayList(result); - eventsDataProvider.getList().clear(); - eventsDataProvider.getList().addAll(availableEvents); - } - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to obtain the data: " + t.getMessage()); - } - }); - } - - private void onEventSelectionChanged() { - EventDTO selectedEvent = eventSelectionModel.getSelectedObject(); - StrippedLeaderboardDTO selectedLeaderboard = leaderboardsSelectionModel.getSelectedObject(); - - if (selectedEvent != null) { - eventDetailsPanel.setVisible(true); - if (selectedLeaderboard != null) { - setLeaderboardDetailsVisible(selectedEvent.leaderboardGroup.leaderboards.contains(selectedLeaderboard)); - } - fillEventDetails(selectedEvent); - } else { - eventDetailsPanel.setVisible(false); - setLeaderboardDetailsVisible(false); - } - } - - private void fillEventDetails(EventDTO event) { - if(event.leaderboardGroup != null) { - eventDescriptionHTML.setHTML(new SafeHtmlBuilder().appendEscapedLines(event.leaderboardGroup.description).toSafeHtml()); - leaderboardsDataProvider.getList().clear(); - leaderboardsDataProvider.getList().addAll(event.leaderboardGroup.leaderboards); - } - } - - private void onLeaderboardSelectionChanged() { - StrippedLeaderboardDTO selectedLeaderboard = leaderboardsSelectionModel.getSelectedObject(); - setLeaderboardDetailsVisible(selectedLeaderboard != null); - if (selectedLeaderboard != null) { - fillLeaderboardDetails(selectedLeaderboard); - } - } - - private void setLeaderboardDetailsVisible(boolean visible) { - leaderboardDetailsPanel.setVisible(visible); - } - - private void fillLeaderboardDetails(StrippedLeaderboardDTO leaderboard) { - racesDataProvider.getList().clear(); - racesDataProvider.getList().addAll(leaderboard.getRaceList()); - } - - private void onCheckBoxLiveChange() { - if (onlyLiveCheckBox.getValue()) { - String today = DateTimeFormat.getFormat("dd.MM.yyyy").format(new Date()).toString(); - - fromTextBox.setText(today); - fromTextBox.setEnabled(false); - untilTextBox.setText(""); - untilTextBox.setEnabled(false); - } else { - fromTextBox.setText(""); - fromTextBox.setEnabled(true); - untilTextBox.setText(""); - untilTextBox.setEnabled(true); - } - onSearchCriteriaChange(); - } - - private void onSearchCriteriaChange() { - //Get search criteria - String location = locationTextBox.getText(); - String name = nameTextBox.getText(); - boolean onlyLive = onlyLiveCheckBox.getValue(); - - Date from = null; - Date until = null; - try { - from = DateTimeFormat.getFormat("dd.MM.yyyy").parse(fromTextBox.getText()); - until = DateTimeFormat.getFormat("dd.MM.yyyy").parse(untilTextBox.getText()); - //Adding 24 hours to until, so that it doesn't result in an empty table if 'from' and 'until' are equal. - //Instead you'll have a 24 hour range - long time = until.getTime() + 24 * 60 * 60 * 1000; - until = new Date(time); - } catch (IllegalArgumentException e) {} - - eventsDataProvider.getList().clear(); - for (EventDTO group : availableEvents) { - if (checkSearchCriteria(group, location, name, onlyLive, from, until)) { - eventsDataProvider.getList().add(group); - } - } - //Now sort again according to selected criterion - ColumnSortEvent.fire(eventsTable, eventsTable.getColumnSortList()); - } - - private boolean checkSearchCriteria(EventDTO event, String location, String name, boolean onlyLive, Date from, Date until) { - boolean result = true; - if (result && location != null && location.length() > 0) { - result = textContainsStringsToCheck( - PlacemarkOrderDTO.placemarksOfAllOrderAsSeperatedString(event.leaderboardGroup.getGroupPlaces(), true), - location.split("\\s")); - } - if (result && name != null && name.length() > 0) { - result = textContainsStringsToCheck(event.getName(), name.split("\\s")); - } - if (result && onlyLiveCheckBox.getValue()) { - result = event.leaderboardGroup.hasLiveRace(timerForClientServerOffset.getLiveTimePointInMillis()); - }else if (result) { - Date startDate = event.leaderboardGroup.getGroupStartDate(); - if (startDate != null) { - if (from != null && until != null) { - result = from.before(startDate) && until.after(startDate); - } else if (from != null) { - result = from.before(startDate); - } else if (until != null) { - result = until.after(startDate); - } - } - } - - return result; - } - - private boolean textContainsStringsToCheck(String text, String[] stringsToCheck) { - boolean contains = false; - for (String stringToCheck : stringsToCheck) { - contains = text.toLowerCase().contains(stringToCheck.toLowerCase()); - if (!contains) { - break; - } - } - return contains; - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupOverviewPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupOverviewPanel.java index 5595d7ba266..946af4cd897 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupOverviewPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupOverviewPanel.java @@ -39,7 +39,6 @@ import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.PlacemarkOrderDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.impl.NaturalComparator; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -48,6 +47,7 @@ import com.sap.sailing.gwt.ui.client.URLEncoder; import com.sap.sailing.gwt.ui.client.shared.components.CollapsablePanel; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java index b407f7fb492..59e30d9567c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java @@ -32,7 +32,6 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell; import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; import com.sap.sailing.gwt.ui.client.ErrorReporter; @@ -45,6 +44,7 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; 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.common.Util; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; @@ -103,14 +103,16 @@ public class LeaderboardGroupPanel extends SimplePanel implements HasWelcomeWidg private final boolean isEmbedded; private final boolean showRaceDetails; private final boolean canReplayDuringLiveRaces; + private final boolean showMapControls; private final Timer timerForClientServerOffset; public LeaderboardGroupPanel(SailingServiceAsync sailingService, StringMessages stringConstants, ErrorReporter errorReporter, final String groupName, String root, String viewMode, boolean embedded, - boolean showRaceDetails, boolean canReplayDuringLiveRaces) { + boolean showRaceDetails, boolean canReplayDuringLiveRaces, boolean showMapControls) { super(); this.isEmbedded = embedded; this.showRaceDetails = showRaceDetails; + this.showMapControls = showMapControls; this.canReplayDuringLiveRaces = canReplayDuringLiveRaces; this.sailingService = sailingService; this.stringMessages = stringConstants; @@ -384,12 +386,11 @@ public class LeaderboardGroupPanel extends SimplePanel implements HasWelcomeWidg private List getRacesOfFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet) { List racesColumnsOfFleet = new ArrayList(); - for (RaceColumnDTO raceColumn : series.getRaceColumns()) { for (FleetDTO fleetOfRaceColumn : series.getFleets()) { - if(fleet.equals(fleetOfRaceColumn)) { - //We have to get the race column from the leaderboard, because the race column of the series - //have no tracked race and would be displayed as inactive race. + if (fleet.equals(fleetOfRaceColumn)) { + // We have to get the race column from the leaderboard, because the race column of the series + // have no tracked race and would be displayed as inactive race. racesColumnsOfFleet.add(leaderboard.getRaceColumnByName(raceColumn.getName())); } } @@ -422,6 +423,7 @@ public class LeaderboardGroupPanel extends SimplePanel implements HasWelcomeWidg if (canReplayDuringLiveRaces) { linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true"); } + linkParams.put(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, Boolean.toString(showMapControls)); linkParams.put("regattaName", raceIdentifier.getRegattaName()); linkParams.put("leaderboardGroupName", leaderboardGroup.getName()); if (viewMode != null && !viewMode.isEmpty()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java index 70d30f70ceb..a773915e8a2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java @@ -20,7 +20,8 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.shared.panels.SimpleWelcomeWidget; import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; -import com.sap.sse.gwt.server.GwtHttpRequestUtils; +import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sse.gwt.shared.GwtHttpRequestUtils; /** * @@ -34,12 +35,14 @@ public class SpectatorEntryPoint extends AbstractEntryPoint implements RegattaRe protected void doOnModuleLoad() { super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); String groupParamValue = Window.Location.getParameter("leaderboardGroupName"); String viewModeParamValue = Window.Location.getParameter("viewMode"); final boolean canReplayDuringLiveRaces = GwtHttpRequestUtils.getBooleanParameter( RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, /* defaultValue */ false); + final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter( + RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, /* defaultValue */ true); boolean showRaceDetails = Window.Location.getParameter("showRaceDetails") != null && Window.Location.getParameter("showRaceDetails").equalsIgnoreCase("true"); final String groupName; @@ -90,7 +93,7 @@ public class SpectatorEntryPoint extends AbstractEntryPoint implements RegattaRe rootPanel.add(groupOverviewPanel); } else { LeaderboardGroupPanel groupPanel = new LeaderboardGroupPanel(sailingService, stringMessages, this, - groupName, root, viewModeParamValue, embedded, showRaceDetails, canReplayDuringLiveRaces); + groupName, root, viewModeParamValue, embedded, showRaceDetails, canReplayDuringLiveRaces, showMapControls); groupAndFeedbackPanel.add(groupPanel); if (!embedded) { groupPanel.setWelcomeWidget(new SimpleWelcomeWidget(stringMessages.welcomeToSailingAnalytics(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java index 0db2ba48f8b..e0b90d5154f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java @@ -19,7 +19,8 @@ import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.client.SailingService; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; -import com.sap.sse.gwt.server.GwtHttpRequestUtils; +import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sse.gwt.shared.GwtHttpRequestUtils; public class TVEntryPoint extends AbstractEntryPoint { private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName"; @@ -38,8 +39,8 @@ public class TVEntryPoint extends AbstractEntryPoint { public void doOnModuleLoad() { super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); - registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath); final boolean showRaceDetails = Window.Location.getParameter(PARAM_SHOW_RACE_DETAILS) != null && Window.Location.getParameter(PARAM_SHOW_RACE_DETAILS).equalsIgnoreCase("true"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java index 518372b752e..ae6a1c858b5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java @@ -147,7 +147,7 @@ public class TVViewController implements RaceTimesInfoProviderListener { List singletonList = Collections.singletonList(raceToShow); raceSelectionModel.setSelection(singletonList); RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, null, raceboardTimer, raceSelectionModel, leaderboardName, - null, raceboardViewConfig, errorReporter, stringMessages, userAgent, raceTimesInfoProvider); + null, raceboardViewConfig, errorReporter, stringMessages, userAgent, raceTimesInfoProvider, /* showMapControls */ false); return raceBoardPanel; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserManagementPage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserManagementPage.java index 41f9aeb5946..85454d864cd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserManagementPage.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserManagementPage.java @@ -18,6 +18,7 @@ import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.client.UserManagementService; import com.sap.sailing.gwt.ui.client.UserManagementServiceAsync; +import com.sap.sse.gwt.client.EntryPointHelper; import com.sap.sse.gwt.client.dialog.DialogUtils; public class UserManagementPage extends AbstractEntryPoint { @@ -27,7 +28,7 @@ public class UserManagementPage extends AbstractEntryPoint { protected void doOnModuleLoad() { super.doOnModuleLoad(); - registerASyncService((ServiceDefTarget) userManagementService, RemoteServiceMappingConstants.userManagementServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) userManagementService, RemoteServiceMappingConstants.userManagementServiceRemotePath); VerticalPanel vp = new VerticalPanel(); RootPanel.get().add(vp); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java index 6a2e2a8ae16..d3c791e5270 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java @@ -3,5 +3,6 @@ package com.sap.sailing.gwt.ui.usermanagement; public enum UserRoles { administrator, spectator, - moderator + moderator, + eventmanager } diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/FormFactor.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/FormFactor.gwt.xml new file mode 100644 index 00000000000..d847effe92c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/FormFactor.gwt.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + = 0) { + var value = args.substring(start); + var begin = value.indexOf("=") + 1; + var end = value.indexOf("&"); + if (end == -1) { + end = value.length; + } + return value.substring(begin, end); + } + + // Detect form factor from user agent. + var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf("iphone") != -1 || ua.indexOf("ipod") != -1) { + // iphone and ipod. + return "mobile"; + } else if (ua.indexOf("ipad") != -1) { + // ipad. + return "tablet"; + } else if (ua.indexOf("android") != -1 || ua.indexOf("mobile") != -1) { + /* + * Android - determine the form factor of android devices based on the diagonal screen + * size. Anything under six inches is a phone, anything over six inches is a tablet. + */ + var dpi = 160; + var width = $wnd.screen.width / dpi; + var height = $wnd.screen.height / dpi; + var size = Math.sqrt(width*width + height*height); + return (size < 6) ? "mobile" : "tablet"; + } + + // Everything else is a desktop. + return "desktop"; + ]]> + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/Home.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/Home.gwt.xml new file mode 100644 index 00000000000..c29cd5c3947 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/Home.gwt.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/logo.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/logo.png new file mode 100644 index 00000000000..4aa0213d896 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/logo.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/search-icon.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/search-icon.png new file mode 100755 index 00000000000..38bd661b57b Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/search-icon.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/temp--sponsor-1.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/temp--sponsor-1.png new file mode 100644 index 00000000000..01852fde9ea Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/temp--sponsor-1.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/large.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/large.css new file mode 100755 index 00000000000..bd645540ac3 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/large.css @@ -0,0 +1,255 @@ +.column, +.columns { + float: left; + padding: 0 0.9375rem; + position: relative; + width: 100%; +} +.column img, +.columns img { + max-width: 100%; + height: auto; + ms-interpolation-mode: bicubic; +} + +.column.smallcentered, +.columns.smallcentered { +} +.column.smalluncentered, +.columns.smalluncentered { +} + +.column.mediumcentered, +.columns.mediumcentered { +} +.column.mediumuncentered, +.columns.mediumuncentered { +} + +.column.largecentered, +.columns.largecentered { + margin-left: auto; + margin-right: auto; + float: none; +} +.column.largeuncentered, +.columns.largeuncentered { + margin-left: 0; + margin-right: 0; + float: left !important; +} + +.small1 { + width: 8.3333333333333%; +} +.small2 { + width: 16.6666666666667%; +} +.small3 { + width: 25%; +} +.small4 { + width: 33.3333333333333%; +} +.small5 { + width: 41.6666666666667%; +} +.small6 { + width: 50%; +} +.small7 { + width: 58.3333333333333%; +} +.small8 { + width: 66.6666666666667%; +} +.small9 { + width: 75%; +} +.small10 { + width: 83.3333333333333%; +} +.small11 { + width: 91.6666666666667%; +} +.small12 { + width: 100%; +} +.smalloffset0 { + margin-left: 0% !important; +} +.smalloffset1 { + margin-left: 8.3333333333333% !important; +} +.smalloffset2 { + margin-left: 16.6666666666667% !important; +} +.smalloffset3 { + margin-left: 25% !important; +} +.smalloffset4 { + margin-left: 33.3333333333333% !important; +} +.smalloffset5 { + margin-left: 41.6666666666667% !important; +} +.smalloffset6 { + margin-left: 50% !important; +} +.smalloffset7 { + margin-left: 58.3333333333333% !important; +} +.smalloffset8 { + margin-left: 66.6666666666667% !important; +} +.smalloffset9 { + margin-left: 75% !important; +} +.smalloffset10 { + margin-left: 83.3333333333333% !important; +} +.smalloffset11 { + margin-left: 91.6666666666667% !important; +} + +.medium1 { +} +.medium2 { +} +.medium3 { +} +.medium4 { +} +.medium5 { +} +.medium6 { +} +.medium7 { +} +.medium8 { +} +.medium9 { +} +.medium10 { +} +.medium11 { +} +.medium12 { +} +.mediumoffset0 { +} +.mediumoffset1 { +} +.mediumoffset2 { +} +.mediumoffset3 { +} +.mediumoffset4 { +} +.mediumoffset5 { +} +.mediumoffset6 { +} +.mediumoffset7 { +} +.mediumoffset8 { +} +.mediumoffset9 { +} +.mediumoffset10 { +} +.mediumoffset11 { +} + + +.large1 { + width: 8.3333333333333%; +} +.large2 { + width: 16.6666666666667%; +} +.large3 { + width: 25%; +} +.large4 { + width: 33.3333333333333%; +} +.large5 { + width: 41.6666666666667%; +} +.large6 { + width: 50%; +} +.large7 { + width: 58.3333333333333%; +} +.large8 { + width: 66.6666666666667%; +} +.large9 { + width: 75%; +} +.large10 { + width: 83.3333333333333%; +} +.large11 { + width: 91.6666666666667%; +} +.large12 { + width: 100%; +} +.largeoffset0 { + margin-left: 0% !important; +} +.largeoffset1 { + margin-left: 8.3333333333333% !important; +} +.largeoffset2 { + margin-left: 16.6666666666667% !important; +} +.largeoffset3 { + margin-left: 25% !important; +} +.largeoffset4 { + margin-left: 33.3333333333333% !important; +} +.largeoffset5 { + margin-left: 41.6666666666667% !important; +} +.largeoffset6 { + margin-left: 50% !important; +} +.largeoffset7 { + margin-left: 58.3333333333333% !important; +} +.largeoffset8 { + margin-left: 66.6666666666667% !important; +} +.largeoffset9 { + margin-left: 75% !important; +} +.largeoffset10 { + margin-left: 83.3333333333333% !important; +} +.largeoffset11 { + margin-left: 91.6666666666667% !important; +} +.showonsmall { + display: none !important; +} +.hideonsmall { + display: inherit !important; +} +.showonmedium { + display: none !important; +} +.hideonmedium { + display: inherit !important; +} +.showonlarge { + display: inherit !important; +} +.hideonlarge { + display: none !important; +} + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css new file mode 100644 index 00000000000..912b2aa79f1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css @@ -0,0 +1,242 @@ +/** + * The main styling styles + */ +*, +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html, +body { + font-family: 'Proxima Nova', sans-serif; + font-size: 100%; + line-height: 1.333; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.grid, +.stackenblochen { + display: block; + width: 100%; + max-width: 75rem; + overflow: hidden; + list-style: none; + margin: 0 auto; + zoom: 1; +} +.grid:before, +.stackenblochen:before, +.grid:after, +.stackenblochen:after { + content: ""; + display: table; +} +.grid:after, +.stackenblochen:after { + clear: both; +} +.grid *, +.stackenblochen * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.grid .grid, +.stackenblochen .grid, +.grid .stackenblochen, +.stackenblochen .stackenblochen { + width: auto; + max-width: none; + margin-left: -0.9375rem; + margin-right: -0.9375rem; +} +figure { + margin: 0; +} +a { + text-decoration: none; + color: #008fcc; +} +a:hover, +a:focus { + text-decoration: underline; +} +a img { + vertical-align: bottom; +} +hr { + font-size: 16px; + font-size: 1rem; + margin: 0; + height: 1px; + border: none; + border-top: 1px solid #ccc; +} +h2 { + font-size: 2.25em; + font-weight: bold; + color: #008fcc; + margin-top: 0.7em; + margin-bottom: 0.7em; +} +h3 { + font-size: 1.5em; + font-weight: bold; + color: #008fcc; +} +h4 { + font-size: 1.25em; + font-weight: bold; + color: #008fcc; +} +h5 { + font-size: 1em; + font-weight: 600; + color: #008fcc; +} +figure img { + display: block; +} +figcaption { + margin-top: 1.25em; +} +.button { + font-size: 16px; + font-size: 1rem; + display: inline-block; + font-weight: 600; + color: #fff; + border: none; + padding: 0.625em 1.0625em; + background-color: #f0ab00; + -webkit-transition: background-color 0.2s; + -moz-transition: background-color 0.2s; + -o-transition: background-color 0.2s; + -ms-transition: background-color 0.2s; + transition: background-color 0.2s; + text-decoration: none; +} +.button:hover, +.button:focus { + background-color: #d89a00; + outline: none; + text-decoration: none; +} +.buttoninactive { + background-color: #ccc; +} +.buttoninactive:hover { + background-color: #b8b8b8; +} +.buttonrefresh { + background-image: url("data:image/png;base64,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"); + background-repeat: no-repeat; + background-position: left top; + -webkit-background-size: cover; + -moz-background-size: cover; + background-size: cover; + width: 2.1875em; + height: 0px; + padding: 2.1875em 0 0 0; + border: 0; + overflow: hidden; +} +.label { + display: inline-block; + font-size: 12px; + font-size: 0.75rem; + font-weight: 600; + color: #fff; + padding: 0.125em 0.875em; +} +.label[data-labeltype='live'] { + background-color: #f00; + text-transform: uppercase; + vertical-align: middle; + margin-top: -0.625em; +} +.label[data-labeltype='new'], +.label[data-labeltype='updated'] { + background-color: #f0ab00; + text-transform: uppercase; + vertical-align: middle; +} +.label[data-labeltype='ess'] { + background-color: #a71724; +} +.label[data-labeltype='worldcup'] { + background-color: #c79242; +} +.label[data-labeltype='bundesliga'] { + background-color: #005fa7; +} +.morelink { + color: #008fcc; + text-transform: uppercase; + text-align: center; + font-size: 0.875em; + font-weight: 700; + padding: 1.75em; + display: block; + text-decoration: none; +} +.morelink:hover, +.morelink:focus { + text-decoration: underline; +} +.dummy { + font-size: 16px; + font-size: 1rem; + min-height: 25em; + position: relative; +} +.dummy:after { + content: 'Dummy content'; + position: absolute; + top: 50%; + left: 50%; + font-weight: 700; + font-size: 6em; + color: #f7f7f7; + text-shadow: 1px 1px 0 #eee, 2px 2px 0 #eee, 3px 3px 0 #eee, 4px 4px 0 #eee, 5px 5px 0 #eee, 6px 6px 0 #eee, 7px 7px 0 #eee; + margin: -100px 0 0 -350px; +} +.mainsection { + padding-top: 5em; + padding-bottom: 5em; +} +.mainsection_header { + text-align: center; + margin-bottom: 3.4375em; +} +.mainsection_header p { + margin-top: 0; + font-size: 1.125em; +} +.mainsection_header_title { + margin: 0; + font-size: 2.25em; + text-align: center; +} +.mainsection_navigation { + text-align: center; +} +.flag { + width: 2em; + height: 1.5em; + display: inline-block; + background-color: none; + background-repeat: no-repeat; + background-position: left top; + -webkit-background-size: cover; + -moz-background-size: cover; + background-size: cover; +} +.flag_DE { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAwCAYAAAChS3wfAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODhDNkY0MENFRkI5ODA4MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo5ODg5NDQwOENDQkMxMUUzODI0QUE3RDlDN0M3RTY3NiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5ODg5NDQwN0NDQkMxMUUzODI0QUE3RDlDN0M3RTY3NiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OEM5Mzc3NUFBNDIwNjgxMTg4QzZFNzBFRDg2RDZCNjAiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4MDExNzQwNzIwNjgxMTg4QzZGNDBDRUZCOTgwODIiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4wNyR5AAAAkklEQVR42uzXMQoCUQwE0PxFWBtBwXJ7ay/tFbyJx9BGFBTUeI55A5M+rwhkVNWlu+9+KitT9zZ6XLu7ysz9r/Cq3LynCg8AAAAAAAAAAAAAAKFZrdMBzukAh3SApyMIAAAAAAAAAAAAAACAxGfoO8IBllM4wHwMB6iHIwgAAAAAAAAAAAAAAIBMgG3w/pufAAMAFb4MGoJYduUAAAAASUVORK5CYII="); +} +.flag_DK { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAwCAYAAAChS3wfAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODhDNkY0MENFRkI5ODA4MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo5ODg5NDQwNENDQkMxMUUzODI0QUE3RDlDN0M3RTY3NiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5ODg5NDQwM0NDQkMxMUUzODI0QUE3RDlDN0M3RTY3NiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OEM5Mzc3NUFBNDIwNjgxMTg4QzZFNzBFRDg2RDZCNjAiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4MDExNzQwNzIwNjgxMTg4QzZGNDBDRUZCOTgwODIiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5DMAoTAAABL0lEQVR42uzZvUpDMRjG8ef0RKsSETkgFb0Bt25dRUSd3Ls4O3o3vYWuDl6B4tZRwUFxcBG30kprz4c5de2ZzBDI/4FkebPkN4SXvMmj7T5J2nOr1D9TTL6VXZ7o6Hawsv41vNNL/0aptQogqVtvxm37bu0qzkxbblso3ixaijwAAAAAAAAAAAAAAAAAAAAAABBhTP2N5SuFpipn88Z6lefLM5qE427qPzxfKX/m2u51G+vtw46yizOl65vBACRVVX3q71M0xrzyCAIAAABx9wH1uMpXqiJX+6CjnePeyvrs/UPjh5ESY8IBqGd1Phuh7PS8EWB8P9Lz1bWMbDgAXgeVrsNLtzaam441s7x8IMNR3gAAAAAAAAAAAAAAAAAAAAAAAAAgXgAb8f3trwADADbjPWP+ur75AAAAAElFTkSuQmCC"); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/media.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/media.css new file mode 100644 index 00000000000..ec079b36ddb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/media.css @@ -0,0 +1,243 @@ +.column, +.columns { + float: left; + padding: 0 0.9375rem; + position: relative; + width: 100%; +} +.column img, +.columns img { + max-width: 100%; + height: auto; + ms-interpolation-mode: bicubic; +} + +.column, +.columns { + float: left; + padding: 0 0.9375rem; + position: relative; + width: 100%; +} +.column img, +.columns img { + max-width: 100%; + height: auto; + ms-interpolation-mode: bicubic; +} + +.column.smallcentered, +.columns.smallcentered { +} +.column.smalluncentered, +.columns.smalluncentered { +} + +.column.mediumcentered, +.columns.mediumcentered { + margin-left: auto; + margin-right: auto; + float: none; +} +.column.mediumuncentered, +.columns.mediumuncentered { + margin-left: 0; + margin-right: 0; + float: left !important; +} + +.column.largecentered, +.columns.largecentered { +} +.column.largeuncentered, +.columns.largeuncentered { +} + +.small1 { + width: 8.3333333333333%; +} +.small2 { + width: 16.6666666666667%; +} +.small3 { + width: 25%; +} +.small4 { + width: 33.3333333333333%; +} +.small5 { + width: 41.6666666666667%; +} +.small6 { + width: 50%; +} +.small7 { + width: 58.3333333333333%; +} +.small8 { + width: 66.6666666666667%; +} +.small9 { + width: 75%; +} +.small10 { + width: 83.3333333333333%; +} +.small11 { + width: 91.6666666666667%; +} +.small12 { + width: 100%; +} +.smalloffset0 { + margin-left: 0% !important; +} +.smalloffset1 { + margin-left: 8.3333333333333% !important; +} +.smalloffset2 { + margin-left: 16.6666666666667% !important; +} +.smalloffset3 { + margin-left: 25% !important; +} +.smalloffset4 { + margin-left: 33.3333333333333% !important; +} +.smalloffset5 { + margin-left: 41.6666666666667% !important; +} +.smalloffset6 { + margin-left: 50% !important; +} +.smalloffset7 { + margin-left: 58.3333333333333% !important; +} +.smalloffset8 { + margin-left: 66.6666666666667% !important; +} +.smalloffset9 { + margin-left: 75% !important; +} +.smalloffset10 { + margin-left: 83.3333333333333% !important; +} +.smalloffset11 { + margin-left: 91.6666666666667% !important; +} +.showonsmall { + display: inherit !important; +} +.hideonsmall { + display: none !important; +} +.showonmedium { + display: none !important; +} +.hideonmedium { + display: inherit !important; +} +.showonlarge { + display: none !important; +} +.hideonlarge { + display: inherit !important; +} + +.medium1 { +} +.medium2 { +} +.medium3 { +} +.medium4 { +} +.medium5 { +} +.medium6 { +} +.medium7 { +} +.medium8 { +} +.medium9 { +} +.medium10 { +} +.medium11 { +} +.medium12 { +} +.mediumoffset0 { +} +.mediumoffset1 { +} +.mediumoffset2 { +} +.mediumoffset3 { +} +.mediumoffset4 { +} +.mediumoffset5 { +} +.mediumoffset6 { +} +.mediumoffset7 { +} +.mediumoffset8 { +} +.mediumoffset9 { +} +.mediumoffset10 { +} +.mediumoffset11 { +} + +.large1 { +} +.large2 { +} +.large3 { +} +.large4 { +} +.large5 { +} +.large6 { +} +.large7 { +} +.large8 { +} +.large9 { +} +.large10 { +} +.large11 { +} +.large12 { +} +.largeoffset0 { +} +.largeoffset1 { +} +.largeoffset2 { +} +.largeoffset3 { +} +.largeoffset4 { +} +.largeoffset5 { +} +.largeoffset6 { +} +.largeoffset7 { +} +.largeoffset8 { +} +.largeoffset9 { +} +.largeoffset10 { +} +.largeoffset11 { +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/medium.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/medium.css new file mode 100755 index 00000000000..45f79882e46 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/medium.css @@ -0,0 +1,269 @@ +.column, +.columns { + float: left; + padding: 0 0.9375rem; + position: relative; + width: 100%; +} +.column img, +.columns img { + max-width: 100%; + height: auto; + ms-interpolation-mode: bicubic; +} + +.column, +.columns { + float: left; + padding: 0 0.9375rem; + position: relative; + width: 100%; +} +.column img, +.columns img { + max-width: 100%; + height: auto; + ms-interpolation-mode: bicubic; +} + + +.column.smallcentered, +.columns.smallcentered { +} +.column.smalluncentered, +.columns.smalluncentered { +} + +.column.mediumcentered, +.columns.mediumcentered { + margin-left: auto; + margin-right: auto; + float: none; +} +.column.mediumuncentered, +.columns.mediumuncentered { + margin-left: 0; + margin-right: 0; + float: left !important; +} + +.column.largecentered, +.columns.largecentered { +} +.column.largeuncentered, +.columns.largeuncentered { +} + +.small1 { + width: 8.3333333333333%; +} +.small2 { + width: 16.6666666666667%; +} +.small3 { + width: 25%; +} +.small4 { + width: 33.3333333333333%; +} +.small5 { + width: 41.6666666666667%; +} +.small6 { + width: 50%; +} +.small7 { + width: 58.3333333333333%; +} +.small8 { + width: 66.6666666666667%; +} +.small9 { + width: 75%; +} +.small10 { + width: 83.3333333333333%; +} +.small11 { + width: 91.6666666666667%; +} +.small12 { + width: 100%; +} +.smalloffset0 { + margin-left: 0% !important; +} +.smalloffset1 { + margin-left: 8.3333333333333% !important; +} +.smalloffset2 { + margin-left: 16.6666666666667% !important; +} +.smalloffset3 { + margin-left: 25% !important; +} +.smalloffset4 { + margin-left: 33.3333333333333% !important; +} +.smalloffset5 { + margin-left: 41.6666666666667% !important; +} +.smalloffset6 { + margin-left: 50% !important; +} +.smalloffset7 { + margin-left: 58.3333333333333% !important; +} +.smalloffset8 { + margin-left: 66.6666666666667% !important; +} +.smalloffset9 { + margin-left: 75% !important; +} +.smalloffset10 { + margin-left: 83.3333333333333% !important; +} +.smalloffset11 { + margin-left: 91.6666666666667% !important; +} + +.medium1 { + width: 8.3333333333333%; +} +.medium2 { + width: 16.6666666666667%; +} +.medium3 { + width: 25%; +} +.medium4 { + width: 33.3333333333333%; +} +.medium5 { + width: 41.6666666666667%; +} +.medium6 { + width: 50%; +} +.medium7 { + width: 58.3333333333333%; +} +.medium8 { + width: 66.6666666666667%; +} +.medium9 { + width: 75%; +} +.medium10 { + width: 83.3333333333333%; +} +.medium11 { + width: 91.6666666666667%; +} +.medium12 { + width: 100%; +} +.mediumoffset0 { + margin-left: 0% !important; +} +.mediumoffset1 { + margin-left: 8.3333333333333% !important; +} +.mediumoffset2 { + margin-left: 16.6666666666667% !important; +} +.mediumoffset3 { + margin-left: 25% !important; +} +.mediumoffset4 { + margin-left: 33.3333333333333% !important; +} +.mediumoffset5 { + margin-left: 41.6666666666667% !important; +} +.mediumoffset6 { + margin-left: 50% !important; +} +.mediumoffset7 { + margin-left: 58.3333333333333% !important; +} +.mediumoffset8 { + margin-left: 66.6666666666667% !important; +} +.mediumoffset9 { + margin-left: 75% !important; +} +.mediumoffset10 { + margin-left: 83.3333333333333% !important; +} +.mediumoffset11 { + margin-left: 91.6666666666667% !important; +} + +.large1 { +} +.large2 { +} +.large3 { +} +.large4 { +} +.large5 { +} +.large6 { +} +.large7 { +} +.large8 { +} +.large9 { +} +.large10 { +} +.large11 { +} +.large12 { +} +.largeoffset0 { +} +.largeoffset1 { +} +.largeoffset2 { +} +.largeoffset3 { +} +.largeoffset4 { +} +.largeoffset5 { +} +.largeoffset6 { +} +.largeoffset7 { +} +.largeoffset8 { +} +.largeoffset9 { +} +.largeoffset10 { +} +.largeoffset11 { +} + +.showonsmall { + display: none !important; +} +.hideonsmall { + display: inherit !important; +} +.showonmedium { + display: inherit !important; +} +.hideonmedium { + display: none !important; +} +.showonlarge { + display: none !important; +} +.hideonlarge { + display: inherit !important; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/AdminConsole.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/AdminConsole.gwt.xml old mode 100755 new mode 100644 index 4ae106e2bcd..3c4267e675b --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/AdminConsole.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/AdminConsole.gwt.xml @@ -3,70 +3,71 @@ - - + + + + + + + + - - - - - - - - - + + + + + + + - - + + - - + + + + - - - - + - - + + + + + + + + + - - - - - - - - - - - + - - + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/DataMining.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/DataMining.gwt.xml index 6ed344d4767..7c7c26cc1db 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/DataMining.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/DataMining.gwt.xml @@ -9,6 +9,14 @@ + + + + + + + + @@ -20,10 +28,13 @@ - + + - + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/EmulatedJRE.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/EmulatedJRE.gwt.xml index 91be341f7ed..b2c4356a0e2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/EmulatedJRE.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/EmulatedJRE.gwt.xml @@ -2,11 +2,10 @@ - - - - - - + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Leaderboard.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Leaderboard.gwt.xml old mode 100755 new mode 100644 index ca8dfe51451..674f54d4acf --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Leaderboard.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Leaderboard.gwt.xml @@ -3,39 +3,53 @@ - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/LeaderboardEditing.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/LeaderboardEditing.gwt.xml old mode 100755 new mode 100644 index 76a1f296622..32357e0725f --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/LeaderboardEditing.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/LeaderboardEditing.gwt.xml @@ -3,40 +3,48 @@ - - + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/PolarSheets.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/PolarSheets.gwt.xml old mode 100755 new mode 100644 index 31201a629a0..58f3a09d307 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/PolarSheets.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/PolarSheets.gwt.xml @@ -5,8 +5,30 @@ - + + + + + + + + + + + + + + + + + + + + + + + @@ -18,19 +40,4 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RaceBoard.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RaceBoard.gwt.xml old mode 100755 new mode 100644 index 389070a02f2..362f099acc5 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RaceBoard.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RaceBoard.gwt.xml @@ -3,44 +3,45 @@ - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RegattaOverview.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RegattaOverview.gwt.xml old mode 100755 new mode 100644 index e67cdf024bb..d1b484868a1 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RegattaOverview.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RegattaOverview.gwt.xml @@ -3,52 +3,61 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Search.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Search.gwt.xml new file mode 100644 index 00000000000..177244e8f24 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Search.gwt.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Simulator.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Simulator.gwt.xml old mode 100755 new mode 100644 index 12a2764741a..6e0b90567c2 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Simulator.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Simulator.gwt.xml @@ -2,7 +2,8 @@ - + + @@ -14,12 +15,7 @@ - - - - - @@ -27,15 +23,31 @@ + + + + + + + + + + + - - + + + + + +